From 049e160852fd0ffed342715bf6fd556ea2541c8e Mon Sep 17 00:00:00 2001 From: 4DBug <4DBug@github.com> Date: Thu, 15 Oct 2026 03:49:53 -0500 Subject: [PATCH] Port multi-select, box-select and typing-select (BeginMultiSelect/EndMultiSelect, SetNextItemSelectionUserData, MultiSelectItemHeader/Footer, BeginBoxSelect/EndBoxSelect, GetTypingSelectRequest/TypingSelectFind*, ImGuiSelectionBasicStorage/ExternalStorage, Metrics nodes); wired into Selectable/Checkbox/TreeNode/clipper/tables; t_multiselect --- build/ImGui.linemap.json | 2 +- build/ImGui.luau | 1691 ++++++++++++++++++++++++- build/ImGuiTyped.luau | 22 + build/imgui_love.linemap.json | 2 +- build/imgui_love.lua | 1691 ++++++++++++++++++++++++- docs/PORT_STATUS.txt | 1131 ++++++++--------- imgui.lua | 28 +- imgui_h.lua | 116 ++ imgui_internal.lua | 195 +++ imgui_tables.lua | 2 +- imgui_widgets.lua | 1333 ++++++++++++++++++- tools/test/check_all.sh | 1 + tools/test/scripts/t_multiselect.luau | 265 ++++ 13 files changed, 5881 insertions(+), 598 deletions(-) create mode 100644 tools/test/scripts/t_multiselect.luau diff --git a/build/ImGui.linemap.json b/build/ImGui.linemap.json index e6c0ae5..985990e 100644 --- a/build/ImGui.linemap.json +++ b/build/ImGui.linemap.json @@ -1 +1 @@ -[[46, "imgui.lua"], [21413, "imgui_demo.lua"], [31841, "imgui_draw.lua"], [37784, "imgui_h.lua"], [40511, "imgui_impl_roblox.lua"], [42159, "imgui_impl_roblox_fonts.lua"], [48562, "imgui_internal.lua"], [52034, "imgui_tables.lua"], [57093, "imgui_widgets.lua"], [67757, "imstb_rectpack.lua"], [68421, "imstb_textedit.lua"], [69963, "imstb_truetype.lua"], [72751, "imstd_minstdio.lua"]] \ No newline at end of file +[[46, "imgui.lua"], [21437, "imgui_demo.lua"], [31865, "imgui_draw.lua"], [37808, "imgui_h.lua"], [40651, "imgui_impl_roblox.lua"], [42299, "imgui_impl_roblox_fonts.lua"], [48702, "imgui_internal.lua"], [52369, "imgui_tables.lua"], [57428, "imgui_widgets.lua"], [69417, "imstb_rectpack.lua"], [70081, "imstb_textedit.lua"], [71623, "imstb_truetype.lua"], [74411, "imstd_minstdio.lua"]] \ No newline at end of file diff --git a/build/ImGui.luau b/build/ImGui.luau index f072ed4..fb198ea 100644 --- a/build/ImGui.luau +++ b/build/ImGui.luau @@ -1,7 +1,7 @@ --!optimize 2 -- GENERATED by tools/bundle.py from *.lua, backends/, fonts/ - do not edit -local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImGuiInputFlags, ImRect_Copy, ImGuiDataType, ImStb, IM_TRUNC, ImGuiKey, ImGuiStyleVarInfo, ImVec2_CopyV, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, ImHashStr, ImS32, IM_ASSERT_USER_ERROR_RET, ImGuiColorEditFlags, ImDrawFlags, ImIsPowerOfTwo, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, ImU16, ImAbs, ImGuiWindowFlags, ImGuiItemFlags, ImGui_ImplRoblox_GetBackendData, stbte, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ImColor, ImCos, ImS16, ImGuiTabBarFlags, ImSin, ImUpperPowerOfTwo, ImVec2_MulVX, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, DockSettingsRenameNodeReferences, IM_ASSERT_PARANOID, ImCeil, ImGuiCol, ImGuiListClipper, ImLengthSqr, ImS8, ImU8, _PathArcToFastExXY, ExampleAppPropertyEditorMT, ImFabs, ImGuiChildFlags, ImGuiLastItemData_Copy, ImGuiScrollFlags, ImLog, ImMemchr, ImStrByte, IMGUI_DEBUG_LOG_VIEWPORT, ImGuiInputEvent, ImGuiTextBuffer, ImPow, CustomConstraints, ExampleAppLogMT, IMGUI_DEBUG_LOG_FONT, ImAddClampOverflow, ImFontAtlasBuildInit, ImFontCalcWordWrapPositionEx, ImGuiComboFlags, ImGuiLastItemData, ImGuiPayload_IsDataType, ImGuiPopupFlags, ImGuiSliderFlags, ImGuiTextFilter, ImRotate, ImSubClampOverflow, ImTextClassifierSetCharClass, ImVec2_AddVA, ImVec2_SubVA, MyDocument, ShowExampleMenuFile, CalcNextScrollFromScrollTargetAndClamp, IMGUI_PAYLOAD_TYPE_WINDOW, IMGUI_VERSION, ImCharIsSeparatorW, ImFmod, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiNavMoveFlags, ImGuiStyle_Copy, ImHashData, ImVec1_Copy, ExampleMemberInfo, ExampleTree_CreateNode, IMGUI_PAYLOAD_TYPE_COLOR_4F, IM_F32_TO_INT8_UNBOUND, IM_PI, ImCharIsBlankA, ImCharIsBlankW, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontDiscardBakes, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImFontAtlasRectId_GetIndex, ImFontAtlasTextureAdd, ImFontAtlasTextureBlockQueueUpload, ImFontAtlasTextureGrow, ImFontBaked_BuildGrowIndex, ImFontConfig, ImFontGlyph, ImGuiTableColumn, ImGuiWindowClass, ImGui_ImplRoblox_RenderDrawData, ImParseFormatFindStart, ImRect_CopyFromV4, ImTextureRect, ImTextureRef, ImTrunc64, MyItem_s_current_sort_specs, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, DockSettingsHandler_ClearAll, EditTableSizingFlags, IMGUI_DEBUG_LOG_TABLE, IMGUI_PAYLOAD_TYPE_COLOR_3F, IM_FREE, ImDrawListSplitter, ImFontAtlasBakedDiscard, ImFontAtlasBuildDestroy, ImFontAtlasBuildNotifySetFont, ImFontAtlasBuildSetTexture, ImFontAtlasBuildSetupFontBakedFallback, ImFontAtlasFontDestroySourceData, ImFontAtlasPackInit, ImFontAtlasTextureBlockCopy, ImFontAtlasTextureGetSizeEstimate, ImFontAtlasUpdateDrawListsSharedData, ImFontBaked_BuildLoadGlyphAdvanceX, ImFontCalcTextSizeEx, ImFontCalcWordWrapPositionEx_Uncached, ImGuiDragDropFlags, ImGuiFocusScopeData, ImGuiFocusedFlags, ImGuiInputTextCallbackData, ImGuiSettingsHandler +local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImGuiInputFlags, ImRect_Copy, ImVec2_CopyV, ImGuiDataType, ImStb, IM_TRUNC, ImGuiKey, ImGuiStyleVarInfo, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, ImHashStr, ImS32, IM_ASSERT_USER_ERROR_RET, ImGuiColorEditFlags, ImDrawFlags, ImIsPowerOfTwo, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, ImAbs, ImU16, ImGuiWindowFlags, ImGuiItemFlags, ImGui_ImplRoblox_GetBackendData, stbte, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ImColor, ImCos, ImS16, ImGuiTabBarFlags, ImSin, ImUpperPowerOfTwo, ImVec2_MulVX, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, DockSettingsRenameNodeReferences, IM_ASSERT_PARANOID, ImCeil, ImGuiCol, ImGuiListClipper, ImLengthSqr, ImS8, ImU8, _PathArcToFastExXY, ExampleAppPropertyEditorMT, ImFabs, ImGuiChildFlags, ImGuiLastItemData_Copy, ImGuiScrollFlags, ImLog, ImMemchr, ImStrByte, IMGUI_DEBUG_LOG_VIEWPORT, ImGuiInputEvent, ImGuiTextBuffer, ImPow, CustomConstraints, ExampleAppLogMT, IMGUI_DEBUG_LOG_FONT, IMGUI_DEBUG_LOG_SELECTION, ImAddClampOverflow, ImFontAtlasBuildInit, ImFontCalcWordWrapPositionEx, ImGuiComboFlags, ImGuiLastItemData, ImGuiMultiSelectFlags, ImGuiPayload_IsDataType, ImGuiPopupFlags, ImGuiSliderFlags, ImGuiTextFilter, ImRotate, ImSubClampOverflow, ImTextClassifierSetCharClass, ImVec2_AddVA, ImVec2_SubVA, MyDocument, ShowExampleMenuFile, CalcNextScrollFromScrollTargetAndClamp, IMGUI_PAYLOAD_TYPE_WINDOW, IMGUI_VERSION, ImCharIsBlankW, ImCharIsSeparatorW, ImFmod, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiNavMoveFlags, ImGuiStyle_Copy, ImHashData, ImVec1_Copy, ExampleMemberInfo, ExampleTree_CreateNode, IMGUI_PAYLOAD_TYPE_COLOR_4F, IM_F32_TO_INT8_UNBOUND, IM_PI, ImCharIsBlankA, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontDiscardBakes, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImFontAtlasRectId_GetIndex, ImFontAtlasTextureAdd, ImFontAtlasTextureBlockQueueUpload, ImFontAtlasTextureGrow, ImFontBaked_BuildGrowIndex, ImFontConfig, ImFontGlyph, ImGuiTableColumn, ImGuiWindowClass, ImGui_ImplRoblox_RenderDrawData, ImParseFormatFindStart, ImRect_CopyFromV4, ImTextureRect, ImTextureRef, ImTrunc64, MyItem_s_current_sort_specs, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, DockSettingsHandler_ClearAll, EditTableSizingFlags, IMGUI_DEBUG_LOG_TABLE, IMGUI_PAYLOAD_TYPE_COLOR_3F, IM_FREE, ImDrawListSplitter, ImFontAtlasBakedDiscard, ImFontAtlasBuildDestroy, ImFontAtlasBuildNotifySetFont, ImFontAtlasBuildSetTexture, ImFontAtlasBuildSetupFontBakedFallback, ImFontAtlasFontDestroySourceData, ImFontAtlasPackInit, ImFontAtlasTextureBlockCopy, ImFontAtlasTextureGetSizeEstimate, ImFontAtlasUpdateDrawListsSharedData, ImFontBaked_BuildLoadGlyphAdvanceX, ImFontCalcTextSizeEx, ImFontCalcWordWrapPositionEx_Uncached, ImGuiDragDropFlags, ImGuiFocusScopeData, ImGuiFocusedFlags local type, mathMin, mathMax, mathFloor, mathCeil, bitOr, bitLShift = type, math.min, math.max, math.floor, math.ceil, bit32.bor, bit32.lshift local function IM_COL32(R, G, B, A) return (bitOr(bitLShift(A, 24 --[[IM_COL32_A_SHIFT]]), bitLShift(B, 16 --[[IM_COL32_B_SHIFT]]), bitLShift(G, 8 --[[IM_COL32_G_SHIFT]]), bitLShift(R, 0 --[[IM_COL32_R_SHIFT]]))) end @native @@ -10873,6 +10873,8 @@ function ImGui.GcCompactTransientMiscBuffers() g.ItemFlagsStack:clear() g.GroupStack:clear() g.InputTextLineIndex:clear() + g.MultiSelectTempDataStacked = 0 + g.MultiSelectTempData:clear() ImGui.TableGcCompactSettings() for _, atlas in g.FontAtlases:iter() do atlas:CompactCache() @@ -14369,6 +14371,8 @@ function ImGui.Shutdown() -- TODO: -- Clear everything else + g.MultiSelectStorage:Clear() + g.MultiSelectTempData:clear() g.MenusIdSubmittedThisFrame:clear() end @@ -19745,7 +19749,7 @@ local function ImGuiListClipper_StepInternal(clipper) -- Add box selection range local bs = g.BoxSelectState - if bs and bs.IsActive and bs.Window == window then + if bs.IsActive and bs.Window == window then -- FIXME: Selectable() use of half-ItemSpacing isn't consistent in matter of layout, as ItemAdd(bb) stray above ItemSize()'s CursorPos. -- RangeSelect's BoxSelect relies on comparing overlap of previous and current rectangle and is sensitive to that. -- As a workaround we currently half ItemSpacing worth on each side. @@ -19891,7 +19895,7 @@ function ImGui.ErrorRecoveryTryToRecoverWindowState(state_in) IM_ASSERT_USER_ERROR(false, "Missing EndTabBar()") ImGui.EndTabBar() end - while g.CurrentMultiSelect ~= nil and g.CurrentMultiSelect.Storage.Window == window and ImGui.EndMultiSelect do + while g.CurrentMultiSelect ~= nil and g.CurrentMultiSelect.Storage.Window == window do --V1044 IM_ASSERT_USER_ERROR(false, "Missing EndMultiSelect()") ImGui.EndMultiSelect() end @@ -21228,6 +21232,25 @@ function ImGui.ShowMetricsWindow(p_open) ImGui.TreePop() end + -- Details for TypingSelect + if ImGui.TreeNode("TypingSelect", "TypingSelect (%d)", (#g.TypingSelectState.SearchBuffer ~= 0) and 1 or 0) then + ImGui.DebugNodeTypingSelectState(g.TypingSelectState) + ImGui.TreePop() + end + + -- Details for MultiSelect + if ImGui.TreeNode("MultiSelect", "MultiSelect (%d)", g.MultiSelectStorage.AliveCount) then + local bs = g.BoxSelectState + ImGui.BulletText("BoxSelect ID=0x%08X, Starting = %d, Active %d", bs.ID, B(bs.IsStarting), B(bs.IsActive)) + for n = 0, g.MultiSelectStorage:GetMapSize() - 1 do + local state = g.MultiSelectStorage:GetByIndex(n) + if state then + ImGui.DebugNodeMultiSelectState(state) + end + end + ImGui.TreePop() + end + -- Details for Docking if ImGui.TreeNode("Docking") then local dc = g.DockContext @@ -21355,6 +21378,7 @@ function ImGui.ShowMetricsWindow(p_open) ImGui.Indent() ImGui.Text("NavWindow: '%s'", N(g.NavWindow)) ImGui.Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer) + ImGui.Text("NavLastValidSelectionUserData = %s", tostring(g.NavLastValidSelectionUserData)) -- [Port] any Lua value: tostring() instead of %lld (0x%llX) ImGui.Text("NavActive: %d, NavVisible: %d", B(io.NavActive), B(io.NavVisible)) ImGui.Text("NavActivateId/DownId/PressedId: %08X/%08X/%08X", g.NavActivateId, g.NavActivateDownId or 0, g.NavActivatePressedId or 0) ImGui.Text("NavActivateFlags: %04X", g.NavActivateFlags or 0) @@ -40496,6 +40520,122 @@ function ImColorMT:U32() return ImGui.ColorConvertFloat4ToU32(self.Value) end function ImColorMT:SetHSV(h, s, v, a) self.Value.x, self.Value.y, self.Value.z = ImGui.ColorConvertHSVtoRGB(h, s, v); self.Value.w = a or 1.0 end function ImColor.HSV(h, s, v, a) local r, g, b = ImGui.ColorConvertHSVtoRGB(h, s, v); return ImColor(r, g, b, a or 1.0) end +------------------------------------------------------------------------------- +-- [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiSelectionRequestType, ImGuiSelectionRequest, ImGuiMultiSelectIO, ImGuiSelectionBasicStorage) +------------------------------------------------------------------------------- + +-- Multi-selection system +-- Documentation at: https://github.com/ocornut/imgui/wiki/Multi-Select +-- - Refer to 'Demo->Widgets->Selection State & Multi-Select' for demos using this. +-- - This system implements standard multi-selection idioms (Ctrl+Mouse/Keyboard, Shift+Mouse/Keyboard, etc) +-- with support for clipper (skipping non-visible items), box-select and many other details. +-- - Selectable(), Checkbox() are supported but custom widgets may use it as well. +-- - TreeNode() is technically supported but... using this correctly is more complicated: you need some sort of linear/random access to your tree, +-- which is suited to advanced trees setups also implementing filters and clipper. We will work toward simplifying and demoing it. +-- - In the spirit of Dear ImGui design, your code owns actual selection data. +-- This is designed to allow all kinds of selection storage you may use in your application e.g. set/map/hash. +-- About ImGuiSelectionBasicStorage: +-- - This is an optional helper to store a selection state and apply selection requests. +-- - It is used by our demos and provided as a convenience to quickly implement multi-selection. +-- Usage: +-- - Identify submitted items with SetNextItemSelectionUserData(), most likely using an index into your current data-set. +-- - Store and maintain actual selection data using persistent object identifiers. +-- - Usage flow: +-- BEGIN - (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO result. +-- - (2) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 6. +-- - (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. Calculate its index and pass to clipper:IncludeItemByIndex(). If storing indices in ImGuiSelectionUserData, a simple clipper:IncludeItemByIndex(ms_io.RangeSrcItem) call will work. +-- LOOP - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls. +-- END - (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO result. +-- - (6) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 2. +-- If you submit all items (no clipper), Step 2 and 3 are optional and will be handled by each item themselves. It is fine to always honor those steps. +-- About ImGuiSelectionUserData: +-- - This can store an application-defined identifier (e.g. index or pointer) submitted via SetNextItemSelectionUserData(). +-- - In return we store them into RangeSrcItem/RangeFirstItem/RangeLastItem and other fields in ImGuiMultiSelectIO. +-- - Most applications will store an object INDEX, hence the chosen name and type. Storing an index is natural, because +-- SetRange requests will give you two end-points and you will need to iterate/interpolate between them to update your selection. +-- - However it is perfectly possible to store a POINTER or another IDENTIFIER inside ImGuiSelectionUserData. +-- Our system never assume that you identify items by indices, it never attempts to interpolate between two values. +-- - If you enable ImGuiMultiSelectFlags.NoRangeSelect then it is guaranteed that you will never have to interpolate +-- between two ImGuiSelectionUserData, which may be a convenient way to use part of the feature with less code work. +-- - As most users will want to store an index, for convenience and to reduce confusion we use ImS64 instead of void*, +-- being syntactically easier to downcast. Feel free to reinterpret_cast and store a pointer inside. +-- [Port] ImGuiSelectionUserData is any Lua value compared with '==' (an integer index, or e.g. a table reference instead of a pointer). +-- -1 (ImGuiSelectionUserData_Invalid) is reserved. ImGuiSelectionBasicStorage/ImGuiSelectionExternalStorage require integer indices. + +-- Flags for BeginMultiSelect() +--- @enum ImGuiMultiSelectFlags +ImGuiMultiSelectFlags = { + None = 0, + SingleSelect = bitLShift(1, 0), -- Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho! + NoSelectAll = bitLShift(1, 1), -- Disable Ctrl+A shortcut to select all. + NoRangeSelect = bitLShift(1, 2), -- Disable Shift+selection mouse/keyboard support (useful for unordered 2D selection). With BoxSelect is also ensure contiguous SetRange requests are not combined into one. This allows not handling interpolation in SetRange requests. + NoAutoSelect = bitLShift(1, 3), -- Disable selecting items when navigating (useful for e.g. supporting range-select in a list of checkboxes). + NoAutoClear = bitLShift(1, 4), -- Disable clearing selection when navigating or selecting another one (generally used with ImGuiMultiSelectFlags.NoAutoSelect. useful for e.g. supporting range-select in a list of checkboxes). + NoAutoClearOnReselect = bitLShift(1, 5), -- Disable clearing selection when clicking/selecting an already selected item. + BoxSelect1d = bitLShift(1, 6), -- Enable box-selection with same width and same x pos items (e.g. full row Selectable()). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space. + BoxSelect2d = bitLShift(1, 7), -- Enable box-selection with varying width or varying x pos items support (e.g. different width labels, or 2D layout/grid). This is slower: alters clipping logic so that e.g. horizontal movements will update selection of normally clipped items. + BoxSelectNoScroll = bitLShift(1, 8), -- Disable scrolling when box-selecting and moving mouse near edges of scope. + ClearOnEscape = bitLShift(1, 9), -- Clear selection when pressing Escape while scope is focused. + ClearOnClickVoid = bitLShift(1, 10), -- Clear selection when clicking on empty location within scope. + ScopeWindow = bitLShift(1, 11), -- Scope for _BoxSelect and _ClearOnClickVoid is whole window (Default). Use if BeginMultiSelect() covers a whole window or used a single time in same window. + ScopeRect = bitLShift(1, 12), -- Scope for _BoxSelect and _ClearOnClickVoid is rectangle encompassing BeginMultiSelect()/EndMultiSelect(). Use if BeginMultiSelect() is called multiple times in same window. + SelectOnAuto = bitLShift(1, 13), -- Apply selection on mouse down when clicking on unselected item, on mouse up when clicking on selected item. (Default) + SelectOnClickAlways = bitLShift(1, 14), -- Apply selection on mouse down when clicking on any items. Prevents Drag and Drop from being used on multiple-selection, but allows e.g. BoxSelect to always reselect even when clicking inside an existing selection. (Excel style behavior) + SelectOnClickRelease = bitLShift(1, 15), -- Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection. + --RangeSelect2d = bitLShift(1, 15), -- Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does. + NavWrapX = bitLShift(1, 16), -- [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one. + NoSelectOnRightClick = bitLShift(1, 17), -- Disable default right-click processing, which selects item on mouse down, and is designed for context-menus. + CheckboxMode_ = bitLShift(1, 20), -- [Internal] +} +ImGuiMultiSelectFlags.SelectOnMask_ = bit32.bor(ImGuiMultiSelectFlags.SelectOnAuto, ImGuiMultiSelectFlags.SelectOnClickAlways, ImGuiMultiSelectFlags.SelectOnClickRelease) +ImGuiMultiSelectFlags.SelectOnClick = ImGuiMultiSelectFlags.SelectOnAuto -- RENAMED in 1.92.6 + +-- Main IO structure returned by BeginMultiSelect()/EndMultiSelect(). +-- This mainly contains a list of selection requests. +-- - Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen. +-- - Some fields are only useful if your list is dynamic and allows deletion (getting post-deletion focus/state right is shown in the demo) +-- - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code. +--- @class ImGuiMultiSelectIO +--- @field Requests ImVector # ms:w, app:r / ms:w app:r // Requests to apply to your selection data. (1-based .Data) +--- @field RangeSrcItem ImGuiSelectionUserData # ms:w app:r / // (If using clipper) Begin: Source item (often the first selected item) must never be clipped: use clipper:IncludeItemByIndex() to ensure it is submitted. +--- @field NavIdItem ImGuiSelectionUserData # ms:w, app:r / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items). +--- @field NavIdSelected bool # ms:w, app:r / app:r // (If using deletion) Last known selection state for NavId (if part of submitted items). +--- @field RangeSrcReset bool # app:w / ms:r // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection). +--- @field ItemsCount int # ms:w, app:r / app:r // 'int items_count' parameter to BeginMultiSelect() is copied here for convenience, allowing simpler calls to your ApplyRequests handler. Not used internally. + +-- Selection request type +--- @enum ImGuiSelectionRequestType +ImGuiSelectionRequestType = { + None = 0, + SetAll = 1, -- Request app to clear selection (if Selected==false) or select all items (if Selected==true). We cannot set RangeFirstItem/RangeLastItem as its contents is entirely up to user (not necessarily an index) + SetRange = 2, -- Request app to select/unselect [RangeFirstItem..RangeLastItem] items (inclusive) based on value of Selected. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false. +} + +-- Selection request item +--- @class ImGuiSelectionRequest +--- @field Type ImGuiSelectionRequestType # ms:w, app:r / ms:w, app:r // Request type. You'll most often receive 1 Clear + 1 SetRange with a single-item range. +--- @field Selected bool # ms:w, app:r / ms:w, app:r // Parameter for SetAll/SetRange requests (true = select, false = unselect) +--- @field RangeDirection int # / ms:w app:r // Parameter for SetRange request: +1 when RangeFirstItem comes before RangeLastItem, -1 otherwise. Useful if you want to preserve selection order on a backward Shift+Click. +--- @field RangeFirstItem ImGuiSelectionUserData # / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom). +--- @field RangeLastItem ImGuiSelectionUserData # / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top). Inclusive! +function ImGuiSelectionRequest() + return { Type = 0 --[[ImGuiSelectionRequestType.None]], Selected = false, RangeDirection = 0, RangeFirstItem = -1, RangeLastItem = -1 } +end +-- [Port] ImVector copy function: push_back() copies fields into the preallocated element (C++ copies by value), +-- so callers can push a reused scratch request without allocating. +function ImGuiSelectionRequest_Copy(t, k, v) + local dst = t[k] + dst.Type = v.Type; dst.Selected = v.Selected; dst.RangeDirection = v.RangeDirection; dst.RangeFirstItem = v.RangeFirstItem; dst.RangeLastItem = v.RangeLastItem +end + +--- @return ImGuiMultiSelectIO +function ImGuiMultiSelectIO() + return { Requests = ImVector(ImGuiSelectionRequest, ImGuiSelectionRequest_Copy), RangeSrcItem = -1, NavIdItem = -1, NavIdSelected = false, RangeSrcReset = false, ItemsCount = 0 } +end + +-- ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage: see [SECTION] Widgets: Multi-Select helpers in imgui_widgets.lua +-- (constructors and methods are defined there, like upstream's out-of-line definitions in imgui_widgets.cpp). + --- @class ImGuiSettingsHandler --- Fns take (ctx, handler, ...) like upstream. ReadOpenFn(ctx, handler, name) returns entry; ReadLineFn(ctx, handler, entry, line); --- WriteAllFn(ctx, handler, buf: ImGuiTextBuffer) @@ -48947,6 +49087,16 @@ end +--- @param x float +--- @return float # Sign operator - returns -1, 0 or 1 based on sign of argument +function ImSign(x) if x < 0.0 then return -1.0 elseif x > 0.0 then return 1.0 end return 0.0 end + +function ImLinearRemapClamp(s0, s1, d0, d1, x) return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0 end + +--- @param c int # byte +--- @return int +function ImToUpper(c) if c >= 97 and c <= 122 then return bit32.band(c, bit32.bnot(32)) end return c end + IM_F32_TO_INT8_UNBOUND = function(val) return math.floor(val * 255.0 + (val >= 0 and 0.5 or -0.5)) end -- Unsaturated, for display purpose IM_F32_TO_INT8_SAT = function(val) return math.floor(ImSaturate(val) * 255.0 + 0.5) end -- Saturated, always output 0..255 @@ -49219,6 +49369,12 @@ end --- @nodiscard function ImGui.WindowPosAbsToRel(window, p) local off = window.DC.CursorStartPos return ImVec2(p.x - off.x, p.y - off.y) end +--- @param window ImGuiWindow +--- @param p ImVec2 +--- @return ImVec2 +--- @nodiscard +function ImGui.WindowPosRelToAbs(window, p) local off = window.DC.CursorStartPos return ImVec2(p.x + off.x, p.y + off.y) end + --- @param c char --- @return bool # True if this character is a ' ' or '\t' function ImCharIsBlankA(c) return c == 32 or c == 9 end @@ -49441,6 +49597,9 @@ function IM_RECT:Add(p) end end +function IM_RECT:AddX(x) if self.Min.x > x then self.Min.x = x end if self.Max.x < x then self.Max.x = x end end +function IM_RECT:AddY(y) if self.Min.y > y then self.Min.y = y end if self.Max.y < y then self.Max.y = y end end + --- @param amount float|ImVec2 @native function IM_RECT:Expand(amount) @@ -50409,6 +50568,164 @@ local function ImGuiNavItemData() return this end +------------------------------------------------------------------------------- +-- [SECTION] Typing-select support +------------------------------------------------------------------------------- + +-- Flags for GetTypingSelectRequest() +--- @enum ImGuiTypingSelectFlags +ImGuiTypingSelectFlags = { + None = 0, + AllowBackspace = bitLShift(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) + AllowSingleCharMode = bitLShift(1, 1), -- Allow "single char" search mode which is activated when pressing the same character multiple times. +} + +-- Returned by GetTypingSelectRequest(), designed to eventually be public. +--- @class ImGuiTypingSelectRequest +--- @field Flags ImGuiTypingSelectFlags # Flags passed to GetTypingSelectRequest() +--- @field SearchBufferLen int +--- @field SearchBuffer string # Search buffer contents (use full string. unless SingleCharMode is set, in which case use SingleCharSize). +--- @field SelectRequest bool # Set when buffer was modified this frame, requesting a selection. +--- @field SingleCharMode bool # Notify when buffer contains same character repeated, to implement special mode. In this situation it preferred to not display any on-screen search indication. +--- @field SingleCharSize int # Length in bytes of first letter codepoint (1 for ascii, 2-4 for UTF-8). If (SearchBufferLen==RepeatCharSize) only 1 letter has been input. +local function ImGuiTypingSelectRequest() + return { Flags = 0, SearchBufferLen = 0, SearchBuffer = "", SelectRequest = false, SingleCharMode = false, SingleCharSize = 0 } +end + +-- Storage for GetTypingSelectRequest() +-- [Port] SearchBuffer is an immutable Lua string (C++: char[64], 63 bytes max + zero terminator). SearchBuffer[0] != 0 -> #SearchBuffer ~= 0. +--- @class ImGuiTypingSelectState +--- @field Request ImGuiTypingSelectRequest # User-facing data +--- @field SearchBuffer string # Search buffer: no need to make dynamic as this search is very transient. +--- @field FocusScope ImGuiID +--- @field LastRequestFrame int +--- @field LastRequestTime float +--- @field SingleCharModeLock bool # After a certain single char repeat count we lock into SingleCharMode. Two benefits: 1) buffer never fill, 2) we can provide an immediate SingleChar mode without timer elapsing. +MT.ImGuiTypingSelectState = {} +MT.ImGuiTypingSelectState.__index = MT.ImGuiTypingSelectState + +--- @return ImGuiTypingSelectState +function ImGuiTypingSelectState() + return setmetatable({ Request = ImGuiTypingSelectRequest(), SearchBuffer = "", FocusScope = 0, LastRequestFrame = 0, LastRequestTime = 0.0, SingleCharModeLock = false }, MT.ImGuiTypingSelectState) +end +function MT.ImGuiTypingSelectState:Clear() self.SearchBuffer = ""; self.SingleCharModeLock = false end -- We preserve remaining data for easier debugging + +------------------------------------------------------------------------------- +-- [SECTION] Box-select support +------------------------------------------------------------------------------- + +--- @class ImGuiBoxSelectState +--- @field ID ImGuiID +--- @field IsActive bool +--- @field IsStarting bool +--- @field IsStartedFromVoid bool # Starting click was not from an item. +--- @field IsStartedSetNavIdOnce bool +--- @field RequestClear bool +--- @field KeyMods ImGuiKeyChord # Latched key-mods for box-select logic. +--- @field StartPosRel ImVec2 # Start position in window-contents relative space (to support scrolling) +--- @field EndPosRel ImVec2 # End position in window-contents relative space +--- @field ScrollAccum ImVec2 # Scrolling accumulator (to behave at high-frame spaces) +--- @field Window ImGuiWindow? +--- @field UnclipMode bool # (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select. +--- @field UnclipRect ImRect # Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets. +--- @field UnclipRects ImRect[] # Per-axis versions. [Port] 1-based: [1] = upstream [0] (X), [2] = upstream [1] (Y) +--- @field BoxSelectRectPrev ImRect # Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos) +--- @field BoxSelectRectCurr ImRect +--- @return ImGuiBoxSelectState +function ImGuiBoxSelectState() + return { + -- Active box-selection data (persistent, 1 active at a time) + ID = 0, IsActive = false, IsStarting = false, IsStartedFromVoid = false, IsStartedSetNavIdOnce = false, RequestClear = false, + KeyMods = 0, + StartPosRel = ImVec2(), EndPosRel = ImVec2(), ScrollAccum = ImVec2(), + Window = nil, + + -- Temporary/Transient data + UnclipMode = false, + UnclipRect = ImRect(), + UnclipRects = { ImRect(), ImRect() }, + BoxSelectRectPrev = ImRect(), + BoxSelectRectCurr = ImRect(), + } +end + +------------------------------------------------------------------------------- +-- [SECTION] Multi-select support +------------------------------------------------------------------------------- + +-- We always assume that -1 is an invalid value (which works for indices and pointers) +-- (ImGuiSelectionUserData_Invalid is defined above with the ImGuiSelectionUserData alias) + +-- Temporary storage for multi-select +--- @class ImGuiMultiSelectTempData +--- @field IO ImGuiMultiSelectIO # MUST BE FIRST FIELD. Requests are set and returned by BeginMultiSelect()/EndMultiSelect() + written to by user during the loop. +--- @field Storage ImGuiMultiSelectState? +--- @field FocusScopeId ImGuiID # Copied from g.CurrentFocusScopeId (unless another selection scope was pushed manually) +--- @field Flags ImGuiMultiSelectFlags +--- @field ScopeRectMin ImVec2 +--- @field BackupCursorMaxPos ImVec2 +--- @field BoxSelectId ImGuiID +--- @field KeyMods ImGuiKeyChord +--- @field LoopRequestSetAll int # -1: no operation, 0: clear all, 1: select all. +--- @field IsEndIO bool # Set when switching IO from BeginMultiSelect() to EndMultiSelect() state. +--- @field IsFocused bool # Set if currently focusing the selection scope (any item of the selection). May be used if you have custom shortcut associated to selection. +--- @field IsKeyboardSetRange bool # Set by BeginMultiSelect() when using Shift+Navigation. Because scrolling may be affected we can't afford a frame of lag with Shift+Navigation. +--- @field NavIdPassedBy bool +--- @field RangeSrcPassedBy bool # Set by the item that matches RangeSrcItem. +--- @field RangeDstPassedBy bool # Set by the item that matches NavJustMovedToId when IsSetRange is set. +--- @field IsSoleOrUnknownSelectionSize bool +MT.ImGuiMultiSelectTempData = {} +MT.ImGuiMultiSelectTempData.__index = MT.ImGuiMultiSelectTempData + +--- @return ImGuiMultiSelectTempData +function ImGuiMultiSelectTempData() + local this = setmetatable({ IO = ImGuiMultiSelectIO(), ScopeRectMin = ImVec2(), BackupCursorMaxPos = ImVec2() }, MT.ImGuiMultiSelectTempData) + this:Clear() + return this +end +-- Zero-clear except IO as we preserve IO.Requests[] buffer allocation. +function MT.ImGuiMultiSelectTempData:Clear() + self:ClearIO() + self.Storage = nil + self.FocusScopeId = 0 + self.Flags = 0 + ImVec2_CopyV(self.ScopeRectMin, 0.0, 0.0) + ImVec2_CopyV(self.BackupCursorMaxPos, 0.0, 0.0) + self.BoxSelectId = 0 + self.KeyMods = 0 + self.LoopRequestSetAll = 0 + self.IsEndIO = false + self.IsFocused = false + self.IsKeyboardSetRange = false + self.NavIdPassedBy = false + self.RangeSrcPassedBy = false + self.RangeDstPassedBy = false + self.IsSoleOrUnknownSelectionSize = false +end +function MT.ImGuiMultiSelectTempData:ClearIO() local io = self.IO; io.Requests:resize(0); io.RangeSrcItem = (-1) --[[ImGuiSelectionUserData_Invalid]]; io.NavIdItem = (-1) --[[ImGuiSelectionUserData_Invalid]]; io.NavIdSelected = false; io.RangeSrcReset = false end + +-- Persistent storage for multi-select (as long as selection is alive) +--- @class ImGuiMultiSelectState +--- @field Window ImGuiWindow? +--- @field ID ImGuiID +--- @field LastFrameActive int # Last used frame-count, for GC. +--- @field LastSelectionSize int # Set by BeginMultiSelect() based on optional info provided by user. May be -1 if unknown. +--- @field RangeSelected int # -1 (don't have) or true/false +--- @field NavIdSelected int # -1 (don't have) or true/false +--- @field RangeSrcItem ImGuiSelectionUserData # +--- @field NavIdItem ImGuiSelectionUserData # SetNextItemSelectionUserData() value for NavId (if part of submitted items) +--- @return ImGuiMultiSelectState +function ImGuiMultiSelectState() + return { Window = nil, ID = 0, LastFrameActive = 0, LastSelectionSize = 0, RangeSelected = -1, NavIdSelected = -1, RangeSrcItem = (-1) --[[ImGuiSelectionUserData_Invalid]], NavIdItem = (-1) --[[ImGuiSelectionUserData_Invalid]] } +end + +--- @param id ImGuiID +--- @return ImGuiBoxSelectState? +function ImGui.GetBoxSelectState(id) local g = GImGui; if id ~= 0 and g.BoxSelectState.ID == id and g.BoxSelectState.IsActive then return g.BoxSelectState end return nil end +--- @param id ImGuiID +--- @return ImGuiMultiSelectState? +function ImGui.GetMultiSelectState(id) local g = GImGui; return g.MultiSelectStorage:GetByKey(id) end + ------------------------------------------------------------------------------- -- [SECTION] ImGuiContext (main Dear ImGui context) ------------------------------------------------------------------------------- @@ -50690,6 +51007,7 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct NavHighlightItemUnderNav = false, -- Disable mouse hovering highlight. Highlight navigation focused item instead of mouse hovered item. NavIdIsAlive = false, -- Nav widget has been seen this frame ~~ NavRectRel is valid NavInputSource = 2 --[[ImGuiInputSource.Keyboard]], -- Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource.Keyboard or ImGuiInputSource.Gamepad + NavLastValidSelectionUserData = (-1) --[[ImGuiSelectionUserData_Invalid]], -- Last valid data passed to SetNextItemSelectionUser(), or -1. For current window. Not reset when focusing an item that doesn't have selection data. NavWindowingTarget = nil, NavWindowingTargetAnim = nil, NavWindowingListWindow = nil, -- Target window when doing Ctrl+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! NavWindowingTimer = 0.0, NavWindowingHighlightAlpha = 0.0, NavWindowingToggleLayer = false, -- Set while Alt or GamepadMenu is held, may be cleared by other operations, and processed when releasing the key. @@ -50729,6 +51047,14 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct NavActivateId = 0, NavActivateDownId = 0, NavActivatePressedId = 0, -- ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey.Space) || IsKeyDown(ImGuiKey.Enter) || IsKeyPressed(ImGuiKey.NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItemByID() NavActivateFlags = 0 --[[ImGuiActivateFlags.None]], + -- Navigation: record of last move request + NavJustMovedFromFocusScopeId = 0, -- Just navigated from this focus scope id (result of a successfully MoveRequest). + NavJustMovedToId = 0, -- Just navigated to this id (result of a successfully MoveRequest). + NavJustMovedToFocusScopeId = 0, -- Just navigated to this focus scope id (result of a successfully MoveRequest). + NavJustMovedToKeyMods = 0, + NavJustMovedToIsTabbing = false, -- Copy of ImGuiNavMoveFlags.IsTabbing. Maybe we should store whole flags. + NavJustMovedToHasSelectionData = false, -- Copy of move result's ItemFlags & ImGuiItemFlags.HasSelectionUserData). Maybe we should just store ImGuiNavItemData. + FrameCount = 0, FrameCountEnded = -1, @@ -50789,6 +51115,8 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct MouseCursor = 0 --[[ImGuiMouseCursor.Arrow]], MouseStationaryTimer = 0.0, -- Time the mouse has been stationary (with some loose heuristic) + TypingSelectState = ImGuiTypingSelectState(), -- State for GetTypingSelectRequest() + InputTextState = ImGuiInputTextState(), InputTextLineIndex = ImGuiTextIndex(), -- Temporary storage InputTextDeactivatedState = ImGuiInputTextDeactivatedState(), @@ -50810,6 +51138,13 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct CurrentTabBarStack = ImVector(), -- holds ImGuiTabBar references directly (no ImGuiPtrOrIndex needed in Lua) ShrinkWidthBuffer = ImVector(), -- ImGuiShrinkWidthItem + -- Multi-Select state + BoxSelectState = ImGuiBoxSelectState(), + CurrentMultiSelect = nil, + MultiSelectTempDataStacked = 0, -- Temporary multi-select data size (because we leave previous instances undestructed, we generally don't use MultiSelectTempData.Size) + MultiSelectTempData = ImVector(), -- ImVector, 1-based + MultiSelectStorage = ImPool(ImGuiMultiSelectState), -- ImPool + WindowResizeBorderExpectedRect = ImRect(), -- Expected border rect, switch to relative edit if moving WindowResizeRelativeMode = false, @@ -53572,7 +53907,7 @@ function ImGui.TableUpdateLayout(table_) local inner_window = table_.InnerWindow local bs = g.BoxSelectState - if bs and bs.Window == inner_window and bs.UnclipMode then + if bs.Window == inner_window and bs.UnclipMode then ImGui.TableApplyExternalUnclipRect(table_, bs.UnclipRect) end @@ -58447,14 +58782,14 @@ function ImGui.Checkbox(label, v) -- Range-Selection/Multi-selection support (header) local checked = v if is_multi_select then - -- TODO: MultiSelectItemHeader() + checked = ImGui.MultiSelectItemHeader(id, checked, nil) end local pressed, hovered, held = ImGui.ButtonBehavior(total_bb, id) -- Range-Selection/Multi-selection support (footer) if is_multi_select then - -- TODO: MultiSelectItemFooter() + checked, pressed = ImGui.MultiSelectItemFooter(id, checked, pressed, 1048576 --[[ImGuiMultiSelectFlags.CheckboxMode_]]) elseif pressed then checked = not checked end @@ -65311,7 +65646,7 @@ function ImGui.Selectable(label, selected, flags, size_arg) local was_selected = selected if is_multi_select then -- Handle multi-select + alter button flags for it - -- TODO: selected, button_flags = ImGui.MultiSelectItemHeader(id, selected, button_flags) + selected, button_flags = ImGui.MultiSelectItemHeader(id, selected, button_flags) end local pressed, hovered, held = ImGui.ButtonBehavior(bb, id, button_flags) @@ -65319,7 +65654,7 @@ function ImGui.Selectable(label, selected, flags, size_arg) -- Multi-selection support (footer) if is_multi_select then - -- TODO: selected, pressed = ImGui.MultiSelectItemFooter(id, selected, pressed) + selected, pressed = ImGui.MultiSelectItemFooter(id, selected, pressed) else -- Auto-select when moved into -- - This will be more fully fleshed in the range-select branch @@ -65412,6 +65747,1331 @@ function ImGui.Selectable(label, selected, flags, size_arg) return pressed, selected end +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Typing-Select support +------------------------------------------------------------------------------- + +do -- [Port] Typing-Select, Box-Select, Multi-Select sections: locals kept private + +local MT = ImGui.GetMetatables() + +local TypingSelectCharBuf = { 0, 0, 0, 0, 0 } -- [Luau] scratch for ImTextCharToUtf8() (C++: char w_buf[5]) + +-- [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(...) then +-- local req = ImGui.GetTypingSelectRequest() +-- if req then focus_idx = ImGui.TypingSelectFindMatch(req, #my_items, function(user_data, n) return my_items[n + 1].Name end, my_items, -1) end +-- However the code is written in a way where calling it from multiple locations is safe (e.g. to obtain buffer). +-- [Port] Item indices passed to get_item_name_func(user_data, idx) and returned by TypingSelectFind*() are 0-based like upstream (-1 = none). +--- @param flags? ImGuiTypingSelectFlags +--- @return ImGuiTypingSelectRequest? +function ImGui.GetTypingSelectRequest(flags) + if flags == nil then flags = 0 --[[ImGuiTypingSelectFlags.None]] end + local g = GImGui + local data = g.TypingSelectState + local out_request = data.Request + + -- Clear buffer + local TYPING_SELECT_RESET_TIMER = 1.80 -- FIXME: Potentially move to IO config. + local TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK = 4 -- Lock single char matching when repeating same char 4 times + if #data.SearchBuffer ~= 0 then + local clear_buffer = false + clear_buffer = clear_buffer or (g.NavFocusScopeId ~= data.FocusScope) + clear_buffer = clear_buffer or (data.LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time) + clear_buffer = clear_buffer or g.NavAnyRequest + clear_buffer = clear_buffer or (g.ActiveId ~= 0 and g.NavActivateId == 0) -- Allow temporary SPACE activation to not interfere + clear_buffer = clear_buffer or ImGui.IsKeyPressed(526 --[[ImGuiKey.Escape]]) or ImGui.IsKeyPressed(525 --[[ImGuiKey.Enter]]) + clear_buffer = clear_buffer or (ImGui.IsKeyPressed(523 --[[ImGuiKey.Backspace]]) and bit32.band(flags, 1 --[[ImGuiTypingSelectFlags.AllowBackspace]]) == 0) + --if clear_buffer then IMGUI_DEBUG_LOG("GetTypingSelectRequest(): Clear SearchBuffer.\n") end + if clear_buffer then + data:Clear() + end + end + + -- Append to buffer + local buffer_max_len = 64 - 1 -- [Port] IM_COUNTOF(data->SearchBuffer) - 1 + local buffer_len = #data.SearchBuffer + local select_request = false + local input_queue = g.IO.InputQueueCharacters + for n = 1, input_queue.Size do + repeat + local w = input_queue.Data[n] + local w_buf = TypingSelectCharBuf + local w_len = ImStd.ImTextCharToUtf8(w_buf, w) -- [Port] also gives ImTextCountUtf8BytesFromStr(&w, &w + 1) + if w < 32 or (buffer_len == 0 and ImCharIsBlankW(w)) or (buffer_len + w_len > buffer_max_len) then -- Ignore leading blanks + break -- continue + end + local w_str = string.char(table.unpack(w_buf, 1, w_len)) + if data.SingleCharModeLock and w_len == out_request.SingleCharSize and string.sub(data.SearchBuffer, 1, w_len) == w_str then + select_request = true -- Same character: don't need to append to buffer. + break -- continue + end + if data.SingleCharModeLock then + data:Clear() -- Different character: clear + buffer_len = 0 + end + data.SearchBuffer = string.sub(data.SearchBuffer, 1, buffer_len) .. w_str -- Append + buffer_len = buffer_len + w_len + select_request = true + until true + end + input_queue:resize(0) + + -- Handle backspace + if bit32.band(flags, 1 --[[ImGuiTypingSelectFlags.AllowBackspace]]) ~= 0 and ImGui.IsKeyPressedEx(523 --[[ImGuiKey.Backspace]], 1 --[[ImGuiInputFlags.Repeat]]) then + local p = ImStd.ImTextFindPreviousUtf8Codepoint(data.SearchBuffer, 1, buffer_len + 1) -- 1-based byte position + data.SearchBuffer = string.sub(data.SearchBuffer, 1, p - 1) + buffer_len = p - 1 + end + + -- Return request if any + if buffer_len == 0 then + return nil + end + if select_request then + data.FocusScope = g.NavFocusScopeId + data.LastRequestFrame = g.FrameCount + data.LastRequestTime = g.Time + end + out_request.Flags = flags + out_request.SearchBufferLen = buffer_len + out_request.SearchBuffer = data.SearchBuffer + out_request.SelectRequest = (data.LastRequestFrame == g.FrameCount) + out_request.SingleCharMode = false + out_request.SingleCharSize = 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. + -- - SingleCharMode is always set for first input character, because it usually leads to a "next". + if bit32.band(flags, 2 --[[ImGuiTypingSelectFlags.AllowSingleCharMode]]) ~= 0 then + local buf = out_request.SearchBuffer + local buf_begin = 1 + local buf_end = buf_begin + out_request.SearchBufferLen + local c0_len = ImStd.ImTextCountUtf8BytesFromChar(buf, buf_begin, buf_end) + local c0 = string.sub(buf, buf_begin, buf_begin + c0_len - 1) + local p = buf_begin + c0_len + while p < buf_end do + if string.sub(buf, p, p + c0_len - 1) ~= c0 then -- memcmp(buf_begin, p, c0_len) != 0 + break + end + p = p + c0_len + end + local single_char_count = (p == buf_end) and math.floor(out_request.SearchBufferLen / c0_len) or 0 + out_request.SingleCharMode = (single_char_count > 0 or data.SingleCharModeLock) + out_request.SingleCharSize = c0_len + data.SingleCharModeLock = data.SingleCharModeLock or (single_char_count >= TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK) -- From now on we stop search matching to lock to single char mode. + end + + return out_request +end + +-- [Port] s1 is the byte range [s1_begin, s1_end) of string s1, s2 is a whole string (out of range bytes read as 0 like a C terminator). +local function ImStrimatchlen(s1, s1_begin, s1_end, s2) + local match_len = 0 + local s2_p = 1 + while s1_begin < s1_end and ImToUpper(string.byte(s1, s1_begin)) == ImToUpper(string.byte(s2, s2_p) or 0) do + s1_begin = s1_begin + 1 + s2_p = s2_p + 1 + match_len = match_len + 1 + end + return match_len +end + +-- Default handler for finding a result for typing-select. You may implement your own. +-- You might want to display a tooltip to visualize the current request SearchBuffer +-- When SingleCharMode is set: +-- - it is better to NOT display a tooltip of other on-screen display indicator. +-- - the index of the currently focused item is required. +-- if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO.NavIdItem, otherwise from g.NavLastValidSelectionUserData. +--- @param req ImGuiTypingSelectRequest? +--- @param items_count int +--- @param get_item_name_func fun(user_data: any, idx: int): string # idx is 0-based +--- @param user_data any +--- @param nav_item_idx int # 0-based, -1 if unknown +--- @return int # 0-based, -1 if none +function ImGui.TypingSelectFindMatch(req, items_count, get_item_name_func, user_data, nav_item_idx) + if req == nil or req.SelectRequest == false then -- Support NULL parameter so both calls can be done from same spot. + return -1 + end + local idx = -1 + if req.SingleCharMode and bit32.band(req.Flags, 2 --[[ImGuiTypingSelectFlags.AllowSingleCharMode]]) ~= 0 then + idx = ImGui.TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx) + else + idx = ImGui.TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data) + end + if idx ~= -1 then + ImGui.SetNavCursorVisibleAfterMove() + end + return idx +end + +-- Special handling when a single character is repeated: perform search on a single letter and goes to next. +function ImGui.TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx) + -- FIXME: Assume selection user data is index. Would be extremely practical. + --if nav_item_idx == -1 then + -- nav_item_idx = g.NavLastValidSelectionUserData + --end + + local first_match_idx = -1 + local return_next_match = false + for idx = 0, items_count - 1 do + repeat + local item_name = get_item_name_func(user_data, idx) + if ImStrimatchlen(req.SearchBuffer, 1, 1 + req.SingleCharSize, item_name) < req.SingleCharSize then + break -- continue + end + if return_next_match then -- Return next matching item after current item. + return idx + end + if first_match_idx == -1 and nav_item_idx == -1 then -- Return first match immediately if we don't have a nav_item_idx value. + return idx + end + if first_match_idx == -1 then -- Record first match for wrapping. + first_match_idx = idx + end + if nav_item_idx == idx then -- Record that we encountering nav_item so we can return next match. + return_next_match = true + end + until true + end + return first_match_idx -- First result +end + +function ImGui.TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data) + local longest_match_idx = -1 + local longest_match_len = 0 + for idx = 0, items_count - 1 do + local item_name = get_item_name_func(user_data, idx) + local match_len = ImStrimatchlen(req.SearchBuffer, 1, 1 + req.SearchBufferLen, item_name) + if match_len > longest_match_len then -- [Port] inverted 'if (match_len <= longest_match_len) continue;' + longest_match_idx = idx + longest_match_len = match_len + if match_len == req.SearchBufferLen then + break + end + end + end + return longest_match_idx +end + +--- @param data ImGuiTypingSelectState +function ImGui.DebugNodeTypingSelectState(data) + ImGui.Text("SearchBuffer = \"%s\"", data.SearchBuffer) + ImGui.Text("SingleCharMode = %d, Size = %d, Lock = %d", data.Request.SingleCharMode and 1 or 0, data.Request.SingleCharSize, data.SingleCharModeLock and 1 or 0) + ImGui.Text("LastRequest = time: %.2f, frame: %d", data.LastRequestTime, data.LastRequestFrame) +end + +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Box-Select support +-- This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet. +------------------------------------------------------------------------------- +-- Extra logic in MultiSelectItemFooter() and ImGuiListClipper:Step() +------------------------------------------------------------------------------- +-- - BoxSelectPreStartDrag() [Internal] +-- - BoxSelectActivateDrag() [Internal] +-- - BoxSelectDeactivateDrag() [Internal] +-- - BoxSelectScrollWithMouseDrag() [Internal] +-- - BeginBoxSelect() [Internal] +-- - EndBoxSelect() [Internal] +------------------------------------------------------------------------------- + +-- Call on the initial click. +local function BoxSelectPreStartDrag(id, clicked_item) + local g = GImGui + local bs = g.BoxSelectState + bs.ID = id + bs.IsStarting = true -- Consider starting box-select. + bs.IsStartedFromVoid = (clicked_item == (-1) --[[ImGuiSelectionUserData_Invalid]]) + bs.IsStartedSetNavIdOnce = bs.IsStartedFromVoid + bs.KeyMods = g.IO.KeyMods + local off = g.CurrentWindow.DC.CursorStartPos -- [Luau] inline WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos) + ImVec2_CopyV(bs.StartPosRel, g.IO.MousePos.x - off.x, g.IO.MousePos.y - off.y) + ImVec2_Copy(bs.EndPosRel, bs.StartPosRel) + ImVec2_CopyV(bs.ScrollAccum, 0.0, 0.0) +end + +local function BoxSelectActivateDrag(bs, window) + local g = GImGui + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Activate\n", bs.ID) + bs.IsActive = true + bs.Window = window + bs.IsStarting = false + ImGui.SetActiveID(bs.ID, window) + ImGui.SetActiveIdUsingAllKeyboardKeys() + if bs.IsStartedFromVoid and bit32.band(bs.KeyMods, bit32.bor(4096 --[[ImGuiMod_Ctrl]], 8192 --[[ImGuiMod_Shift]])) == 0 then + bs.RequestClear = true + end +end + +local function BoxSelectDeactivateDrag(bs) + local g = GImGui + bs.IsActive = false; bs.IsStarting = false + if g.ActiveId == bs.ID then + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Deactivate\n", bs.ID) + ImGui.ClearActiveID() + end + bs.ID = 0 +end + +local function BoxSelectScrollWithMouseDrag(bs, window, inner_r) + local g = GImGui + assert(bs.Window == window) + for n = 1, 2 do -- each axis -- [Port] 1 = X, 2 = Y + repeat + local k = (n == 1) and "x" or "y" + local mouse_pos = g.IO.MousePos[k] + local dist = (mouse_pos > inner_r.Max[k]) and mouse_pos - inner_r.Max[k] or (mouse_pos < inner_r.Min[k]) and mouse_pos - inner_r.Min[k] or 0.0 + local scroll_curr = window.Scroll[k] + if dist == 0.0 or (dist < 0.0 and scroll_curr < 0.0) or (dist > 0.0 and scroll_curr >= window.ScrollMax[k]) then + break -- continue + end + + local speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0, 1.0, 4.0, ImAbs(dist)) -- x1 to x4 depending on distance + local scroll_step = g.FontSize * 35.0 * speed_multiplier * ImSign(dist) * g.IO.DeltaTime + bs.ScrollAccum[k] = bs.ScrollAccum[k] + scroll_step + + -- Accumulate into a stored value so we can handle high-framerate + local scroll_step_i = ImFloor(bs.ScrollAccum[k]) + if scroll_step_i == 0.0 then + break -- continue + end + if n == 1 then + ImGui.SetScrollX(window, scroll_curr + scroll_step_i) + else + ImGui.SetScrollY(window, scroll_curr + scroll_step_i) + end + bs.ScrollAccum[k] = bs.ScrollAccum[k] - scroll_step_i + until true + end +end + +--- @param scope_rect ImRect +--- @param window ImGuiWindow +--- @param box_select_id ImGuiID +--- @param ms_flags ImGuiMultiSelectFlags +--- @return bool +function ImGui.BeginBoxSelect(scope_rect, window, box_select_id, ms_flags) + local g = GImGui + local bs = g.BoxSelectState + ImGui.KeepAliveID(box_select_id) + if bs.ID ~= box_select_id then + return false + end + + -- IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry. + bs.UnclipMode = false + bs.RequestClear = false + if bs.IsStarting and ImGui.IsMouseDragPastThreshold(0) then + BoxSelectActivateDrag(bs, window) + elseif (bs.IsStarting or bs.IsActive) and g.IO.MouseDown[0] == false then + BoxSelectDeactivateDrag(bs) + end + if not bs.IsActive then + return false + end + + -- Current frame absolute prev/current rectangles are used to toggle selection. + -- They are derived from positions relative to scrolling space, so "previous" rectangle is reprojected for current frame coordinates. + -- [Luau] scalar versions of WindowPosRelToAbs()/ImClamp()/ImMin()/ImMax() (no temporary ImVec2) + local off = window.DC.CursorStartPos + local start_pos_abs_x, start_pos_abs_y = bs.StartPosRel.x + off.x, bs.StartPosRel.y + off.y + local prev_end_pos_abs_x, prev_end_pos_abs_y = bs.EndPosRel.x + off.x, bs.EndPosRel.y + off.y -- Clamped already + local curr_end_pos_abs_x, curr_end_pos_abs_y = g.IO.MousePos.x, g.IO.MousePos.y + if bit32.band(ms_flags, 2048 --[[ImGuiMultiSelectFlags.ScopeWindow]]) ~= 0 then -- Box-select scrolling only happens with ScopeWindow + curr_end_pos_abs_x = ImClamp(curr_end_pos_abs_x, scope_rect.Min.x, scope_rect.Max.x) + curr_end_pos_abs_y = ImClamp(curr_end_pos_abs_y, scope_rect.Min.y, scope_rect.Max.y) + end + ImVec2_CopyV(bs.BoxSelectRectPrev.Min, ImMin(start_pos_abs_x, prev_end_pos_abs_x), ImMin(start_pos_abs_y, prev_end_pos_abs_y)) + ImVec2_CopyV(bs.BoxSelectRectPrev.Max, ImMax(start_pos_abs_x, prev_end_pos_abs_x), ImMax(start_pos_abs_y, prev_end_pos_abs_y)) + ImVec2_CopyV(bs.BoxSelectRectCurr.Min, ImMin(start_pos_abs_x, curr_end_pos_abs_x), ImMin(start_pos_abs_y, curr_end_pos_abs_y)) + ImVec2_CopyV(bs.BoxSelectRectCurr.Max, ImMax(start_pos_abs_x, curr_end_pos_abs_x), ImMax(start_pos_abs_y, curr_end_pos_abs_y)) + --IMGUI_DEBUG_LOG("StartPosRel (%.2f,%.2f) EndPosRel (%.2f,%.2f) -> (%.2f,%.2f)\n", bs.StartPosRel.x, bs.StartPosRel.y, bs.EndPosRel.x, bs.EndPosRel.y, WindowPosAbsToRel(window, g.IO.MousePos).x, WindowPosAbsToRel(window, g.IO.MousePos).y) + + -- Box-select 2D mode detects change of the rectangle. + -- Storing unclip rects which will be tested by widgets supporting box-select. Always update rectangles when active (even if we don't use them). + -- To facilitate understanding this: enable IMGUI_DEBUG_BOXSELECT and visualize all geometry. + if bit32.band(ms_flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 then + -- For both sides, compute the area differing between Prev and Curr rectangles. + local unclip_x, unclip_y = bs.UnclipRects[1], bs.UnclipRects[2] -- [Port] UnclipRects[0], UnclipRects[1] + ImVec2_CopyV(unclip_x.Min, 3.4028234663853e+38 --[[FLT_MAX]], 3.4028234663853e+38 --[[FLT_MAX]]); ImVec2_CopyV(unclip_x.Max, -3.4028234663853e+38 --[[FLT_MAX]], -3.4028234663853e+38 --[[FLT_MAX]]) + ImVec2_CopyV(unclip_y.Min, 3.4028234663853e+38 --[[FLT_MAX]], 3.4028234663853e+38 --[[FLT_MAX]]); ImVec2_CopyV(unclip_y.Max, -3.4028234663853e+38 --[[FLT_MAX]], -3.4028234663853e+38 --[[FLT_MAX]]) + local curr, prev = bs.BoxSelectRectCurr, bs.BoxSelectRectPrev + for side = 0, 1 do + local a = (side == 0) and curr.Min or curr.Max + local b = (side == 0) and prev.Min or prev.Max + local d_min_x, d_min_y = ImMin(a.x, b.x), ImMin(a.y, b.y) + local d_max_x, d_max_y = ImMax(a.x, b.x), ImMax(a.y, b.y) + if d_min_x ~= d_max_x then + unclip_x:AddX(d_min_x) + unclip_x:AddX(d_max_x) + end + if d_min_y ~= d_max_y then + unclip_y:AddY(d_min_y) + unclip_y:AddY(d_max_y) + end + end + + -- box_select_intersection = BoxSelectRectPrev + BoxSelectRectCurr + local box_select_intersection_min_x, box_select_intersection_min_y = ImMin(prev.Min.x, curr.Min.x), ImMin(prev.Min.y, curr.Min.y) + local box_select_intersection_max_x, box_select_intersection_max_y = ImMax(prev.Max.x, curr.Max.x), ImMax(prev.Max.y, curr.Max.y) + if bit32.band(ms_flags, 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]]) ~= 0 then + if prev.Min.x ~= curr.Min.x or prev.Max.x ~= curr.Max.x then + unclip_x:AddY(box_select_intersection_min_y) + unclip_x:AddY(box_select_intersection_max_y) + end + end + if bit32.band(ms_flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 then + if prev.Min.y ~= curr.Min.y or prev.Max.y ~= curr.Max.y then + unclip_y:AddX(box_select_intersection_min_x) + unclip_y:AddX(box_select_intersection_max_x) + end + end + + -- Merge both rectangles into one. + -- FIXME-OPT: When UnclipRect.Area() is much larger than the sum of UnclipRects[0]/[1] Areas, widgets should + -- ideally first use UnclipRect as a first coarse cull layer + the individual ones as a second validation. + ImRect_Copy(bs.UnclipRect, unclip_x) + bs.UnclipRect:Add(unclip_y) + if not bs.UnclipRect:IsInverted() and (not window.ClipRect:Contains(bs.UnclipRect.Min) or not window.ClipRect:Contains(bs.UnclipRect.Max)) then -- !! Don't use Contains(ImRect) + bs.UnclipMode = true + end + if bs.UnclipMode and g.CurrentTable ~= nil then + ImGui.TableApplyExternalUnclipRect(g.CurrentTable, bs.UnclipRect) -- No need submitting both + end + end + + -- [Port] IMGUI_DEBUG_BOXSELECT visualization not ported (compile-time debug option, disabled upstream). + return true +end + +--- @param scope_rect ImRect +--- @param ms_flags ImGuiMultiSelectFlags +function ImGui.EndBoxSelect(scope_rect, ms_flags) + local g = GImGui + local window = g.CurrentWindow + local bs = g.BoxSelectState + assert(bs.IsActive) + bs.UnclipMode = false + + -- Render selection rectangle + bs.EndPosRel = ImGui.WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)) -- Clamp stored position according to current scrolling view + local box_select_r = ImRect(bs.BoxSelectRectCurr.Min, bs.BoxSelectRectCurr.Max) + box_select_r:ClipWith(scope_rect) + local draw_window = ImGui.FindFrontMostVisibleChildWindow(window) + draw_window.DrawList:AddRectFilled(box_select_r.Min, box_select_r.Max, ImGui.GetColorU32(29 --[[ImGuiCol.SeparatorHovered]], 0.30)) -- FIXME-MULTISELECT: Styling + draw_window.DrawList:AddRect(box_select_r.Min, box_select_r.Max, ImGui.GetColorU32(59 --[[ImGuiCol.NavCursor]])) -- FIXME-MULTISELECT FIXME-DPI: Styling + + -- Scroll + local enable_scroll = bit32.band(ms_flags, 2048 --[[ImGuiMultiSelectFlags.ScopeWindow]]) ~= 0 and bit32.band(ms_flags, 256 --[[ImGuiMultiSelectFlags.BoxSelectNoScroll]]) == 0 + if enable_scroll then + local scroll_r = ImRect(scope_rect.Min, scope_rect.Max) + scroll_r:Expand(-g.FontSize) + --GetForegroundDrawList():AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255)) + if not scroll_r:Contains(g.IO.MousePos) then + BoxSelectScrollWithMouseDrag(bs, window, scroll_r) + end + end +end + +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Multi-Select support +------------------------------------------------------------------------------- +-- - DebugLogMultiSelectRequests() [Internal] +-- - CalcScopeRect() [Internal] +-- - BeginMultiSelect() +-- - EndMultiSelect() +-- - SetNextItemSelectionUserData() +-- - MultiSelectItemHeader() [Internal] +-- - MultiSelectItemFooter() [Internal] +-- - DebugNodeMultiSelectState() [Internal] +------------------------------------------------------------------------------- + +-- [Luau] reused scratch request: ImVector:push_back() copies it into the preallocated element (ImGuiSelectionRequest_Copy) +local MultiSelectScratchRequest = { Type = 0, Selected = false, RangeDirection = 0, RangeFirstItem = -1, RangeLastItem = -1 } +local MultiSelectScratchItemRect = ImRect() -- [Luau] scratch for MultiSelectItemFooter()'s item_rect + +local function DebugLogMultiSelectRequests(func, io) + local g = GImGui + for _, req in io.Requests:iter() do + if req.Type == 1 --[[ImGuiSelectionRequestType.SetAll]] then IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", func, req.Selected and 1 or 0, req.Selected and "SelectAll" or "Clear") end + if req.Type == 2 --[[ImGuiSelectionRequestType.SetRange]] then IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %s..%s = %d (dir %d)\n", func, tostring(req.RangeFirstItem), tostring(req.RangeLastItem), req.Selected and 1 or 0, req.RangeDirection) end + end +end + +local function CalcScopeRect(ms, window) + if bit32.band(ms.Flags, 4096 --[[ImGuiMultiSelectFlags.ScopeRect]]) ~= 0 then + -- Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only + -- This probably doesn't work inside a table as there are ample ambiguities related to exact time of calling BeginMultiSelect()/EndMultiSelect(). + return ImRect(ms.ScopeRectMin.x, ms.ScopeRectMin.y, ImMax(window.DC.CursorMaxPos.x, ms.ScopeRectMin.x), ImMax(window.DC.CursorMaxPos.y, ms.ScopeRectMin.y)) + else + --// When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970) + local scope_rect = ImRect(window.InnerClipRect.Min, window.InnerClipRect.Max) + --if g.CurrentTable ~= nil then + -- scope_rect = g.CurrentTable.HostClipRect + --end + + -- Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect? + scope_rect.Min.x = ImMin(scope_rect.Min.x + window.DecoInnerSizeX1, scope_rect.Max.x) + scope_rect.Min.y = ImMin(scope_rect.Min.y + window.DecoInnerSizeY1, scope_rect.Max.y) + return scope_rect + end +end + +-- Return ImGuiMultiSelectIO structure. +-- Lifetime: don't hold on ImGuiMultiSelectIO references over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +-- Passing 'selection_size' and 'items_count' parameters is currently optional. +-- - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags.ClearOnEscape won't claim Escape key when selection_size 0, +-- allowing a first press to clear selection THEN the second press to leave child window and return to parent. +-- - 'items_count' is stored in ImGuiMultiSelectIO which makes it a convenient way to pass the information to your ApplyRequest() handler (but you may pass it differently). +-- - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1. +-- - If you can easily tell if your selection is empty or not, you may pass 0/1, or you may enable ImGuiMultiSelectFlags.ClearOnEscape flag dynamically. +--- @param flags ImGuiMultiSelectFlags +--- @param selection_size? int +--- @param items_count? int +--- @return ImGuiMultiSelectIO +function ImGui.BeginMultiSelect(flags, selection_size, items_count) + if selection_size == nil then selection_size = -1 end + if items_count == nil then items_count = -1 end + local g = GImGui + local window = g.CurrentWindow + + g.MultiSelectTempDataStacked = g.MultiSelectTempDataStacked + 1 + while g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size do -- [Port] resize(n, ImGuiMultiSelectTempData()) would share one instance + g.MultiSelectTempData:push_back(ImGuiMultiSelectTempData()) + end + local ms = g.MultiSelectTempData.Data[g.MultiSelectTempDataStacked] + g.CurrentMultiSelect = ms + if bit32.band(flags, bit32.bor(2048 --[[ImGuiMultiSelectFlags.ScopeWindow]], 4096 --[[ImGuiMultiSelectFlags.ScopeRect]])) == 0 then + flags = bit32.bor(flags, 2048 --[[ImGuiMultiSelectFlags.ScopeWindow]]) + end + if bit32.band(flags, 1 --[[ImGuiMultiSelectFlags.SingleSelect]]) ~= 0 then + flags = bit32.band(flags, bit32.bnot(bit32.bor(128 --[[ImGuiMultiSelectFlags.BoxSelect2d]], 64 --[[ImGuiMultiSelectFlags.BoxSelect1d]]))) + end + if bit32.band(flags, 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]]) ~= 0 then + flags = bit32.band(flags, bit32.bnot(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]])) + end + + -- FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250) + -- They should perhaps be stacked properly? + local table_ = g.CurrentTable + if table_ then + if not table_.IsLayoutLocked then + ImGui.TableUpdateLayout(table_) + elseif table_.CurrentColumn ~= -1 then + ImGui.TableEndCell(table_) -- This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it. + end + end + + -- FIXME: BeginFocusScope() + local id = window.IDStack.Data[window.IDStack.Size] -- window.IDStack:back() + ms:Clear() + ms.FocusScopeId = id + ms.Flags = flags + ImVec2_Copy(ms.BackupCursorMaxPos, window.DC.CursorMaxPos) + ImVec2_Copy(ms.ScopeRectMin, window.DC.CursorPos) + if bit32.band(flags, 4096 --[[ImGuiMultiSelectFlags.ScopeRect]]) ~= 0 then + ImVec2_Copy(window.DC.CursorMaxPos, ms.ScopeRectMin) -- CalcScopeRect() for ImGuiMultiSelectFlags.ScopeRect will measure in EndMultiSelect(). + end + ImGui.PushFocusScope(ms.FocusScopeId) + ms.IsFocused = ImGui.IsInNavFocusRoute(g.CurrentFocusScopeId) + if bit32.band(flags, 2048 --[[ImGuiMultiSelectFlags.ScopeWindow]]) ~= 0 then -- Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items. + window.DC.NavLayersActiveMask = bit32.bor(window.DC.NavLayersActiveMask, bit32.lshift(1, 0 --[[ImGuiNavLayer.Main]])) + end + + -- Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame. + if g.NavJustMovedToId ~= 0 then + ms.KeyMods = g.NavJustMovedToIsTabbing and 0 or g.NavJustMovedToKeyMods + else + ms.KeyMods = g.IO.KeyMods + end + if bit32.band(flags, 4 --[[ImGuiMultiSelectFlags.NoRangeSelect]]) ~= 0 then + ms.KeyMods = bit32.band(ms.KeyMods, bit32.bnot(8192 --[[ImGuiMod_Shift]])) + end + + -- Bind storage + local storage = g.MultiSelectStorage:GetOrAddByKey(id) + storage.ID = id + storage.LastFrameActive = g.FrameCount + storage.LastSelectionSize = selection_size + storage.Window = window + ms.Storage = storage + + -- Output to user + ms.IO.Requests:resize(0) + ms.IO.RangeSrcItem = storage.RangeSrcItem + ms.IO.NavIdItem = storage.NavIdItem + ms.IO.NavIdSelected = (storage.NavIdSelected == 1) + ms.IO.ItemsCount = items_count + + -- Clear when using Navigation to move within the scope + -- (we compare FocusScopeId so it possible to use multiple selections inside a same window) + local request_clear = false + local request_select_all = false + if g.NavJustMovedToId ~= 0 and g.NavJustMovedToFocusScopeId == ms.FocusScopeId and g.NavJustMovedToHasSelectionData then + if bit32.band(ms.KeyMods, 8192 --[[ImGuiMod_Shift]]) ~= 0 then + ms.IsKeyboardSetRange = true + end + if ms.IsKeyboardSetRange then + assert(storage.RangeSrcItem ~= (-1) --[[ImGuiSelectionUserData_Invalid]]) -- Not ready -> could clear? + end + if bit32.band(ms.KeyMods, bit32.bor(4096 --[[ImGuiMod_Ctrl]], 8192 --[[ImGuiMod_Shift]])) == 0 and bit32.band(flags, bit32.bor(16 --[[ImGuiMultiSelectFlags.NoAutoClear]], 8 --[[ImGuiMultiSelectFlags.NoAutoSelect]])) == 0 then + request_clear = true + end + elseif g.NavJustMovedFromFocusScopeId == ms.FocusScopeId then + -- Also clear on leaving scope (may be optional?) + if bit32.band(ms.KeyMods, bit32.bor(4096 --[[ImGuiMod_Ctrl]], 8192 --[[ImGuiMod_Shift]])) == 0 and bit32.band(flags, bit32.bor(16 --[[ImGuiMultiSelectFlags.NoAutoClear]], 8 --[[ImGuiMultiSelectFlags.NoAutoSelect]])) == 0 then + request_clear = true + end + end + + -- Box-select handling: update active state. + local bs = g.BoxSelectState + if bit32.band(flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 then + ms.BoxSelectId = ImGui.GetID("##BoxSelect") + if ImGui.BeginBoxSelect(CalcScopeRect(ms, window), window, ms.BoxSelectId, flags) then + request_clear = request_clear or bs.RequestClear + end + end + + if ms.IsFocused then + -- Shortcut: Clear selection (Escape) + -- - Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window. + -- - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock. + if bit32.band(flags, 512 --[[ImGuiMultiSelectFlags.ClearOnEscape]]) ~= 0 then + if selection_size ~= 0 or bs.IsActive then + if ImGui.Shortcut(526 --[[ImGuiKey.Escape]], 0 --[[ImGuiInputFlags.None]], bs.IsActive and bs.ID or 0) then + request_clear = true + if bs.IsActive then + BoxSelectDeactivateDrag(bs) + end + end + end + end + + -- Shortcut: Select all (Ctrl+A) + if bit32.band(flags, 1 --[[ImGuiMultiSelectFlags.SingleSelect]]) == 0 and bit32.band(flags, 2 --[[ImGuiMultiSelectFlags.NoSelectAll]]) == 0 then + if ImGui.Shortcut(bit32.bor(4096 --[[ImGuiMod_Ctrl]], 546 --[[ImGuiKey.A]])) then + request_select_all = true + end + end + end + + if request_clear or request_select_all then + ImGui.MultiSelectAddSetAll(ms, request_select_all) + if not request_select_all then + storage.LastSelectionSize = 0 + end + end + ms.LoopRequestSetAll = request_select_all and 1 or (request_clear and 0 or -1) + ms.IsSoleOrUnknownSelectionSize = (storage.LastSelectionSize == 1) or (storage.LastSelectionSize == -1) + --ms.PrevSubmittedItem = ImGuiSelectionUserData_Invalid + + if bit32.band(g.DebugLogFlags, 64 --[[ImGuiDebugLogFlags.EventSelection]]) ~= 0 then + DebugLogMultiSelectRequests("BeginMultiSelect", ms.IO) + end + + return ms.IO +end + +-- Return updated ImGuiMultiSelectIO structure. +-- Lifetime: don't hold on ImGuiMultiSelectIO references over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +--- @return ImGuiMultiSelectIO +function ImGui.EndMultiSelect() + local g = GImGui + local ms = g.CurrentMultiSelect + local storage = ms.Storage + local window = g.CurrentWindow + IM_ASSERT_USER_ERROR(ms.FocusScopeId == g.CurrentFocusScopeId, "EndMultiSelect() FocusScope mismatch!") + assert(g.CurrentMultiSelect ~= nil and storage.Window == g.CurrentWindow) + assert(g.MultiSelectTempDataStacked > 0 and g.MultiSelectTempData.Data[g.MultiSelectTempDataStacked] == g.CurrentMultiSelect) + + local scope_rect = CalcScopeRect(ms, window) + if ms.IsFocused then + -- We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here. + if ms.IO.RangeSrcReset or (ms.RangeSrcPassedBy == false and ms.IO.RangeSrcItem ~= (-1) --[[ImGuiSelectionUserData_Invalid]]) then -- Can't read storage.RangeSrcItem here -> we want the state at beginning of the scope (see tests for easy failure) + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n") -- Will set be to NavId. + storage.RangeSrcItem = (-1) --[[ImGuiSelectionUserData_Invalid]] + end + if ms.NavIdPassedBy == false and storage.NavIdItem ~= (-1) --[[ImGuiSelectionUserData_Invalid]] then + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset NavIdItem.\n") + storage.NavIdItem = (-1) --[[ImGuiSelectionUserData_Invalid]] + storage.NavIdSelected = -1 + end + + if bit32.band(ms.Flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 and ImGui.GetBoxSelectState(ms.BoxSelectId) then + ImGui.EndBoxSelect(scope_rect, ms.Flags) + end + end + + if ms.IsEndIO == false then + ms.IO.Requests:resize(0) + end + + -- Clear selection when clicking void? + -- We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection! + -- The InnerRect test is necessary for non-child/decorated windows. + local scope_hovered = window.InnerRect:Contains(g.IO.MousePos) and ImGui.IsWindowHovered(1 --[[ImGuiHoveredFlags.ChildWindows]]) + if scope_hovered and bit32.band(ms.Flags, 4096 --[[ImGuiMultiSelectFlags.ScopeRect]]) ~= 0 then + scope_hovered = scope_hovered and scope_rect:Contains(g.IO.MousePos) + end + if scope_hovered and g.HoveredId == 0 and g.ActiveId == 0 then + if bit32.band(ms.Flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 then + if not g.BoxSelectState.IsActive and not g.BoxSelectState.IsStarting and g.IO.MouseClickedCount[0] == 1 then + BoxSelectPreStartDrag(ms.BoxSelectId, (-1) --[[ImGuiSelectionUserData_Invalid]]) + ImGui.FocusWindow(window, 2 --[[ImGuiFocusRequestFlags.UnlessBelowModal]]) + ImGui.SetHoveredID(ms.BoxSelectId) + if bit32.band(ms.Flags, 4096 --[[ImGuiMultiSelectFlags.ScopeRect]]) ~= 0 then + ImGui.SetNavID(0, 0 --[[ImGuiNavLayer.Main]], ms.FocusScopeId, ImRect(g.IO.MousePos.x, g.IO.MousePos.y, g.IO.MousePos.x, g.IO.MousePos.y)) -- Automatically switch FocusScope for initial click from void to box-select. + end + end + end + + if bit32.band(ms.Flags, 1024 --[[ImGuiMultiSelectFlags.ClearOnClickVoid]]) ~= 0 then + if ImGui.IsMouseReleased(0) and ImGui.IsMouseDragPastThreshold(0) == false and g.IO.KeyMods == 0 --[[ImGuiMod_None]] then + ImGui.MultiSelectAddSetAll(ms, false) + end + end + end + + -- Courtesy nav wrapping helper flag + if bit32.band(ms.Flags, 65536 --[[ImGuiMultiSelectFlags.NavWrapX]]) ~= 0 then + assert(bit32.band(ms.Flags, 2048 --[[ImGuiMultiSelectFlags.ScopeWindow]]) ~= 0) -- Only supported at window scope + ImGui.NavMoveRequestTryWrapping(ImGui.GetCurrentWindow(), 4 --[[ImGuiNavMoveFlags.WrapX]]) + end + + -- Unwind + local table_ = g.CurrentTable + if table_ then + if table_.IsInsideRow then + ImGui.TableEndRow(table_) + end + end + ImVec2_CopyV(window.DC.CursorMaxPos, ImMax(ms.BackupCursorMaxPos.x, window.DC.CursorMaxPos.x), ImMax(ms.BackupCursorMaxPos.y, window.DC.CursorMaxPos.y)) + ImGui.PopFocusScope() + + if bit32.band(g.DebugLogFlags, 64 --[[ImGuiDebugLogFlags.EventSelection]]) ~= 0 then + DebugLogMultiSelectRequests("EndMultiSelect", ms.IO) + end + + ms.FocusScopeId = 0 + ms.Flags = 0 --[[ImGuiMultiSelectFlags.None]] + g.MultiSelectTempDataStacked = g.MultiSelectTempDataStacked - 1 + if g.MultiSelectTempDataStacked > 0 then + g.CurrentMultiSelect = g.MultiSelectTempData.Data[g.MultiSelectTempDataStacked] + else + g.CurrentMultiSelect = nil + end + + return ms.IO +end + +--- @param selection_user_data ImGuiSelectionUserData +function ImGui.SetNextItemSelectionUserData(selection_user_data) + -- Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code! + -- This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api. + local g = GImGui + local next_item_data = g.NextItemData + next_item_data.SelectionUserData = selection_user_data + next_item_data.FocusScopeId = g.CurrentFocusScopeId + + local ms = g.CurrentMultiSelect + if ms then + -- Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping) + next_item_data.ItemFlagsSet = bit32.bor(next_item_data.ItemFlagsSet, 2097152 --[[ImGuiItemFlags.HasSelectionUserData]], 4194304 --[[ImGuiItemFlags.IsMultiSelect]]) + if ms.IO.RangeSrcItem == selection_user_data then + ms.RangeSrcPassedBy = true + end + --ms.PrevSubmittedItem = ms.CurrSubmittedItem -- Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select. + --ms.CurrSubmittedItem = selection_user_data + else + next_item_data.ItemFlagsSet = bit32.bor(next_item_data.ItemFlagsSet, 2097152 --[[ImGuiItemFlags.HasSelectionUserData]]) + end +end + +-- In charge of: +-- - Applying SetAll for submitted items. +-- - Applying SetRange for submitted items and record end points. +-- - Altering button behavior flags to facilitate use with drag and drop. +-- [Port] out-params as return values: `selected, button_flags = MultiSelectItemHeader(id, selected, button_flags)` (button_flags may be nil: C++ p_button_flags == NULL) +--- @param id ImGuiID +--- @param selected bool +--- @param button_flags ImGuiButtonFlags? +--- @return bool, ImGuiButtonFlags? +function ImGui.MultiSelectItemHeader(id, selected, button_flags) + local g = GImGui + local ms = g.CurrentMultiSelect + + if ms.IsFocused then + local storage = ms.Storage + local item_data = g.NextItemData.SelectionUserData + assert(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId, "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope") + + -- Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect(). + -- This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper. + -- If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect() + if ms.LoopRequestSetAll ~= -1 then + selected = (ms.LoopRequestSetAll == 1) + end + + -- When using Shift+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection) + -- For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function) + if ms.IsKeyboardSetRange then + assert(id ~= 0 and bit32.band(ms.KeyMods, 8192 --[[ImGuiMod_Shift]]) ~= 0) + local is_range_dst = (ms.RangeDstPassedBy == false) and g.NavJustMovedToId == id -- Assume that g.NavJustMovedToId is not clipped. + if is_range_dst then + ms.RangeDstPassedBy = true + end + if is_range_dst and storage.RangeSrcItem == (-1) --[[ImGuiSelectionUserData_Invalid]] then -- If we don't have RangeSrc, assign RangeSrc = RangeDst + storage.RangeSrcItem = item_data + storage.RangeSelected = selected and 1 or 0 + end + local is_range_src = storage.RangeSrcItem == item_data + if is_range_src or is_range_dst or ms.RangeSrcPassedBy ~= ms.RangeDstPassedBy then + -- Apply range-select value to visible items + assert(storage.RangeSrcItem ~= (-1) --[[ImGuiSelectionUserData_Invalid]] and storage.RangeSelected ~= -1) + selected = (storage.RangeSelected ~= 0) + elseif bit32.band(ms.KeyMods, 4096 --[[ImGuiMod_Ctrl]]) == 0 and bit32.band(ms.Flags, 16 --[[ImGuiMultiSelectFlags.NoAutoClear]]) == 0 then + -- Clear other items + selected = false + end + end + end + + -- Alter button behavior flags + -- To handle drag and drop of multiple items we need to avoid clearing selection on click. + -- Enabling this test makes actions using Ctrl+Shift delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items. + if button_flags ~= nil then + button_flags = bit32.bor(button_flags, 524288 --[[ImGuiButtonFlags.NoHoveredOnFocus]]) + button_flags = bit32.band(button_flags, bit32.bnot(bit32.bor(16 --[[ImGuiButtonFlags.PressedOnClick]], 32 --[[ImGuiButtonFlags.PressedOnClickRelease]]))) + if bit32.band(ms.Flags, 16384 --[[ImGuiMultiSelectFlags.SelectOnClickAlways]]) ~= 0 then + button_flags = bit32.bor(button_flags, 16 --[[ImGuiButtonFlags.PressedOnClick]]) + elseif bit32.band(ms.Flags, 32768 --[[ImGuiMultiSelectFlags.SelectOnClickRelease]]) ~= 0 then + button_flags = bit32.bor(button_flags, 32 --[[ImGuiButtonFlags.PressedOnClickRelease]]) + else -- ImGuiMultiSelectFlags.SelectOnAuto + button_flags = bit32.bor(button_flags, (not selected or (g.ActiveId == id and g.ActiveIdHasBeenPressedBefore)) and 16 --[[ImGuiButtonFlags.PressedOnClick]] or 32 --[[ImGuiButtonFlags.PressedOnClickRelease]]) + end + end + return selected, button_flags +end + +-- In charge of: +-- - Auto-select on navigation. +-- - Box-select toggle handling. +-- - Right-click handling. +-- - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse. +-- - Record current selection state for RangeSrc +-- This is all rather complex, best to run and refer to "widgets_multiselect_xxx" tests in imgui_test_suite. +-- [Port] out-params as return values: `selected, pressed = MultiSelectItemFooter(id, selected, pressed, extra_flags)` +--- @param id ImGuiID +--- @param selected bool +--- @param pressed bool +--- @param extra_flags? ImGuiMultiSelectFlags +--- @return bool, bool +function ImGui.MultiSelectItemFooter(id, selected, pressed, extra_flags) + if extra_flags == nil then extra_flags = 0 end + local g = GImGui + local window = g.CurrentWindow + + local ms = g.CurrentMultiSelect + local storage = ms.Storage + if pressed then + ms.IsFocused = true + end + + local hovered = false + if bit32.band(g.LastItemData.StatusFlags, 1 --[[ImGuiItemStatusFlags.HoveredRect]]) ~= 0 then + hovered = ImGui.IsItemHovered(32 --[[ImGuiHoveredFlags.AllowWhenBlockedByPopup]]) + end + if not ms.IsFocused and not hovered then + return selected, pressed + end + + local item_data = g.NextItemData.SelectionUserData + + local flags = bit32.bor(ms.Flags, extra_flags) + local is_singleselect = bit32.band(flags, 1 --[[ImGuiMultiSelectFlags.SingleSelect]]) ~= 0 + local is_ctrl = bit32.band(ms.KeyMods, 4096 --[[ImGuiMod_Ctrl]]) ~= 0 + local is_shift = bit32.band(ms.KeyMods, 8192 --[[ImGuiMod_Shift]]) ~= 0 + + local apply_to_range_src = false + + if g.NavId == id and storage.RangeSrcItem == (-1) --[[ImGuiSelectionUserData_Invalid]] then + apply_to_range_src = true + end + if ms.IsEndIO == false then + ms.IO.Requests:resize(0) + ms.IsEndIO = true + end + + -- Auto-select as you navigate a list + if g.NavJustMovedToId == id then + if bit32.band(flags, 8 --[[ImGuiMultiSelectFlags.NoAutoSelect]]) == 0 then + if is_ctrl and is_shift then + pressed = true + elseif not is_ctrl then + selected = true; pressed = true + end + else + -- With NoAutoSelect, using Shift+keyboard performs a write/copy + if is_shift then + pressed = true + elseif not is_ctrl then + apply_to_range_src = true -- Since if (pressed) {} main block is not running we update this + end + end + end + + if apply_to_range_src then + storage.RangeSrcItem = item_data + storage.RangeSelected = selected and 1 or 0 -- Will be updated at the end of this function anyway. + end + + -- Box-select toggle handling + if ms.BoxSelectId ~= 0 then + local bs = ImGui.GetBoxSelectState(ms.BoxSelectId) + if bs then + local item_rect = MultiSelectScratchItemRect + ImRect_Copy(item_rect, g.LastItemData.Rect) + if not window.DC.NavIsScrollPushableX then -- FIXME: Rename to be more generic. + local table_ = g.CurrentTable + if table_ and table_.CurrentColumn ~= -1 then + -- FIXME: We cannot solely use current ClipRect as it includes HostClipRect. + -- However we account for ClipRect being larger than current column (e.g. when using SpanAllColumns) + -- A more generic version would be nice, but window.WorkRect.Min/Max exclude CellPadding. (#7994, #9383) + local column = table_.Columns[table_.CurrentColumn] + -- [Port] upstream tests ItemFlags against ImGuiItemStatusFlags_HasClipRect (sic), reproduced as-is. + local has_clip_rect = bit32.band(g.LastItemData.ItemFlags, 512 --[[ImGuiItemStatusFlags.HasClipRect]]) ~= 0 + local clip_min_x = has_clip_rect and g.LastItemData.ClipRect.Min.x or window.ClipRect.Min.x + local clip_max_x = has_clip_rect and g.LastItemData.ClipRect.Max.x or window.ClipRect.Max.x + if clip_min_x ~= clip_max_x then -- When zero sized we expect that bounds have been clamped and thus are unreliable + item_rect.Min.x = ImMax(item_rect.Min.x, ImMin(column.MinX, clip_min_x)) + item_rect.Max.x = ImMin(item_rect.Max.x, ImMax(column.MaxX, clip_max_x)) + else + item_rect.Min.x = ImMax(item_rect.Min.x, column.MinX) + item_rect.Max.x = ImMin(item_rect.Max.x, column.MaxX) + end + --GetForegroundDrawList():AddRect(item_rect.Min, item_rect.Max, IM_COL32(255, 0, 255, 255)) + end + end + local rect_overlap_curr = bs.BoxSelectRectCurr:Overlaps(item_rect) + local rect_overlap_prev = bs.BoxSelectRectPrev:Overlaps(item_rect) + if (rect_overlap_curr and not rect_overlap_prev and not selected) or (rect_overlap_prev and not rect_overlap_curr) then + if storage.LastSelectionSize <= 0 and bs.IsStartedSetNavIdOnce then + pressed = true -- First item act as a pressed: code below will emit selection request and set NavId (whatever we emit here will be overridden anyway) + bs.IsStartedSetNavIdOnce = false + else + selected = not selected + ImGui.MultiSelectAddSetRange(ms, selected, 1, item_data, item_data) + end + storage.LastSelectionSize = ImMax(storage.LastSelectionSize + 1, 1) + end + end + end + + -- Right-click handling. + -- FIXME-MULTISELECT: Maybe should be moved to Selectable()? Also see #5816, #8200, #9015 + if hovered and ImGui.IsMouseClicked(1) and bit32.band(flags, bit32.bor(8 --[[ImGuiMultiSelectFlags.NoAutoSelect]], 131072 --[[ImGuiMultiSelectFlags.NoSelectOnRightClick]])) == 0 then + if g.ActiveId ~= 0 and g.ActiveId ~= id then + ImGui.ClearActiveID() + end + ImGui.SetFocusID(id, window) + if not pressed and not selected then + pressed = true + is_ctrl = false; is_shift = false + end + end + + -- Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected. + -- The later, "unless current item is not select", may become optional? It seems like a better default if Enter doesn't necessarily open something + -- (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional? + local enter_pressed = pressed and (g.NavActivateId == id) and bit32.band(g.NavActivateFlags, 1 --[[ImGuiActivateFlags.PreferInput]]) ~= 0 + + -- Alter selection + if pressed and (not enter_pressed or not selected) then + -- Box-select + local input_source = (g.NavJustMovedToId == id or g.NavActivateId == id) and g.NavInputSource or 1 --[[ImGuiInputSource.Mouse]] + if bit32.band(flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 then + if not g.BoxSelectState.IsActive and not g.BoxSelectState.IsStarting and input_source == 1 --[[ImGuiInputSource.Mouse]] and g.IO.MouseClickedCount[0] == 1 then + BoxSelectPreStartDrag(ms.BoxSelectId, item_data) + end + end + + ------------------------------------------------------------------------------------------ + -- ACTION | Begin | Pressed/Activated | End + ------------------------------------------------------------------------------------------ + -- Keys Navigated: | Clear | Src=item, Sel=1 SetRange 1 + -- Keys Navigated: Ctrl | n/a | n/a + -- Keys Navigated: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + -- Keys Navigated: Ctrl+Shift | n/a | Dst=item, Sel=Src => Clear + SetRange Src-Dst + -- Keys Activated: | n/a | Src=item, Sel=1 => Clear + SetRange 1 + -- Keys Activated: Ctrl | n/a | Src=item, Sel=!Sel => SetSange 1 + -- Keys Activated: Shift | n/a | Dst=item, Sel=1 => Clear + SetSange 1 + ------------------------------------------------------------------------------------------ + -- Mouse Pressed: | n/a | Src=item, Sel=1, => Clear + SetRange 1 + -- Mouse Pressed: Ctrl | n/a | Src=item, Sel=!Sel => SetRange 1 + -- Mouse Pressed: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + -- Mouse Pressed: Ctrl+Shift | n/a | Dst=item, Sel=!Sel => SetRange Src-Dst + ------------------------------------------------------------------------------------------ + + if bit32.band(flags, 16 --[[ImGuiMultiSelectFlags.NoAutoClear]]) == 0 then + local request_clear = false + if is_singleselect then + request_clear = true + elseif (input_source == 1 --[[ImGuiInputSource.Mouse]] or g.NavActivateId == id) and not is_ctrl then + if bit32.band(flags, 32 --[[ImGuiMultiSelectFlags.NoAutoClearOnReselect]]) ~= 0 then request_clear = not selected else request_clear = true end + elseif (input_source == 2 --[[ImGuiInputSource.Keyboard]] or input_source == 3 --[[ImGuiInputSource.Gamepad]]) and is_shift and not is_ctrl then + request_clear = true -- With is_shift==false the RequestClear was done in BeginIO, not necessary to do again. + end + if request_clear then + ImGui.MultiSelectAddSetAll(ms, false) + end + end + + local range_direction + local range_selected + if is_shift and not is_singleselect then + --IM_ASSERT(storage.HasRangeSrc and storage.HasRangeValue) + if storage.RangeSrcItem == (-1) --[[ImGuiSelectionUserData_Invalid]] then + storage.RangeSrcItem = item_data + end + if bit32.band(flags, 1048576 --[[ImGuiMultiSelectFlags.CheckboxMode_]]) == 0 then + -- Shift+Arrow always select + -- Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage.RangeSelected) + if is_ctrl and storage.RangeSelected ~= -1 then range_selected = (storage.RangeSelected ~= 0) else range_selected = true end + else + -- Shift+Arrow copy source selection state + -- Shift+Click always copy from target selection state + if ms.IsKeyboardSetRange then + if storage.RangeSelected ~= -1 then range_selected = (storage.RangeSelected ~= 0) else range_selected = true end + else + range_selected = not selected + end + end + range_direction = ms.RangeSrcPassedBy and 1 or -1 + else + -- Ctrl inverts selection, otherwise always select + if bit32.band(flags, 1048576 --[[ImGuiMultiSelectFlags.CheckboxMode_]]) == 0 then + if is_ctrl then selected = not selected else selected = true end + else + selected = not selected + end + storage.RangeSrcItem = item_data + range_selected = selected + range_direction = 1 + end + ImGui.MultiSelectAddSetRange(ms, range_selected, range_direction, storage.RangeSrcItem, item_data) + end + + -- Update/store the selection state of the Source item (used by Ctrl+Shift, when Source is unselected we perform a range unselect) + if storage.RangeSrcItem == item_data then + storage.RangeSelected = selected and 1 or 0 + end + + -- Update/store the selection state of focused item + if g.NavId == id then + storage.NavIdItem = item_data + storage.NavIdSelected = selected and 1 or 0 + end + if storage.NavIdItem == item_data then + ms.NavIdPassedBy = true + end + + return selected, pressed +end + +--- @param ms ImGuiMultiSelectTempData +--- @param selected bool +function ImGui.MultiSelectAddSetAll(ms, selected) + local req = MultiSelectScratchRequest -- ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid } + req.Type = 1 --[[ImGuiSelectionRequestType.SetAll]]; req.Selected = selected; req.RangeDirection = 0; req.RangeFirstItem = (-1) --[[ImGuiSelectionUserData_Invalid]]; req.RangeLastItem = (-1) --[[ImGuiSelectionUserData_Invalid]] + ms.IO.Requests:resize(0) -- Can always clear previous requests + ms.IO.Requests:push_back(req) -- Add new request +end + +--- @param ms ImGuiMultiSelectTempData +--- @param selected bool +--- @param range_dir int +--- @param first_item ImGuiSelectionUserData +--- @param last_item ImGuiSelectionUserData +function ImGui.MultiSelectAddSetRange(ms, selected, range_dir, first_item, last_item) + -- Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges) + -- FIXME-OPT: Disabled on 2026/04/09 as this would break with any form of coarse clipping that we don't know about (e.g. TableNextColumn() return value). + -- The low-hanging fruit would be to know that ImGuiSelectionUserData are sequential indices, in which case we can trivially compare PrevSubmittedItem + RangeDir == FirstItem. + -- User can always perform this merge if required. + -- (#if 0 block not ported) + + local req = MultiSelectScratchRequest -- ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item } + req.Type = 2 --[[ImGuiSelectionRequestType.SetRange]]; req.Selected = selected; req.RangeDirection = range_dir + if range_dir > 0 then + req.RangeFirstItem = first_item; req.RangeLastItem = last_item + else + req.RangeFirstItem = last_item; req.RangeLastItem = first_item + end + ms.IO.Requests:push_back(req) -- Add new request +end + +--- @param storage ImGuiMultiSelectState +function ImGui.DebugNodeMultiSelectState(storage) + local is_active = (storage.LastFrameActive >= ImGui.GetFrameCount() - 2) -- Note that fully clipped early out scrolling tables will appear as inactive here. + if not is_active then ImGui.PushStyleColor(0 --[[ImGuiCol.Text]], ImGui.GetStyleColorVec4(1 --[[ImGuiCol.TextDisabled]])) end + local open = ImGui.TreeNode(storage.ID, "MultiSelect 0x%08X in '%s'%s", storage.ID, storage.Window and storage.Window.Name or "N/A", is_active and "" or " *Inactive*") + if not is_active then ImGui.PopStyleColor() end + if not open then + return + end + -- [Port] ImGuiSelectionUserData may be any Lua value: printed with tostring() instead of %lld (0x%llX) + ImGui.Text("RangeSrcItem = %s, RangeSelected = %d", tostring(storage.RangeSrcItem), storage.RangeSelected) + ImGui.Text("NavIdItem = %s, NavIdSelected = %d", tostring(storage.NavIdItem), storage.NavIdSelected) + ImGui.Text("LastSelectionSize = %d", storage.LastSelectionSize) -- Provided by user + ImGui.TreePop() +end + +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Multi-Select helpers +------------------------------------------------------------------------------- +-- - ImGuiSelectionBasicStorage +-- - ImGuiSelectionExternalStorage +------------------------------------------------------------------------------- +-- [Port] Storage representation: +-- - Upstream _Storage is an ImGuiStorage (vector of {ImGuiID key, int val_i} pairs sorted by key, val_i = selection order +-- or 0 when unselected). Here _Storage is a plain Lua table: [id] = selection order (always > 0); unselected ids are +-- removed (nil) instead of being kept with val_i == 0. Contains()/SetItemSelected() are O(1). +-- - Iteration: `local it, id = nil, 0; local has_more; while true do has_more, it, id = selection:GetNextSelectedItem(it) if not has_more then break end ... end` +-- (C++: void* it = NULL; ImGuiID id; while (selection.GetNextSelectedItem(&it, &id)) {...}). The opaque iterator is an +-- integer position into _IterKeys, a snapshot of the selected ids sorted by key (or by selection order when +-- PreserveOrder is set), rebuilt when an iteration starts (it == nil). Ids unselected during an iteration are skipped. +-- A storage supports one iteration at a time (starting a new one rebuilds the shared snapshot). +-- - Keys returned by AdapterIndexToStorageId must be mutually comparable with '<' (numbers by default) for the sort. + +--- @class ImGuiSelectionBasicStorage +--- @field Size int # Number of selected items, maintained by this helper. +--- @field PreserveOrder bool # = false // GetNextSelectedItem() will return ordered selection (currently implemented by two additional sorts of selection. Could be improved) +--- @field UserData any # = nil // User data for use by adapter function // e.g. selection.UserData = my_items +--- @field AdapterIndexToStorageId fun(self: ImGuiSelectionBasicStorage, idx: int): ImGuiID # e.g. selection.AdapterIndexToStorageId = function(self, idx) return self.UserData[idx + 1].ID end +--- @field _SelectionOrder int # [Internal] Increasing counter to store selection order +--- @field _Storage table # [Internal] Selection set. Think of this as similar to e.g. std::set. Prefer not accessing directly: iterate with GetNextSelectedItem(). +MT.ImGuiSelectionBasicStorage = {} +MT.ImGuiSelectionBasicStorage.__index = MT.ImGuiSelectionBasicStorage + +local function ImGuiSelectionBasicStorage_DefaultAdapterIndexToStorageId(_, idx) return idx end + +--- @return ImGuiSelectionBasicStorage +function ImGuiSelectionBasicStorage() + local this = setmetatable({}, MT.ImGuiSelectionBasicStorage) + this.Size = 0 + this.PreserveOrder = false + this.UserData = nil + this.AdapterIndexToStorageId = ImGuiSelectionBasicStorage_DefaultAdapterIndexToStorageId -- [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; } + this._SelectionOrder = 1 -- Always >0 + this._Storage = {} + this._IterKeys = {} -- [Port] GetNextSelectedItem() snapshot + return this +end + +function MT.ImGuiSelectionBasicStorage:Clear() + self.Size = 0 + self._SelectionOrder = 1 -- Always >0 + table.clear(self._Storage) +end + +--- @param r ImGuiSelectionBasicStorage +function MT.ImGuiSelectionBasicStorage:Swap(r) + self.Size, r.Size = r.Size, self.Size + self._SelectionOrder, r._SelectionOrder = r._SelectionOrder, self._SelectionOrder + self._Storage, r._Storage = r._Storage, self._Storage +end + +--- @param id ImGuiID +--- @return bool +function MT.ImGuiSelectionBasicStorage:Contains(id) + return self._Storage[id] ~= nil +end + +local PairComparerStorage = nil +local function PairComparerByValueInt(lhs, rhs) + return PairComparerStorage[lhs] < PairComparerStorage[rhs] +end + +-- GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user. +-- (e.g. store unselected vs compact down, compact down on demand, use raw ImVector instead of ImGuiStorage...) +-- [Port] `has_more, opaque_it, out_id = selection:GetNextSelectedItem(opaque_it)`: pass nil to start, then the returned opaque_it. +--- @param opaque_it any +--- @return bool, any, ImGuiID +function MT.ImGuiSelectionBasicStorage:GetNextSelectedItem(opaque_it) + local storage = self._Storage + local keys = self._IterKeys + local it = opaque_it + if it == nil then + table.clear(keys) + local n = 0 + for id in pairs(storage) do n = n + 1; keys[n] = id end + if self.PreserveOrder then + PairComparerStorage = storage + table.sort(keys, PairComparerByValueInt) -- ~ImGuiStorage::BuildSortByValueInt() + PairComparerStorage = nil + else + table.sort(keys) -- ImGuiStorage is sorted by key + end + it = 1 + end + local it_end = #keys + 1 + assert(it >= 1 and it <= it_end) + while it < it_end and storage[keys[it]] == nil do + it = it + 1 + end + local has_more = (it ~= it_end) + if has_more then + return true, it + 1, keys[it] + end + return false, it, 0 +end + +--- @param id ImGuiID +--- @param selected bool +function MT.ImGuiSelectionBasicStorage:SetItemSelected(id, selected) + local storage = self._Storage + local p_int = storage[id] + if selected and p_int == nil then storage[id] = self._SelectionOrder; self._SelectionOrder = self._SelectionOrder + 1; self.Size = self.Size + 1 + elseif not selected and p_int ~= nil then storage[id] = nil; self.Size = self.Size - 1 end +end + +--- @param idx int +--- @return ImGuiID +function MT.ImGuiSelectionBasicStorage:GetStorageIdFromIndex(idx) return self.AdapterIndexToStorageId(self, idx) end -- Convert index to item id based on provided adapter. + +-- Optimized for batch edits (with same value of 'selected') +-- [Port] With a hash table there is nothing to defer: insert/remove in place. size_before_amends is unused. +local function ImGuiSelectionBasicStorage_BatchSetItemSelected(selection, id, selected, size_before_amends, selection_order) + local storage = selection._Storage + local is_contained = storage[id] ~= nil + if selected == is_contained then + return + end + if selected then + storage[id] = selection_order + else + storage[id] = nil + end + selection.Size = selection.Size + (selected and 1 or -1) +end + +-- [Port] Upstream sorts the ImGuiStorage vector here (BuildSortByKey). The Lua table needs no sorting: GetNextSelectedItem() sorts its snapshot. +local function ImGuiSelectionBasicStorage_BatchFinish(selection, selected, size_before_amends) +end + +-- Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +-- - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen. +-- - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem. +-- - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection. +-- - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform +-- a lookup in order to have some way to iterate/interpolate between two items. +-- - A full-featured application is likely to allow search/filtering which is likely to lead to using indices +-- and constructing a view index <> object id/ptr data structure anyway. +-- WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ImGuiSelectionBasicStorage' INDIRECTION LOGIC. +-- Notice that with the simplest adapter (using indices everywhere), all functions return their parameters. +-- The most simple implementation (using indices everywhere) would look like: +-- for _, req in ms_io.Requests:iter() do +-- if req.Type == ImGuiSelectionRequestType.SetAll then Clear(); if req.Selected then for n = 0, items_count - 1 do SetItemSelected(n, true) end end end +-- if req.Type == ImGuiSelectionRequestType.SetRange then for n = req.RangeFirstItem, req.RangeLastItem do SetItemSelected(n, req.Selected) end end +-- end +--- @param ms_io ImGuiMultiSelectIO +function MT.ImGuiSelectionBasicStorage:ApplyRequests(ms_io) + -- For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional. + -- It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect(). + -- Other scheme may handle SetAll differently. + assert(ms_io.ItemsCount ~= -1, "Missing value for items_count in BeginMultiSelect() call!") + assert(self.AdapterIndexToStorageId ~= nil) + + -- This is optimized/specialized to cope with very large selections (e.g. 100k+ items) + -- - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish(). + -- - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass. + -- - A more optimal version wouldn't even use ImGuiStorage but directly a ImVector to reduce bandwidth, but this is a reasonable trade off to reuse code. + -- - There are many ways this could be better optimized. The worse case scenario being: using BoxSelect2d in a grid, box-select scrolling down while wiggling + -- left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each. + -- FIXME-OPT: For each block of consecutive SetRange request: + -- - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage. + -- - rewrite sorted storage a single time. + for _, req in ms_io.Requests:iter() do + if req.Type == 1 --[[ImGuiSelectionRequestType.SetAll]] then + self:Clear() + if req.Selected then + local size_before_amends = self.Size -- [Port] _Storage.Data.Size (0 after Clear()) + for idx = 0, ms_io.ItemsCount - 1 do + ImGuiSelectionBasicStorage_BatchSetItemSelected(self, self:GetStorageIdFromIndex(idx), req.Selected, size_before_amends, self._SelectionOrder) + self._SelectionOrder = self._SelectionOrder + 1 + end + ImGuiSelectionBasicStorage_BatchFinish(self, req.Selected, size_before_amends) + end + elseif req.Type == 2 --[[ImGuiSelectionRequestType.SetRange]] then + local selection_changes = req.RangeLastItem - req.RangeFirstItem + 1 + --local g = GImGui; IMGUI_DEBUG_LOG_SELECTION("Req %d/%d: set %d to %d\n", ms_io.Requests:index_from_ptr(req), ms_io.Requests.Size, selection_changes, req.Selected) + if selection_changes == 1 or (selection_changes < math.floor(self.Size / 100)) then + -- Multiple sorted insertion + copy likely to be faster. + -- Technically we could do a single copy with a little more work (sort sequential SetRange requests) + for idx = req.RangeFirstItem, req.RangeLastItem do + self:SetItemSelected(self:GetStorageIdFromIndex(idx), req.Selected) + end + else + -- Append insertion + single sort likely be faster. + -- Use req.RangeDirection to set order field so that Shift+Clicking from 1 to 5 is different than Shift+Clicking from 5 to 1 + local size_before_amends = self.Size + local selection_order = self._SelectionOrder + ((req.RangeDirection < 0) and selection_changes - 1 or 0) + for idx = req.RangeFirstItem, req.RangeLastItem do + ImGuiSelectionBasicStorage_BatchSetItemSelected(self, self:GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order) + selection_order = selection_order + req.RangeDirection + end + if req.Selected then + self._SelectionOrder = self._SelectionOrder + selection_changes + end + ImGuiSelectionBasicStorage_BatchFinish(self, req.Selected, size_before_amends) + end + end + end +end + +------------------------------------------------------------------------------- + +-- Optional helper to apply multi-selection requests to existing randomly accessible storage. +-- Convenient if you want to quickly wire multi-select API on e.g. an array of bool or items storing their own selection state. +--- @class ImGuiSelectionExternalStorage +--- @field UserData any # User data for use by adapter function // e.g. selection.UserData = my_items +--- @field AdapterSetItemSelected fun(self: ImGuiSelectionExternalStorage, idx: int, selected: bool) # e.g. AdapterSetItemSelected = function(self, idx, selected) self.UserData[idx + 1].Selected = selected end +MT.ImGuiSelectionExternalStorage = {} +MT.ImGuiSelectionExternalStorage.__index = MT.ImGuiSelectionExternalStorage + +--- @return ImGuiSelectionExternalStorage +function ImGuiSelectionExternalStorage() + local this = setmetatable({}, MT.ImGuiSelectionExternalStorage) + this.UserData = nil + this.AdapterSetItemSelected = nil + return this +end + +-- Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +-- We also pull 'ms_io.ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage +-- This makes no assumption about underlying storage. +--- @param ms_io ImGuiMultiSelectIO +function MT.ImGuiSelectionExternalStorage:ApplyRequests(ms_io) + assert(self.AdapterSetItemSelected) + for _, req in ms_io.Requests:iter() do + if req.Type == 1 --[[ImGuiSelectionRequestType.SetAll]] then + for idx = 0, ms_io.ItemsCount - 1 do + self.AdapterSetItemSelected(self, idx, req.Selected) + end + end + if req.Type == 2 --[[ImGuiSelectionRequestType.SetRange]] then + for idx = req.RangeFirstItem, req.RangeLastItem do + self.AdapterSetItemSelected(self, idx, req.Selected) + end + end + end +end + +end -- do (Typing-Select, Box-Select, Multi-Select) + ------------------------------------------------------------------------------- -- [SECTION] Widgets: ListBox ------------------------------------------------------------------------------- @@ -73226,6 +74886,7 @@ return { ImGuiActivateFlags = ImGuiActivateFlags, ImGuiAxis = ImGuiAxis, ImGuiBackendFlags = ImGuiBackendFlags, + ImGuiBoxSelectState = ImGuiBoxSelectState, ImGuiButtonFlags = ImGuiButtonFlags, ImGuiChildFlags = ImGuiChildFlags, ImGuiCol = ImGuiCol, @@ -73298,6 +74959,10 @@ return { ImGuiMouseButton = ImGuiMouseButton, ImGuiMouseCursor = ImGuiMouseCursor, ImGuiMouseSource = ImGuiMouseSource, + ImGuiMultiSelectFlags = ImGuiMultiSelectFlags, + ImGuiMultiSelectIO = ImGuiMultiSelectIO, + ImGuiMultiSelectState = ImGuiMultiSelectState, + ImGuiMultiSelectTempData = ImGuiMultiSelectTempData, ImGuiNavLayer = ImGuiNavLayer, ImGuiNavMoveFlags = ImGuiNavMoveFlags, ImGuiNavRenderCursorFlags = ImGuiNavRenderCursorFlags, @@ -73325,6 +74990,11 @@ return { ImGuiPopupPositionPolicy = ImGuiPopupPositionPolicy, ImGuiScrollFlags = ImGuiScrollFlags, ImGuiSelectableFlags = ImGuiSelectableFlags, + ImGuiSelectionBasicStorage = ImGuiSelectionBasicStorage, + ImGuiSelectionExternalStorage = ImGuiSelectionExternalStorage, + ImGuiSelectionRequest = ImGuiSelectionRequest, + ImGuiSelectionRequestType = ImGuiSelectionRequestType, + ImGuiSelectionRequest_Copy = ImGuiSelectionRequest_Copy, ImGuiSelectionUserData_Invalid = ImGuiSelectionUserData_Invalid, ImGuiSeparatorFlags = ImGuiSeparatorFlags, ImGuiSettingsHandler = ImGuiSettingsHandler, @@ -73357,6 +75027,8 @@ return { ImGuiTextFlags = ImGuiTextFlags, ImGuiTooltipFlags = ImGuiTooltipFlags, ImGuiTreeNodeFlags = ImGuiTreeNodeFlags, + ImGuiTypingSelectFlags = ImGuiTypingSelectFlags, + ImGuiTypingSelectState = ImGuiTypingSelectState, ImGuiViewport = ImGuiViewport, ImGuiViewportFlags = ImGuiViewportFlags, ImGuiViewportP = ImGuiViewportP, @@ -73390,6 +75062,7 @@ return { ImIsPowerOfTwo = ImIsPowerOfTwo, ImLengthSqr = ImLengthSqr, ImLerp = ImLerp, + ImLinearRemapClamp = ImLinearRemapClamp, ImLog = ImLog, ImMax = ImMax, ImMemchr = ImMemchr, @@ -73409,6 +75082,7 @@ return { ImS32 = ImS32, ImS8 = ImS8, ImSaturate = ImSaturate, + ImSign = ImSign, ImSin = ImSin, ImSqrt = ImSqrt, ImStb = ImStb, @@ -73428,6 +75102,7 @@ return { ImTextureRect = ImTextureRect, ImTextureRef = ImTextureRef, ImTextureStatus = ImTextureStatus, + ImToUpper = ImToUpper, ImTrunc = ImTrunc, ImTrunc64 = ImTrunc64, ImU16 = ImU16, diff --git a/build/ImGuiTyped.luau b/build/ImGuiTyped.luau index 19875ec..5349e75 100644 --- a/build/ImGuiTyped.luau +++ b/build/ImGuiTyped.luau @@ -13,6 +13,7 @@ export type ImGuiAPI = { ArrowButton: (str_id: string, dir: number) -> boolean, ArrowButtonEx: (...any) -> ...any, Begin: (name: string, open: boolean?, flags: number?) -> (boolean, boolean?), + BeginBoxSelect: (...any) -> ...any, BeginChild: (...any) -> ...any, BeginChildEx: (...any) -> ...any, BeginColumns: (...any) -> ...any, @@ -36,6 +37,7 @@ export type ImGuiAPI = { BeginMenu: (label: string, enabled: boolean?) -> boolean, BeginMenuBar: () -> boolean, BeginMenuEx: (...any) -> ...any, + BeginMultiSelect: (flags: number, selection_size: number?, items_count: number?) -> any, BeginPopup: (str_id: string, flags: number?) -> boolean, BeginPopupContextItem: (str_id: string?, popup_flags: number?) -> boolean, BeginPopupContextVoid: (str_id: string?, popup_flags: number?) -> boolean, @@ -117,11 +119,13 @@ export type ImGuiAPI = { DebugNodeFont: (...any) -> ...any, DebugNodeFontGlyph: (...any) -> ...any, DebugNodeFontGlyphsForSrcMask: (...any) -> ...any, + DebugNodeMultiSelectState: (...any) -> ...any, DebugNodeStorage: (...any) -> ...any, DebugNodeTabBar: (...any) -> ...any, DebugNodeTable: (...any) -> ...any, DebugNodeTableSettings: (...any) -> ...any, DebugNodeTexture: (...any) -> ...any, + DebugNodeTypingSelectState: (...any) -> ...any, DebugNodeViewport: (...any) -> ...any, DebugNodeWindow: (...any) -> ...any, DebugNodeWindowSettings: (...any) -> ...any, @@ -220,6 +224,7 @@ export type ImGuiAPI = { DragScalarN: (label: string, data_type: number, v: any, components: number, v_speed: number?, v_min: any, v_max: any, format: string?, flags: number?) -> ...any, Dummy: (size: ImVec2) -> (), End: () -> (), + EndBoxSelect: (...any) -> ...any, EndChild: () -> (), EndColumns: (...any) -> ...any, EndCombo: () -> (), @@ -235,6 +240,7 @@ export type ImGuiAPI = { EndMainMenuBar: () -> (), EndMenu: () -> (), EndMenuBar: () -> (), + EndMultiSelect: () -> any, EndPopup: () -> (), EndTabBar: () -> (), EndTabItem: () -> (), @@ -274,6 +280,7 @@ export type ImGuiAPI = { GcCompactTransientMiscBuffers: (...any) -> ...any, GcCompactTransientWindowBuffers: (...any) -> ...any, GetBackgroundDrawList: (viewport: any) -> ...any, + GetBoxSelectState: (...any) -> ...any, GetClipboardText: () -> any, GetColorU32: (...any) -> ...any, GetColumnIndex: () -> number, @@ -331,6 +338,7 @@ export type ImGuiAPI = { GetMouseDragDelta: (button: number?, lock_threshold: number?) -> ImVec2, GetMousePos: () -> ImVec2, GetMousePosOnOpeningCurrentPopup: () -> ImVec2, + GetMultiSelectState: (...any) -> ...any, GetNavTweakPressedAmount: (...any) -> ...any, GetPlatformIO: () -> any, GetPopupAllowedExtentRect: (...any) -> ...any, @@ -353,6 +361,7 @@ export type ImGuiAPI = { GetTopMostPopupModal: (...any) -> ...any, GetTreeNodeToLabelSpacing: () -> number, GetTypematicRepeatRate: (...any) -> ...any, + GetTypingSelectRequest: (...any) -> ...any, GetVersion: () -> any, GetViewportPlatformMonitor: (...any) -> ...any, GetWindowAlwaysWantOwnTabBar: (...any) -> ...any, @@ -479,6 +488,10 @@ export type ImGuiAPI = { MenuItem: (...any) -> ...any, MenuItemEx: (...any) -> ...any, MouseButtonToKey: (...any) -> ...any, + MultiSelectAddSetAll: (...any) -> ...any, + MultiSelectAddSetRange: (...any) -> ...any, + MultiSelectItemFooter: (...any) -> ...any, + MultiSelectItemHeader: (...any) -> ...any, NavApplyItemToResult: (...any) -> ...any, NavBiasScoringRect: (...any) -> ...any, NavCalcPreferredRefPos: (...any) -> ...any, @@ -639,6 +652,7 @@ export type ImGuiAPI = { SetNextItemColorMarker: (...any) -> ...any, SetNextItemOpen: (is_open: boolean, cond: number?) -> (), SetNextItemRefVal: (...any) -> ...any, + SetNextItemSelectionUserData: (selection_user_data: any) -> (), SetNextItemShortcut: (key_chord: any, flags: number?) -> (), SetNextItemStorageID: (storage_id: number) -> (), SetNextItemWidth: (item_width: number) -> (), @@ -848,6 +862,9 @@ export type ImGuiAPI = { TreePop: () -> (), TreePush: (...any) -> ...any, TreePushOverrideID: (...any) -> ...any, + TypingSelectFindBestLeadingMatch: (...any) -> ...any, + TypingSelectFindMatch: (...any) -> ...any, + TypingSelectFindNextSingleCharMatch: (...any) -> ...any, Unindent: (indent_w: number?) -> (), UnregisterFontAtlas: (...any) -> ...any, UpdateAliasKey: (...any) -> ...any, @@ -880,6 +897,7 @@ export type ImGuiAPI = { VSliderScalar: (label: string, size: ImVec2, data_type: number, data: any, min: any, max: any, format: string?, flags: number?) -> ...any, Value: (...any) -> ...any, WindowPosAbsToRel: (...any) -> ...any, + WindowPosRelToAbs: (...any) -> ...any, WindowRectAbsToRel: (...any) -> ...any, WindowRectRelToAbs: (...any) -> ...any, WindowSelectViewport: (...any) -> ...any, @@ -913,9 +931,11 @@ export type ImGuiItemFlags = { None: number, NoTabStop: number, NoNav: number, N export type ImGuiItemStatusFlags = { None: number, HoveredRect: number, HasDisplayRect: number, Edited: number, ToggledSelection: number, ToggledOpen: number, HasDeactivated: number, Deactivated: number, HoveredWindow: number, Visible: number, HasClipRect: number, HasShortcut: number, EditedInternal: number, [string]: number } export type ImGuiKey = { None: number, NamedKey_BEGIN: number, Tab: number, LeftArrow: number, RightArrow: number, UpArrow: number, DownArrow: number, PageUp: number, PageDown: number, Home: number, End: number, Insert: number, Delete: number, Backspace: number, Space: number, Enter: number, Escape: number, LeftCtrl: number, RightCtrl: number, Menu: number, K0: number, A: number, K: number, U: number, F1: number, F7: number, F13: number, F19: number, Apostrophe: number, CapsLock: number, Keypad0: number, KeypadDecimal: number, AppBack: number, AppForward: number, Oem102: number, GamepadStart: number, GamepadFaceLeft: number, GamepadDpadLeft: number, GamepadL1: number, GamepadL2: number, GamepadL3: number, GamepadLStickLeft: number, GamepadRStickLeft: number, MouseLeft: number, ReservedForModCtrl: number, NamedKey_END: number, NamedKey_COUNT: number, NavKeyboardTweakSlow: number, NavKeyboardTweakFast: number, NavGamepadTweakSlow: number, NavGamepadTweakFast: number, NavGamepadMenu: number, NavGamepadContextMenu: number, [string]: number } export type ImGuiMouseSource = { Mouse: number, TouchScreen: number, Pen: number, COUNT: number, [string]: number } +export type ImGuiMultiSelectFlags = { None: number, SingleSelect: number, NoSelectAll: number, NoRangeSelect: number, NoAutoSelect: number, NoAutoClear: number, NoAutoClearOnReselect: number, BoxSelect1d: number, BoxSelect2d: number, BoxSelectNoScroll: number, ClearOnEscape: number, ClearOnClickVoid: number, ScopeWindow: number, ScopeRect: number, SelectOnAuto: number, SelectOnClickAlways: number, SelectOnClickRelease: number, NavWrapX: number, NoSelectOnRightClick: number, CheckboxMode_: number, SelectOnMask_: number, SelectOnClick: number, [string]: number } export type ImGuiNextItemDataFlags = { None: number, HasWidth: number, HasOpen: number, HasShortcut: number, HasRefVal: number, HasStorageID: number, HasColorMarker: number, [string]: number } export type ImGuiPopupFlags = { None: number, MouseButtonLeft: number, MouseButtonRight: number, MouseButtonMiddle: number, NoReopen: number, NoOpenOverExistingPopup: number, NoOpenOverItems: number, AnyPopupId: number, AnyPopupLevel: number, AnyPopup: number, MouseButtonShift_: number, MouseButtonMask_: number, InvalidMask_: number, [string]: number } export type ImGuiSelectableFlags = { None: number, NoAutoClosePopups: number, SpanAllColumns: number, AllowDoubleClick: number, Disabled: number, AllowOverlap: number, Highlight: number, SelectOnNav: number, NoHoldingActiveID: number, SelectOnClick: number, SelectOnRelease: number, SpanAvailWidth: number, SetNavIdOnHover: number, NoPadWithHalfSpacing: number, NoSetKeyOwner: number, [string]: number } +export type ImGuiSelectionRequestType = { None: number, SetAll: number, SetRange: number, [string]: number } export type ImGuiSliderFlags = { None: number, Logarithmic: number, NoRoundToFormat: number, NoInput: number, WrapAround: number, ClampOnInput: number, ClampZeroRange: number, NoSpeedTweaks: number, ColorMarkers: number, InvalidMask_: number, AlwaysClamp: number, Vertical: number, ReadOnly: number, [string]: number } export type ImGuiStyleVar = { Alpha: number, DisabledAlpha: number, WindowPadding: number, WindowRounding: number, WindowBorderSize: number, WindowMinSize: number, WindowTitleAlign: number, ChildRounding: number, ChildBorderSize: number, PopupRounding: number, PopupBorderSize: number, FramePadding: number, FrameRounding: number, FrameBorderSize: number, ItemSpacing: number, ItemInnerSpacing: number, IndentSpacing: number, CellPadding: number, ScrollbarSize: number, ScrollbarRounding: number, ScrollbarPadding: number, GrabMinSize: number, GrabRounding: number, ImageRounding: number, ImageBorderSize: number, TabRounding: number, TabBorderSize: number, TabMinWidthBase: number, TabMinWidthShrink: number, TabBarBorderSize: number, TabBarOverlineSize: number, TableAngledHeadersAngle: number, TableAngledHeadersTextAlign: number, TreeLinesSize: number, TreeLinesRounding: number, MenuItemRounding: number, SelectableRounding: number, DragDropTargetRounding: number, ButtonTextAlign: number, SelectableTextAlign: number, SeparatorTextBorderSize: number, SeparatorTextAlign: number, SeparatorTextPadding: number, COUNT: number, [string]: number } export type ImGuiTreeNodeFlags = { None: number, Selected: number, Framed: number, AllowOverlap: number, NoTreePushOnOpen: number, NoAutoOpenOnLog: number, DefaultOpen: number, OpenOnDoubleClick: number, OpenOnArrow: number, Leaf: number, Bullet: number, FramePadding: number, SpanAvailWidth: number, SpanFullWidth: number, SpanLabelWidth: number, SpanAllColumns: number, LabelSpanAllColumns: number, NavLeftJumpsToParent: number, DrawLinesNone: number, DrawLinesFull: number, DrawLinesToNodes: number, NoNavFocus: number, ClipLabelForTrailingButton: number, UpsideDownArrow: number, CollapsingHeader: number, OpenOnMask_: number, DrawLinesMask_: number, [string]: number } @@ -945,9 +965,11 @@ export type ImGuiModule = { ImGuiItemStatusFlags: ImGuiItemStatusFlags, ImGuiKey: ImGuiKey, ImGuiMouseSource: ImGuiMouseSource, + ImGuiMultiSelectFlags: ImGuiMultiSelectFlags, ImGuiNextItemDataFlags: ImGuiNextItemDataFlags, ImGuiPopupFlags: ImGuiPopupFlags, ImGuiSelectableFlags: ImGuiSelectableFlags, + ImGuiSelectionRequestType: ImGuiSelectionRequestType, ImGuiSliderFlags: ImGuiSliderFlags, ImGuiStyleVar: ImGuiStyleVar, ImGuiTreeNodeFlags: ImGuiTreeNodeFlags, diff --git a/build/imgui_love.linemap.json b/build/imgui_love.linemap.json index c02da20..6957318 100644 --- a/build/imgui_love.linemap.json +++ b/build/imgui_love.linemap.json @@ -1 +1 @@ -[[183, "imgui.lua"], [21550, "imgui_demo.lua"], [31978, "imgui_draw.lua"], [37921, "imgui_h.lua"], [40648, "imgui_impl_love.lua"], [41188, "imgui_internal.lua"], [44660, "imgui_tables.lua"], [49719, "imgui_widgets.lua"], [60383, "imstb_rectpack.lua"], [61047, "imstb_textedit.lua"], [62589, "imstb_truetype.lua"], [65377, "imstd_minstdio.lua"]] \ No newline at end of file +[[183, "imgui.lua"], [21574, "imgui_demo.lua"], [32002, "imgui_draw.lua"], [37945, "imgui_h.lua"], [40788, "imgui_impl_love.lua"], [41328, "imgui_internal.lua"], [44995, "imgui_tables.lua"], [50054, "imgui_widgets.lua"], [62043, "imstb_rectpack.lua"], [62707, "imstb_textedit.lua"], [64249, "imstb_truetype.lua"], [67037, "imstd_minstdio.lua"]] \ No newline at end of file diff --git a/build/imgui_love.lua b/build/imgui_love.lua index d30dbdd..f60db92 100644 --- a/build/imgui_love.lua +++ b/build/imgui_love.lua @@ -138,7 +138,7 @@ if not utf8 then end -local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImGuiKey, ImGuiInputFlags, ImRect_Copy, ImGuiDataType, ImStb, IM_TRUNC, ImGuiStyleVarInfo, ImVec2_CopyV, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, ImHashStr, ImS32, IM_ASSERT_USER_ERROR_RET, ImGuiColorEditFlags, ImDrawFlags, ImIsPowerOfTwo, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, ImU16, ImAbs, ImGuiWindowFlags, ImGuiItemFlags, stbte, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ImColor, ImCos, ImS16, ImGuiTabBarFlags, ImSin, ImUpperPowerOfTwo, ImVec2_MulVX, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, ImGuiMouseCursor, DockSettingsRenameNodeReferences, IM_ASSERT_PARANOID, ImCeil, ImGuiCol, ImGuiListClipper, ImLengthSqr, ImS8, ImU8, _PathArcToFastExXY, ExampleAppPropertyEditorMT, ImFabs, ImGuiScrollFlags, ImGui_ImplLOVE_GetBackendData, ImLog, ImPow, CustomConstraints, ExampleAppLogMT, IMGUI_DEBUG_LOG_FONT, ImFontAtlasBuildInit, ImFontCalcWordWrapPositionEx, ImGuiComboFlags, ImGuiPopupFlags, ImGuiSliderFlags, ImTextClassifierSetCharClass, ImVec2_AddVA, ImVec2_SubVA, MyDocument, ShowExampleMenuFile, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiStyle_Copy, ExampleMemberInfo, ExampleTree_CreateNode, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImGuiTableColumn, MyItem_s_current_sort_specs, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, EditTableSizingFlags, IM_FREE, ImDrawListSplitter, ImGuiDragDropFlags, MyItemColumnID_ID, MyItemColumnID_Name, MyItemColumnID_Quantity, stb_text_locate_coord, stb_text_makeundo_insert, stb_text_makeundo_replace, stb_textedit_find_charpos, stbtt_GetGlyphShape, EditTabBarFittingPolicyFlags, ExampleImageViewerData, ImGuiTableColumnSortSpecs, ImGuiTableInstanceData, ImGuiTableSettings, stb_text_makeundo_delete, stb_text_redo, stb_text_undo, stb_textedit_click, stb_textedit_drag, stbtt_FindGlyphIndex, stbtt_GetFontOffsetForIndex, stbtt_GetFontVMetrics, stbtt_GetGlyphBitmapBox, stbtt_GetGlyphHMetrics, stbtt_InitFont, stbtt_MakeGlyphBitmapSubpixelPrefilter, stbtt_ScaleForPixelHeight, stbtt__GetGlyphInfoT2, stbtt__GetGlyphShapeT2, stbtt_fontinfo, ImGuiOldColumnData, ImGuiOldColumnFlags, ImGuiOldColumns, ImGuiTable, ImGuiTableColumnSettings, ImGuiTableSortSpecs, ImGuiTableTempData, ImGui_ImplLOVE_CreateTexture, ImGui_ImplLOVE_DestroyTexture, ImGui_ImplLOVE_Init, ImGui_ImplLOVE_NewFrame, ImGui_ImplLOVE_RenderDrawData, ImGui_ImplLOVE_Shutdown, ImGui_ImplLOVE_UpdateTexture, stbrp_init_target, IMGUI_TABLE_MAX_COLUMNS, IMSTB_TEXTEDIT_UNDOSTATECOUNT, IM_COL32_DISABLE, ImGui_ImplLove, STB_TEXTEDIT_K_BACKSPACE, STB_TEXTEDIT_K_DELETE, STB_TEXTEDIT_K_DOWN, STB_TEXTEDIT_K_LEFT, STB_TEXTEDIT_K_LINEEND, STB_TEXTEDIT_K_LINESTART, STB_TEXTEDIT_K_PGDOWN, STB_TEXTEDIT_K_PGUP, STB_TEXTEDIT_K_REDO, STB_TEXTEDIT_K_RIGHT, STB_TEXTEDIT_K_SHIFT, STB_TEXTEDIT_K_TEXTEND +local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImGuiKey, ImGuiInputFlags, ImRect_Copy, ImVec2_CopyV, ImGuiDataType, ImStb, IM_TRUNC, ImGuiStyleVarInfo, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, ImHashStr, ImS32, IM_ASSERT_USER_ERROR_RET, ImGuiColorEditFlags, ImDrawFlags, ImIsPowerOfTwo, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, ImAbs, ImU16, ImGuiWindowFlags, ImGuiItemFlags, stbte, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ImColor, ImCos, ImS16, ImGuiTabBarFlags, ImSin, ImUpperPowerOfTwo, ImVec2_MulVX, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, ImGuiMouseCursor, DockSettingsRenameNodeReferences, IM_ASSERT_PARANOID, ImCeil, ImGuiCol, ImGuiListClipper, ImLengthSqr, ImS8, ImU8, _PathArcToFastExXY, ExampleAppPropertyEditorMT, ImFabs, ImGuiScrollFlags, ImGui_ImplLOVE_GetBackendData, ImLog, ImPow, CustomConstraints, ExampleAppLogMT, ImFontAtlasBuildInit, ImGuiComboFlags, ImGuiMultiSelectFlags, ImGuiPopupFlags, ImGuiSliderFlags, ImTextClassifierSetCharClass, ImVec2_AddVA, ImVec2_SubVA, MyDocument, ShowExampleMenuFile, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiStyle_Copy, ExampleMemberInfo, ExampleTree_CreateNode, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImFontAtlasTextureAdd, ImFontAtlasTextureBlockQueueUpload, ImGuiTableColumn, MyItem_s_current_sort_specs, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, EditTableSizingFlags, IM_FREE, ImDrawListSplitter, MyItemColumnID_ID, MyItemColumnID_Name, MyItemColumnID_Quantity, stb_text_locate_coord, stb_text_makeundo_insert, stb_text_makeundo_replace, stb_textedit_find_charpos, stbtt_GetGlyphShape, EditTabBarFittingPolicyFlags, ExampleImageViewerData, ImGuiTableColumnSortSpecs, ImGuiTableInstanceData, ImGuiTableSettings, stb_text_makeundo_delete, stb_text_redo, stb_text_undo, stb_textedit_click, stb_textedit_drag, stbtt_FindGlyphIndex, stbtt_GetFontOffsetForIndex, stbtt_GetFontVMetrics, stbtt_GetGlyphBitmapBox, stbtt_GetGlyphHMetrics, stbtt_InitFont, stbtt_MakeGlyphBitmapSubpixelPrefilter, stbtt_ScaleForPixelHeight, stbtt__GetGlyphInfoT2, stbtt__GetGlyphShapeT2, stbtt_fontinfo, ImGuiOldColumnData, ImGuiOldColumnFlags, ImGuiOldColumns, ImGuiTable, ImGuiTableColumnSettings, ImGuiTableSortSpecs, ImGuiTableTempData, ImGui_ImplLOVE_CreateTexture, ImGui_ImplLOVE_DestroyTexture, ImGui_ImplLOVE_Init, ImGui_ImplLOVE_NewFrame, ImGui_ImplLOVE_RenderDrawData, ImGui_ImplLOVE_Shutdown, ImGui_ImplLOVE_UpdateTexture, stbrp_init_target, IMGUI_TABLE_MAX_COLUMNS, IMSTB_TEXTEDIT_UNDOSTATECOUNT, IM_COL32_DISABLE, ImGui_ImplLove, STB_TEXTEDIT_K_BACKSPACE, STB_TEXTEDIT_K_DELETE, STB_TEXTEDIT_K_DOWN, STB_TEXTEDIT_K_LEFT, STB_TEXTEDIT_K_LINEEND, STB_TEXTEDIT_K_LINESTART, STB_TEXTEDIT_K_PGDOWN, STB_TEXTEDIT_K_PGUP, STB_TEXTEDIT_K_REDO, STB_TEXTEDIT_K_RIGHT, STB_TEXTEDIT_K_SHIFT, STB_TEXTEDIT_K_TEXTEND local type, mathMin, mathMax, mathFloor, mathCeil, bitOr, bitLShift = type, math.min, math.max, math.floor, math.ceil, bit32.bor, bit32.lshift local function IM_COL32(R, G, B, A) return (bitOr(bitLShift(A, 24 --[[IM_COL32_A_SHIFT]]), bitLShift(B, 16 --[[IM_COL32_B_SHIFT]]), bitLShift(G, 8 --[[IM_COL32_G_SHIFT]]), bitLShift(R, 0 --[[IM_COL32_R_SHIFT]]))) end @@ -11010,6 +11010,8 @@ function ImGui.GcCompactTransientMiscBuffers() g.ItemFlagsStack:clear() g.GroupStack:clear() g.InputTextLineIndex:clear() + g.MultiSelectTempDataStacked = 0 + g.MultiSelectTempData:clear() ImGui.TableGcCompactSettings() for _, atlas in g.FontAtlases:iter() do atlas:CompactCache() @@ -14506,6 +14508,8 @@ function ImGui.Shutdown() -- TODO: -- Clear everything else + g.MultiSelectStorage:Clear() + g.MultiSelectTempData:clear() g.MenusIdSubmittedThisFrame:clear() end @@ -19882,7 +19886,7 @@ local function ImGuiListClipper_StepInternal(clipper) -- Add box selection range local bs = g.BoxSelectState - if bs and bs.IsActive and bs.Window == window then + if bs.IsActive and bs.Window == window then -- FIXME: Selectable() use of half-ItemSpacing isn't consistent in matter of layout, as ItemAdd(bb) stray above ItemSize()'s CursorPos. -- RangeSelect's BoxSelect relies on comparing overlap of previous and current rectangle and is sensitive to that. -- As a workaround we currently half ItemSpacing worth on each side. @@ -20028,7 +20032,7 @@ function ImGui.ErrorRecoveryTryToRecoverWindowState(state_in) IM_ASSERT_USER_ERROR(false, "Missing EndTabBar()") ImGui.EndTabBar() end - while g.CurrentMultiSelect ~= nil and g.CurrentMultiSelect.Storage.Window == window and ImGui.EndMultiSelect do + while g.CurrentMultiSelect ~= nil and g.CurrentMultiSelect.Storage.Window == window do --V1044 IM_ASSERT_USER_ERROR(false, "Missing EndMultiSelect()") ImGui.EndMultiSelect() end @@ -21365,6 +21369,25 @@ function ImGui.ShowMetricsWindow(p_open) ImGui.TreePop() end + -- Details for TypingSelect + if ImGui.TreeNode("TypingSelect", "TypingSelect (%d)", (#g.TypingSelectState.SearchBuffer ~= 0) and 1 or 0) then + ImGui.DebugNodeTypingSelectState(g.TypingSelectState) + ImGui.TreePop() + end + + -- Details for MultiSelect + if ImGui.TreeNode("MultiSelect", "MultiSelect (%d)", g.MultiSelectStorage.AliveCount) then + local bs = g.BoxSelectState + ImGui.BulletText("BoxSelect ID=0x%08X, Starting = %d, Active %d", bs.ID, B(bs.IsStarting), B(bs.IsActive)) + for n = 0, g.MultiSelectStorage:GetMapSize() - 1 do + local state = g.MultiSelectStorage:GetByIndex(n) + if state then + ImGui.DebugNodeMultiSelectState(state) + end + end + ImGui.TreePop() + end + -- Details for Docking if ImGui.TreeNode("Docking") then local dc = g.DockContext @@ -21492,6 +21515,7 @@ function ImGui.ShowMetricsWindow(p_open) ImGui.Indent() ImGui.Text("NavWindow: '%s'", N(g.NavWindow)) ImGui.Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer) + ImGui.Text("NavLastValidSelectionUserData = %s", tostring(g.NavLastValidSelectionUserData)) -- [Port] any Lua value: tostring() instead of %lld (0x%llX) ImGui.Text("NavActive: %d, NavVisible: %d", B(io.NavActive), B(io.NavVisible)) ImGui.Text("NavActivateId/DownId/PressedId: %08X/%08X/%08X", g.NavActivateId, g.NavActivateDownId or 0, g.NavActivatePressedId or 0) ImGui.Text("NavActivateFlags: %04X", g.NavActivateFlags or 0) @@ -40633,6 +40657,122 @@ function ImColorMT:U32() return ImGui.ColorConvertFloat4ToU32(self.Value) end function ImColorMT:SetHSV(h, s, v, a) self.Value.x, self.Value.y, self.Value.z = ImGui.ColorConvertHSVtoRGB(h, s, v); self.Value.w = a or 1.0 end function ImColor.HSV(h, s, v, a) local r, g, b = ImGui.ColorConvertHSVtoRGB(h, s, v); return ImColor(r, g, b, a or 1.0) end +------------------------------------------------------------------------------- +-- [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiSelectionRequestType, ImGuiSelectionRequest, ImGuiMultiSelectIO, ImGuiSelectionBasicStorage) +------------------------------------------------------------------------------- + +-- Multi-selection system +-- Documentation at: https://github.com/ocornut/imgui/wiki/Multi-Select +-- - Refer to 'Demo->Widgets->Selection State & Multi-Select' for demos using this. +-- - This system implements standard multi-selection idioms (Ctrl+Mouse/Keyboard, Shift+Mouse/Keyboard, etc) +-- with support for clipper (skipping non-visible items), box-select and many other details. +-- - Selectable(), Checkbox() are supported but custom widgets may use it as well. +-- - TreeNode() is technically supported but... using this correctly is more complicated: you need some sort of linear/random access to your tree, +-- which is suited to advanced trees setups also implementing filters and clipper. We will work toward simplifying and demoing it. +-- - In the spirit of Dear ImGui design, your code owns actual selection data. +-- This is designed to allow all kinds of selection storage you may use in your application e.g. set/map/hash. +-- About ImGuiSelectionBasicStorage: +-- - This is an optional helper to store a selection state and apply selection requests. +-- - It is used by our demos and provided as a convenience to quickly implement multi-selection. +-- Usage: +-- - Identify submitted items with SetNextItemSelectionUserData(), most likely using an index into your current data-set. +-- - Store and maintain actual selection data using persistent object identifiers. +-- - Usage flow: +-- BEGIN - (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO result. +-- - (2) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 6. +-- - (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. Calculate its index and pass to clipper:IncludeItemByIndex(). If storing indices in ImGuiSelectionUserData, a simple clipper:IncludeItemByIndex(ms_io.RangeSrcItem) call will work. +-- LOOP - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls. +-- END - (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO result. +-- - (6) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 2. +-- If you submit all items (no clipper), Step 2 and 3 are optional and will be handled by each item themselves. It is fine to always honor those steps. +-- About ImGuiSelectionUserData: +-- - This can store an application-defined identifier (e.g. index or pointer) submitted via SetNextItemSelectionUserData(). +-- - In return we store them into RangeSrcItem/RangeFirstItem/RangeLastItem and other fields in ImGuiMultiSelectIO. +-- - Most applications will store an object INDEX, hence the chosen name and type. Storing an index is natural, because +-- SetRange requests will give you two end-points and you will need to iterate/interpolate between them to update your selection. +-- - However it is perfectly possible to store a POINTER or another IDENTIFIER inside ImGuiSelectionUserData. +-- Our system never assume that you identify items by indices, it never attempts to interpolate between two values. +-- - If you enable ImGuiMultiSelectFlags.NoRangeSelect then it is guaranteed that you will never have to interpolate +-- between two ImGuiSelectionUserData, which may be a convenient way to use part of the feature with less code work. +-- - As most users will want to store an index, for convenience and to reduce confusion we use ImS64 instead of void*, +-- being syntactically easier to downcast. Feel free to reinterpret_cast and store a pointer inside. +-- [Port] ImGuiSelectionUserData is any Lua value compared with '==' (an integer index, or e.g. a table reference instead of a pointer). +-- -1 (ImGuiSelectionUserData_Invalid) is reserved. ImGuiSelectionBasicStorage/ImGuiSelectionExternalStorage require integer indices. + +-- Flags for BeginMultiSelect() +--- @enum ImGuiMultiSelectFlags +ImGuiMultiSelectFlags = { + None = 0, + SingleSelect = bitLShift(1, 0), -- Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho! + NoSelectAll = bitLShift(1, 1), -- Disable Ctrl+A shortcut to select all. + NoRangeSelect = bitLShift(1, 2), -- Disable Shift+selection mouse/keyboard support (useful for unordered 2D selection). With BoxSelect is also ensure contiguous SetRange requests are not combined into one. This allows not handling interpolation in SetRange requests. + NoAutoSelect = bitLShift(1, 3), -- Disable selecting items when navigating (useful for e.g. supporting range-select in a list of checkboxes). + NoAutoClear = bitLShift(1, 4), -- Disable clearing selection when navigating or selecting another one (generally used with ImGuiMultiSelectFlags.NoAutoSelect. useful for e.g. supporting range-select in a list of checkboxes). + NoAutoClearOnReselect = bitLShift(1, 5), -- Disable clearing selection when clicking/selecting an already selected item. + BoxSelect1d = bitLShift(1, 6), -- Enable box-selection with same width and same x pos items (e.g. full row Selectable()). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space. + BoxSelect2d = bitLShift(1, 7), -- Enable box-selection with varying width or varying x pos items support (e.g. different width labels, or 2D layout/grid). This is slower: alters clipping logic so that e.g. horizontal movements will update selection of normally clipped items. + BoxSelectNoScroll = bitLShift(1, 8), -- Disable scrolling when box-selecting and moving mouse near edges of scope. + ClearOnEscape = bitLShift(1, 9), -- Clear selection when pressing Escape while scope is focused. + ClearOnClickVoid = bitLShift(1, 10), -- Clear selection when clicking on empty location within scope. + ScopeWindow = bitLShift(1, 11), -- Scope for _BoxSelect and _ClearOnClickVoid is whole window (Default). Use if BeginMultiSelect() covers a whole window or used a single time in same window. + ScopeRect = bitLShift(1, 12), -- Scope for _BoxSelect and _ClearOnClickVoid is rectangle encompassing BeginMultiSelect()/EndMultiSelect(). Use if BeginMultiSelect() is called multiple times in same window. + SelectOnAuto = bitLShift(1, 13), -- Apply selection on mouse down when clicking on unselected item, on mouse up when clicking on selected item. (Default) + SelectOnClickAlways = bitLShift(1, 14), -- Apply selection on mouse down when clicking on any items. Prevents Drag and Drop from being used on multiple-selection, but allows e.g. BoxSelect to always reselect even when clicking inside an existing selection. (Excel style behavior) + SelectOnClickRelease = bitLShift(1, 15), -- Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection. + --RangeSelect2d = bitLShift(1, 15), -- Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does. + NavWrapX = bitLShift(1, 16), -- [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one. + NoSelectOnRightClick = bitLShift(1, 17), -- Disable default right-click processing, which selects item on mouse down, and is designed for context-menus. + CheckboxMode_ = bitLShift(1, 20), -- [Internal] +} +ImGuiMultiSelectFlags.SelectOnMask_ = bit32.bor(ImGuiMultiSelectFlags.SelectOnAuto, ImGuiMultiSelectFlags.SelectOnClickAlways, ImGuiMultiSelectFlags.SelectOnClickRelease) +ImGuiMultiSelectFlags.SelectOnClick = ImGuiMultiSelectFlags.SelectOnAuto -- RENAMED in 1.92.6 + +-- Main IO structure returned by BeginMultiSelect()/EndMultiSelect(). +-- This mainly contains a list of selection requests. +-- - Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen. +-- - Some fields are only useful if your list is dynamic and allows deletion (getting post-deletion focus/state right is shown in the demo) +-- - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code. +--- @class ImGuiMultiSelectIO +--- @field Requests ImVector # ms:w, app:r / ms:w app:r // Requests to apply to your selection data. (1-based .Data) +--- @field RangeSrcItem ImGuiSelectionUserData # ms:w app:r / // (If using clipper) Begin: Source item (often the first selected item) must never be clipped: use clipper:IncludeItemByIndex() to ensure it is submitted. +--- @field NavIdItem ImGuiSelectionUserData # ms:w, app:r / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items). +--- @field NavIdSelected bool # ms:w, app:r / app:r // (If using deletion) Last known selection state for NavId (if part of submitted items). +--- @field RangeSrcReset bool # app:w / ms:r // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection). +--- @field ItemsCount int # ms:w, app:r / app:r // 'int items_count' parameter to BeginMultiSelect() is copied here for convenience, allowing simpler calls to your ApplyRequests handler. Not used internally. + +-- Selection request type +--- @enum ImGuiSelectionRequestType +ImGuiSelectionRequestType = { + None = 0, + SetAll = 1, -- Request app to clear selection (if Selected==false) or select all items (if Selected==true). We cannot set RangeFirstItem/RangeLastItem as its contents is entirely up to user (not necessarily an index) + SetRange = 2, -- Request app to select/unselect [RangeFirstItem..RangeLastItem] items (inclusive) based on value of Selected. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false. +} + +-- Selection request item +--- @class ImGuiSelectionRequest +--- @field Type ImGuiSelectionRequestType # ms:w, app:r / ms:w, app:r // Request type. You'll most often receive 1 Clear + 1 SetRange with a single-item range. +--- @field Selected bool # ms:w, app:r / ms:w, app:r // Parameter for SetAll/SetRange requests (true = select, false = unselect) +--- @field RangeDirection int # / ms:w app:r // Parameter for SetRange request: +1 when RangeFirstItem comes before RangeLastItem, -1 otherwise. Useful if you want to preserve selection order on a backward Shift+Click. +--- @field RangeFirstItem ImGuiSelectionUserData # / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom). +--- @field RangeLastItem ImGuiSelectionUserData # / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top). Inclusive! +function ImGuiSelectionRequest() + return { Type = 0 --[[ImGuiSelectionRequestType.None]], Selected = false, RangeDirection = 0, RangeFirstItem = -1, RangeLastItem = -1 } +end +-- [Port] ImVector copy function: push_back() copies fields into the preallocated element (C++ copies by value), +-- so callers can push a reused scratch request without allocating. +function ImGuiSelectionRequest_Copy(t, k, v) + local dst = t[k] + dst.Type = v.Type; dst.Selected = v.Selected; dst.RangeDirection = v.RangeDirection; dst.RangeFirstItem = v.RangeFirstItem; dst.RangeLastItem = v.RangeLastItem +end + +--- @return ImGuiMultiSelectIO +function ImGuiMultiSelectIO() + return { Requests = ImVector(ImGuiSelectionRequest, ImGuiSelectionRequest_Copy), RangeSrcItem = -1, NavIdItem = -1, NavIdSelected = false, RangeSrcReset = false, ItemsCount = 0 } +end + +-- ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage: see [SECTION] Widgets: Multi-Select helpers in imgui_widgets.lua +-- (constructors and methods are defined there, like upstream's out-of-line definitions in imgui_widgets.cpp). + --- @class ImGuiSettingsHandler --- Fns take (ctx, handler, ...) like upstream. ReadOpenFn(ctx, handler, name) returns entry; ReadLineFn(ctx, handler, entry, line); --- WriteAllFn(ctx, handler, buf: ImGuiTextBuffer) @@ -41573,6 +41713,16 @@ end +--- @param x float +--- @return float # Sign operator - returns -1, 0 or 1 based on sign of argument +function ImSign(x) if x < 0.0 then return -1.0 elseif x > 0.0 then return 1.0 end return 0.0 end + +function ImLinearRemapClamp(s0, s1, d0, d1, x) return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0 end + +--- @param c int # byte +--- @return int +function ImToUpper(c) if c >= 97 and c <= 122 then return bit32.band(c, bit32.bnot(32)) end return c end + IM_F32_TO_INT8_UNBOUND = function(val) return math.floor(val * 255.0 + (val >= 0 and 0.5 or -0.5)) end -- Unsaturated, for display purpose IM_F32_TO_INT8_SAT = function(val) return math.floor(ImSaturate(val) * 255.0 + 0.5) end -- Saturated, always output 0..255 @@ -41845,6 +41995,12 @@ end --- @nodiscard function ImGui.WindowPosAbsToRel(window, p) local off = window.DC.CursorStartPos return ImVec2(p.x - off.x, p.y - off.y) end +--- @param window ImGuiWindow +--- @param p ImVec2 +--- @return ImVec2 +--- @nodiscard +function ImGui.WindowPosRelToAbs(window, p) local off = window.DC.CursorStartPos return ImVec2(p.x + off.x, p.y + off.y) end + --- @param c char --- @return bool # True if this character is a ' ' or '\t' function ImCharIsBlankA(c) return c == 32 or c == 9 end @@ -42067,6 +42223,9 @@ function IM_RECT:Add(p) end end +function IM_RECT:AddX(x) if self.Min.x > x then self.Min.x = x end if self.Max.x < x then self.Max.x = x end end +function IM_RECT:AddY(y) if self.Min.y > y then self.Min.y = y end if self.Max.y < y then self.Max.y = y end end + --- @param amount float|ImVec2 function IM_RECT:Expand(amount) @@ -43035,6 +43194,164 @@ local function ImGuiNavItemData() return this end +------------------------------------------------------------------------------- +-- [SECTION] Typing-select support +------------------------------------------------------------------------------- + +-- Flags for GetTypingSelectRequest() +--- @enum ImGuiTypingSelectFlags +ImGuiTypingSelectFlags = { + None = 0, + AllowBackspace = bitLShift(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) + AllowSingleCharMode = bitLShift(1, 1), -- Allow "single char" search mode which is activated when pressing the same character multiple times. +} + +-- Returned by GetTypingSelectRequest(), designed to eventually be public. +--- @class ImGuiTypingSelectRequest +--- @field Flags ImGuiTypingSelectFlags # Flags passed to GetTypingSelectRequest() +--- @field SearchBufferLen int +--- @field SearchBuffer string # Search buffer contents (use full string. unless SingleCharMode is set, in which case use SingleCharSize). +--- @field SelectRequest bool # Set when buffer was modified this frame, requesting a selection. +--- @field SingleCharMode bool # Notify when buffer contains same character repeated, to implement special mode. In this situation it preferred to not display any on-screen search indication. +--- @field SingleCharSize int # Length in bytes of first letter codepoint (1 for ascii, 2-4 for UTF-8). If (SearchBufferLen==RepeatCharSize) only 1 letter has been input. +local function ImGuiTypingSelectRequest() + return { Flags = 0, SearchBufferLen = 0, SearchBuffer = "", SelectRequest = false, SingleCharMode = false, SingleCharSize = 0 } +end + +-- Storage for GetTypingSelectRequest() +-- [Port] SearchBuffer is an immutable Lua string (C++: char[64], 63 bytes max + zero terminator). SearchBuffer[0] != 0 -> #SearchBuffer ~= 0. +--- @class ImGuiTypingSelectState +--- @field Request ImGuiTypingSelectRequest # User-facing data +--- @field SearchBuffer string # Search buffer: no need to make dynamic as this search is very transient. +--- @field FocusScope ImGuiID +--- @field LastRequestFrame int +--- @field LastRequestTime float +--- @field SingleCharModeLock bool # After a certain single char repeat count we lock into SingleCharMode. Two benefits: 1) buffer never fill, 2) we can provide an immediate SingleChar mode without timer elapsing. +MT.ImGuiTypingSelectState = {} +MT.ImGuiTypingSelectState.__index = MT.ImGuiTypingSelectState + +--- @return ImGuiTypingSelectState +function ImGuiTypingSelectState() + return setmetatable({ Request = ImGuiTypingSelectRequest(), SearchBuffer = "", FocusScope = 0, LastRequestFrame = 0, LastRequestTime = 0.0, SingleCharModeLock = false }, MT.ImGuiTypingSelectState) +end +function MT.ImGuiTypingSelectState:Clear() self.SearchBuffer = ""; self.SingleCharModeLock = false end -- We preserve remaining data for easier debugging + +------------------------------------------------------------------------------- +-- [SECTION] Box-select support +------------------------------------------------------------------------------- + +--- @class ImGuiBoxSelectState +--- @field ID ImGuiID +--- @field IsActive bool +--- @field IsStarting bool +--- @field IsStartedFromVoid bool # Starting click was not from an item. +--- @field IsStartedSetNavIdOnce bool +--- @field RequestClear bool +--- @field KeyMods ImGuiKeyChord # Latched key-mods for box-select logic. +--- @field StartPosRel ImVec2 # Start position in window-contents relative space (to support scrolling) +--- @field EndPosRel ImVec2 # End position in window-contents relative space +--- @field ScrollAccum ImVec2 # Scrolling accumulator (to behave at high-frame spaces) +--- @field Window ImGuiWindow? +--- @field UnclipMode bool # (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select. +--- @field UnclipRect ImRect # Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets. +--- @field UnclipRects ImRect[] # Per-axis versions. [Port] 1-based: [1] = upstream [0] (X), [2] = upstream [1] (Y) +--- @field BoxSelectRectPrev ImRect # Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos) +--- @field BoxSelectRectCurr ImRect +--- @return ImGuiBoxSelectState +function ImGuiBoxSelectState() + return { + -- Active box-selection data (persistent, 1 active at a time) + ID = 0, IsActive = false, IsStarting = false, IsStartedFromVoid = false, IsStartedSetNavIdOnce = false, RequestClear = false, + KeyMods = 0, + StartPosRel = ImVec2(), EndPosRel = ImVec2(), ScrollAccum = ImVec2(), + Window = nil, + + -- Temporary/Transient data + UnclipMode = false, + UnclipRect = ImRect(), + UnclipRects = { ImRect(), ImRect() }, + BoxSelectRectPrev = ImRect(), + BoxSelectRectCurr = ImRect(), + } +end + +------------------------------------------------------------------------------- +-- [SECTION] Multi-select support +------------------------------------------------------------------------------- + +-- We always assume that -1 is an invalid value (which works for indices and pointers) +-- (ImGuiSelectionUserData_Invalid is defined above with the ImGuiSelectionUserData alias) + +-- Temporary storage for multi-select +--- @class ImGuiMultiSelectTempData +--- @field IO ImGuiMultiSelectIO # MUST BE FIRST FIELD. Requests are set and returned by BeginMultiSelect()/EndMultiSelect() + written to by user during the loop. +--- @field Storage ImGuiMultiSelectState? +--- @field FocusScopeId ImGuiID # Copied from g.CurrentFocusScopeId (unless another selection scope was pushed manually) +--- @field Flags ImGuiMultiSelectFlags +--- @field ScopeRectMin ImVec2 +--- @field BackupCursorMaxPos ImVec2 +--- @field BoxSelectId ImGuiID +--- @field KeyMods ImGuiKeyChord +--- @field LoopRequestSetAll int # -1: no operation, 0: clear all, 1: select all. +--- @field IsEndIO bool # Set when switching IO from BeginMultiSelect() to EndMultiSelect() state. +--- @field IsFocused bool # Set if currently focusing the selection scope (any item of the selection). May be used if you have custom shortcut associated to selection. +--- @field IsKeyboardSetRange bool # Set by BeginMultiSelect() when using Shift+Navigation. Because scrolling may be affected we can't afford a frame of lag with Shift+Navigation. +--- @field NavIdPassedBy bool +--- @field RangeSrcPassedBy bool # Set by the item that matches RangeSrcItem. +--- @field RangeDstPassedBy bool # Set by the item that matches NavJustMovedToId when IsSetRange is set. +--- @field IsSoleOrUnknownSelectionSize bool +MT.ImGuiMultiSelectTempData = {} +MT.ImGuiMultiSelectTempData.__index = MT.ImGuiMultiSelectTempData + +--- @return ImGuiMultiSelectTempData +function ImGuiMultiSelectTempData() + local this = setmetatable({ IO = ImGuiMultiSelectIO(), ScopeRectMin = ImVec2(), BackupCursorMaxPos = ImVec2() }, MT.ImGuiMultiSelectTempData) + this:Clear() + return this +end +-- Zero-clear except IO as we preserve IO.Requests[] buffer allocation. +function MT.ImGuiMultiSelectTempData:Clear() + self:ClearIO() + self.Storage = nil + self.FocusScopeId = 0 + self.Flags = 0 + ImVec2_CopyV(self.ScopeRectMin, 0.0, 0.0) + ImVec2_CopyV(self.BackupCursorMaxPos, 0.0, 0.0) + self.BoxSelectId = 0 + self.KeyMods = 0 + self.LoopRequestSetAll = 0 + self.IsEndIO = false + self.IsFocused = false + self.IsKeyboardSetRange = false + self.NavIdPassedBy = false + self.RangeSrcPassedBy = false + self.RangeDstPassedBy = false + self.IsSoleOrUnknownSelectionSize = false +end +function MT.ImGuiMultiSelectTempData:ClearIO() local io = self.IO; io.Requests:resize(0); io.RangeSrcItem = (-1) --[[ImGuiSelectionUserData_Invalid]]; io.NavIdItem = (-1) --[[ImGuiSelectionUserData_Invalid]]; io.NavIdSelected = false; io.RangeSrcReset = false end + +-- Persistent storage for multi-select (as long as selection is alive) +--- @class ImGuiMultiSelectState +--- @field Window ImGuiWindow? +--- @field ID ImGuiID +--- @field LastFrameActive int # Last used frame-count, for GC. +--- @field LastSelectionSize int # Set by BeginMultiSelect() based on optional info provided by user. May be -1 if unknown. +--- @field RangeSelected int # -1 (don't have) or true/false +--- @field NavIdSelected int # -1 (don't have) or true/false +--- @field RangeSrcItem ImGuiSelectionUserData # +--- @field NavIdItem ImGuiSelectionUserData # SetNextItemSelectionUserData() value for NavId (if part of submitted items) +--- @return ImGuiMultiSelectState +function ImGuiMultiSelectState() + return { Window = nil, ID = 0, LastFrameActive = 0, LastSelectionSize = 0, RangeSelected = -1, NavIdSelected = -1, RangeSrcItem = (-1) --[[ImGuiSelectionUserData_Invalid]], NavIdItem = (-1) --[[ImGuiSelectionUserData_Invalid]] } +end + +--- @param id ImGuiID +--- @return ImGuiBoxSelectState? +function ImGui.GetBoxSelectState(id) local g = GImGui; if id ~= 0 and g.BoxSelectState.ID == id and g.BoxSelectState.IsActive then return g.BoxSelectState end return nil end +--- @param id ImGuiID +--- @return ImGuiMultiSelectState? +function ImGui.GetMultiSelectState(id) local g = GImGui; return g.MultiSelectStorage:GetByKey(id) end + ------------------------------------------------------------------------------- -- [SECTION] ImGuiContext (main Dear ImGui context) ------------------------------------------------------------------------------- @@ -43316,6 +43633,7 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct NavHighlightItemUnderNav = false, -- Disable mouse hovering highlight. Highlight navigation focused item instead of mouse hovered item. NavIdIsAlive = false, -- Nav widget has been seen this frame ~~ NavRectRel is valid NavInputSource = 2 --[[ImGuiInputSource.Keyboard]], -- Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource.Keyboard or ImGuiInputSource.Gamepad + NavLastValidSelectionUserData = (-1) --[[ImGuiSelectionUserData_Invalid]], -- Last valid data passed to SetNextItemSelectionUser(), or -1. For current window. Not reset when focusing an item that doesn't have selection data. NavWindowingTarget = nil, NavWindowingTargetAnim = nil, NavWindowingListWindow = nil, -- Target window when doing Ctrl+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! NavWindowingTimer = 0.0, NavWindowingHighlightAlpha = 0.0, NavWindowingToggleLayer = false, -- Set while Alt or GamepadMenu is held, may be cleared by other operations, and processed when releasing the key. @@ -43355,6 +43673,14 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct NavActivateId = 0, NavActivateDownId = 0, NavActivatePressedId = 0, -- ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey.Space) || IsKeyDown(ImGuiKey.Enter) || IsKeyPressed(ImGuiKey.NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItemByID() NavActivateFlags = 0 --[[ImGuiActivateFlags.None]], + -- Navigation: record of last move request + NavJustMovedFromFocusScopeId = 0, -- Just navigated from this focus scope id (result of a successfully MoveRequest). + NavJustMovedToId = 0, -- Just navigated to this id (result of a successfully MoveRequest). + NavJustMovedToFocusScopeId = 0, -- Just navigated to this focus scope id (result of a successfully MoveRequest). + NavJustMovedToKeyMods = 0, + NavJustMovedToIsTabbing = false, -- Copy of ImGuiNavMoveFlags.IsTabbing. Maybe we should store whole flags. + NavJustMovedToHasSelectionData = false, -- Copy of move result's ItemFlags & ImGuiItemFlags.HasSelectionUserData). Maybe we should just store ImGuiNavItemData. + FrameCount = 0, FrameCountEnded = -1, @@ -43415,6 +43741,8 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct MouseCursor = 0 --[[ImGuiMouseCursor.Arrow]], MouseStationaryTimer = 0.0, -- Time the mouse has been stationary (with some loose heuristic) + TypingSelectState = ImGuiTypingSelectState(), -- State for GetTypingSelectRequest() + InputTextState = ImGuiInputTextState(), InputTextLineIndex = ImGuiTextIndex(), -- Temporary storage InputTextDeactivatedState = ImGuiInputTextDeactivatedState(), @@ -43436,6 +43764,13 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct CurrentTabBarStack = ImVector(), -- holds ImGuiTabBar references directly (no ImGuiPtrOrIndex needed in Lua) ShrinkWidthBuffer = ImVector(), -- ImGuiShrinkWidthItem + -- Multi-Select state + BoxSelectState = ImGuiBoxSelectState(), + CurrentMultiSelect = nil, + MultiSelectTempDataStacked = 0, -- Temporary multi-select data size (because we leave previous instances undestructed, we generally don't use MultiSelectTempData.Size) + MultiSelectTempData = ImVector(), -- ImVector, 1-based + MultiSelectStorage = ImPool(ImGuiMultiSelectState), -- ImPool + WindowResizeBorderExpectedRect = ImRect(), -- Expected border rect, switch to relative edit if moving WindowResizeRelativeMode = false, @@ -46198,7 +46533,7 @@ function ImGui.TableUpdateLayout(table_) local inner_window = table_.InnerWindow local bs = g.BoxSelectState - if bs and bs.Window == inner_window and bs.UnclipMode then + if bs.Window == inner_window and bs.UnclipMode then ImGui.TableApplyExternalUnclipRect(table_, bs.UnclipRect) end @@ -51073,14 +51408,14 @@ function ImGui.Checkbox(label, v) -- Range-Selection/Multi-selection support (header) local checked = v if is_multi_select then - -- TODO: MultiSelectItemHeader() + checked = ImGui.MultiSelectItemHeader(id, checked, nil) end local pressed, hovered, held = ImGui.ButtonBehavior(total_bb, id) -- Range-Selection/Multi-selection support (footer) if is_multi_select then - -- TODO: MultiSelectItemFooter() + checked, pressed = ImGui.MultiSelectItemFooter(id, checked, pressed, 1048576 --[[ImGuiMultiSelectFlags.CheckboxMode_]]) elseif pressed then checked = not checked end @@ -57937,7 +58272,7 @@ function ImGui.Selectable(label, selected, flags, size_arg) local was_selected = selected if is_multi_select then -- Handle multi-select + alter button flags for it - -- TODO: selected, button_flags = ImGui.MultiSelectItemHeader(id, selected, button_flags) + selected, button_flags = ImGui.MultiSelectItemHeader(id, selected, button_flags) end local pressed, hovered, held = ImGui.ButtonBehavior(bb, id, button_flags) @@ -57945,7 +58280,7 @@ function ImGui.Selectable(label, selected, flags, size_arg) -- Multi-selection support (footer) if is_multi_select then - -- TODO: selected, pressed = ImGui.MultiSelectItemFooter(id, selected, pressed) + selected, pressed = ImGui.MultiSelectItemFooter(id, selected, pressed) else -- Auto-select when moved into -- - This will be more fully fleshed in the range-select branch @@ -58038,6 +58373,1331 @@ function ImGui.Selectable(label, selected, flags, size_arg) return pressed, selected end +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Typing-Select support +------------------------------------------------------------------------------- + +do -- [Port] Typing-Select, Box-Select, Multi-Select sections: locals kept private + +local MT = ImGui.GetMetatables() + +local TypingSelectCharBuf = { 0, 0, 0, 0, 0 } -- [Luau] scratch for ImTextCharToUtf8() (C++: char w_buf[5]) + +-- [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(...) then +-- local req = ImGui.GetTypingSelectRequest() +-- if req then focus_idx = ImGui.TypingSelectFindMatch(req, #my_items, function(user_data, n) return my_items[n + 1].Name end, my_items, -1) end +-- However the code is written in a way where calling it from multiple locations is safe (e.g. to obtain buffer). +-- [Port] Item indices passed to get_item_name_func(user_data, idx) and returned by TypingSelectFind*() are 0-based like upstream (-1 = none). +--- @param flags? ImGuiTypingSelectFlags +--- @return ImGuiTypingSelectRequest? +function ImGui.GetTypingSelectRequest(flags) + if flags == nil then flags = 0 --[[ImGuiTypingSelectFlags.None]] end + local g = GImGui + local data = g.TypingSelectState + local out_request = data.Request + + -- Clear buffer + local TYPING_SELECT_RESET_TIMER = 1.80 -- FIXME: Potentially move to IO config. + local TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK = 4 -- Lock single char matching when repeating same char 4 times + if #data.SearchBuffer ~= 0 then + local clear_buffer = false + clear_buffer = clear_buffer or (g.NavFocusScopeId ~= data.FocusScope) + clear_buffer = clear_buffer or (data.LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time) + clear_buffer = clear_buffer or g.NavAnyRequest + clear_buffer = clear_buffer or (g.ActiveId ~= 0 and g.NavActivateId == 0) -- Allow temporary SPACE activation to not interfere + clear_buffer = clear_buffer or ImGui.IsKeyPressed(526 --[[ImGuiKey.Escape]]) or ImGui.IsKeyPressed(525 --[[ImGuiKey.Enter]]) + clear_buffer = clear_buffer or (ImGui.IsKeyPressed(523 --[[ImGuiKey.Backspace]]) and bit32.band(flags, 1 --[[ImGuiTypingSelectFlags.AllowBackspace]]) == 0) + --if clear_buffer then IMGUI_DEBUG_LOG("GetTypingSelectRequest(): Clear SearchBuffer.\n") end + if clear_buffer then + data:Clear() + end + end + + -- Append to buffer + local buffer_max_len = 64 - 1 -- [Port] IM_COUNTOF(data->SearchBuffer) - 1 + local buffer_len = #data.SearchBuffer + local select_request = false + local input_queue = g.IO.InputQueueCharacters + for n = 1, input_queue.Size do + repeat + local w = input_queue.Data[n] + local w_buf = TypingSelectCharBuf + local w_len = ImStd.ImTextCharToUtf8(w_buf, w) -- [Port] also gives ImTextCountUtf8BytesFromStr(&w, &w + 1) + if w < 32 or (buffer_len == 0 and ImCharIsBlankW(w)) or (buffer_len + w_len > buffer_max_len) then -- Ignore leading blanks + break -- continue + end + local w_str = string.char(table.unpack(w_buf, 1, w_len)) + if data.SingleCharModeLock and w_len == out_request.SingleCharSize and string.sub(data.SearchBuffer, 1, w_len) == w_str then + select_request = true -- Same character: don't need to append to buffer. + break -- continue + end + if data.SingleCharModeLock then + data:Clear() -- Different character: clear + buffer_len = 0 + end + data.SearchBuffer = string.sub(data.SearchBuffer, 1, buffer_len) .. w_str -- Append + buffer_len = buffer_len + w_len + select_request = true + until true + end + input_queue:resize(0) + + -- Handle backspace + if bit32.band(flags, 1 --[[ImGuiTypingSelectFlags.AllowBackspace]]) ~= 0 and ImGui.IsKeyPressedEx(523 --[[ImGuiKey.Backspace]], 1 --[[ImGuiInputFlags.Repeat]]) then + local p = ImStd.ImTextFindPreviousUtf8Codepoint(data.SearchBuffer, 1, buffer_len + 1) -- 1-based byte position + data.SearchBuffer = string.sub(data.SearchBuffer, 1, p - 1) + buffer_len = p - 1 + end + + -- Return request if any + if buffer_len == 0 then + return nil + end + if select_request then + data.FocusScope = g.NavFocusScopeId + data.LastRequestFrame = g.FrameCount + data.LastRequestTime = g.Time + end + out_request.Flags = flags + out_request.SearchBufferLen = buffer_len + out_request.SearchBuffer = data.SearchBuffer + out_request.SelectRequest = (data.LastRequestFrame == g.FrameCount) + out_request.SingleCharMode = false + out_request.SingleCharSize = 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. + -- - SingleCharMode is always set for first input character, because it usually leads to a "next". + if bit32.band(flags, 2 --[[ImGuiTypingSelectFlags.AllowSingleCharMode]]) ~= 0 then + local buf = out_request.SearchBuffer + local buf_begin = 1 + local buf_end = buf_begin + out_request.SearchBufferLen + local c0_len = ImStd.ImTextCountUtf8BytesFromChar(buf, buf_begin, buf_end) + local c0 = string.sub(buf, buf_begin, buf_begin + c0_len - 1) + local p = buf_begin + c0_len + while p < buf_end do + if string.sub(buf, p, p + c0_len - 1) ~= c0 then -- memcmp(buf_begin, p, c0_len) != 0 + break + end + p = p + c0_len + end + local single_char_count = (p == buf_end) and math.floor(out_request.SearchBufferLen / c0_len) or 0 + out_request.SingleCharMode = (single_char_count > 0 or data.SingleCharModeLock) + out_request.SingleCharSize = c0_len + data.SingleCharModeLock = data.SingleCharModeLock or (single_char_count >= TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK) -- From now on we stop search matching to lock to single char mode. + end + + return out_request +end + +-- [Port] s1 is the byte range [s1_begin, s1_end) of string s1, s2 is a whole string (out of range bytes read as 0 like a C terminator). +local function ImStrimatchlen(s1, s1_begin, s1_end, s2) + local match_len = 0 + local s2_p = 1 + while s1_begin < s1_end and ImToUpper(string.byte(s1, s1_begin)) == ImToUpper(string.byte(s2, s2_p) or 0) do + s1_begin = s1_begin + 1 + s2_p = s2_p + 1 + match_len = match_len + 1 + end + return match_len +end + +-- Default handler for finding a result for typing-select. You may implement your own. +-- You might want to display a tooltip to visualize the current request SearchBuffer +-- When SingleCharMode is set: +-- - it is better to NOT display a tooltip of other on-screen display indicator. +-- - the index of the currently focused item is required. +-- if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO.NavIdItem, otherwise from g.NavLastValidSelectionUserData. +--- @param req ImGuiTypingSelectRequest? +--- @param items_count int +--- @param get_item_name_func fun(user_data: any, idx: int): string # idx is 0-based +--- @param user_data any +--- @param nav_item_idx int # 0-based, -1 if unknown +--- @return int # 0-based, -1 if none +function ImGui.TypingSelectFindMatch(req, items_count, get_item_name_func, user_data, nav_item_idx) + if req == nil or req.SelectRequest == false then -- Support NULL parameter so both calls can be done from same spot. + return -1 + end + local idx = -1 + if req.SingleCharMode and bit32.band(req.Flags, 2 --[[ImGuiTypingSelectFlags.AllowSingleCharMode]]) ~= 0 then + idx = ImGui.TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx) + else + idx = ImGui.TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data) + end + if idx ~= -1 then + ImGui.SetNavCursorVisibleAfterMove() + end + return idx +end + +-- Special handling when a single character is repeated: perform search on a single letter and goes to next. +function ImGui.TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx) + -- FIXME: Assume selection user data is index. Would be extremely practical. + --if nav_item_idx == -1 then + -- nav_item_idx = g.NavLastValidSelectionUserData + --end + + local first_match_idx = -1 + local return_next_match = false + for idx = 0, items_count - 1 do + repeat + local item_name = get_item_name_func(user_data, idx) + if ImStrimatchlen(req.SearchBuffer, 1, 1 + req.SingleCharSize, item_name) < req.SingleCharSize then + break -- continue + end + if return_next_match then -- Return next matching item after current item. + return idx + end + if first_match_idx == -1 and nav_item_idx == -1 then -- Return first match immediately if we don't have a nav_item_idx value. + return idx + end + if first_match_idx == -1 then -- Record first match for wrapping. + first_match_idx = idx + end + if nav_item_idx == idx then -- Record that we encountering nav_item so we can return next match. + return_next_match = true + end + until true + end + return first_match_idx -- First result +end + +function ImGui.TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data) + local longest_match_idx = -1 + local longest_match_len = 0 + for idx = 0, items_count - 1 do + local item_name = get_item_name_func(user_data, idx) + local match_len = ImStrimatchlen(req.SearchBuffer, 1, 1 + req.SearchBufferLen, item_name) + if match_len > longest_match_len then -- [Port] inverted 'if (match_len <= longest_match_len) continue;' + longest_match_idx = idx + longest_match_len = match_len + if match_len == req.SearchBufferLen then + break + end + end + end + return longest_match_idx +end + +--- @param data ImGuiTypingSelectState +function ImGui.DebugNodeTypingSelectState(data) + ImGui.Text("SearchBuffer = \"%s\"", data.SearchBuffer) + ImGui.Text("SingleCharMode = %d, Size = %d, Lock = %d", data.Request.SingleCharMode and 1 or 0, data.Request.SingleCharSize, data.SingleCharModeLock and 1 or 0) + ImGui.Text("LastRequest = time: %.2f, frame: %d", data.LastRequestTime, data.LastRequestFrame) +end + +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Box-Select support +-- This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet. +------------------------------------------------------------------------------- +-- Extra logic in MultiSelectItemFooter() and ImGuiListClipper:Step() +------------------------------------------------------------------------------- +-- - BoxSelectPreStartDrag() [Internal] +-- - BoxSelectActivateDrag() [Internal] +-- - BoxSelectDeactivateDrag() [Internal] +-- - BoxSelectScrollWithMouseDrag() [Internal] +-- - BeginBoxSelect() [Internal] +-- - EndBoxSelect() [Internal] +------------------------------------------------------------------------------- + +-- Call on the initial click. +local function BoxSelectPreStartDrag(id, clicked_item) + local g = GImGui + local bs = g.BoxSelectState + bs.ID = id + bs.IsStarting = true -- Consider starting box-select. + bs.IsStartedFromVoid = (clicked_item == (-1) --[[ImGuiSelectionUserData_Invalid]]) + bs.IsStartedSetNavIdOnce = bs.IsStartedFromVoid + bs.KeyMods = g.IO.KeyMods + local off = g.CurrentWindow.DC.CursorStartPos -- [Luau] inline WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos) + ImVec2_CopyV(bs.StartPosRel, g.IO.MousePos.x - off.x, g.IO.MousePos.y - off.y) + ImVec2_Copy(bs.EndPosRel, bs.StartPosRel) + ImVec2_CopyV(bs.ScrollAccum, 0.0, 0.0) +end + +local function BoxSelectActivateDrag(bs, window) + local g = GImGui + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Activate\n", bs.ID) + bs.IsActive = true + bs.Window = window + bs.IsStarting = false + ImGui.SetActiveID(bs.ID, window) + ImGui.SetActiveIdUsingAllKeyboardKeys() + if bs.IsStartedFromVoid and bit32.band(bs.KeyMods, bit32.bor(4096 --[[ImGuiMod_Ctrl]], 8192 --[[ImGuiMod_Shift]])) == 0 then + bs.RequestClear = true + end +end + +local function BoxSelectDeactivateDrag(bs) + local g = GImGui + bs.IsActive = false; bs.IsStarting = false + if g.ActiveId == bs.ID then + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Deactivate\n", bs.ID) + ImGui.ClearActiveID() + end + bs.ID = 0 +end + +local function BoxSelectScrollWithMouseDrag(bs, window, inner_r) + local g = GImGui + assert(bs.Window == window) + for n = 1, 2 do -- each axis -- [Port] 1 = X, 2 = Y + repeat + local k = (n == 1) and "x" or "y" + local mouse_pos = g.IO.MousePos[k] + local dist = (mouse_pos > inner_r.Max[k]) and mouse_pos - inner_r.Max[k] or (mouse_pos < inner_r.Min[k]) and mouse_pos - inner_r.Min[k] or 0.0 + local scroll_curr = window.Scroll[k] + if dist == 0.0 or (dist < 0.0 and scroll_curr < 0.0) or (dist > 0.0 and scroll_curr >= window.ScrollMax[k]) then + break -- continue + end + + local speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0, 1.0, 4.0, ImAbs(dist)) -- x1 to x4 depending on distance + local scroll_step = g.FontSize * 35.0 * speed_multiplier * ImSign(dist) * g.IO.DeltaTime + bs.ScrollAccum[k] = bs.ScrollAccum[k] + scroll_step + + -- Accumulate into a stored value so we can handle high-framerate + local scroll_step_i = ImFloor(bs.ScrollAccum[k]) + if scroll_step_i == 0.0 then + break -- continue + end + if n == 1 then + ImGui.SetScrollX(window, scroll_curr + scroll_step_i) + else + ImGui.SetScrollY(window, scroll_curr + scroll_step_i) + end + bs.ScrollAccum[k] = bs.ScrollAccum[k] - scroll_step_i + until true + end +end + +--- @param scope_rect ImRect +--- @param window ImGuiWindow +--- @param box_select_id ImGuiID +--- @param ms_flags ImGuiMultiSelectFlags +--- @return bool +function ImGui.BeginBoxSelect(scope_rect, window, box_select_id, ms_flags) + local g = GImGui + local bs = g.BoxSelectState + ImGui.KeepAliveID(box_select_id) + if bs.ID ~= box_select_id then + return false + end + + -- IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry. + bs.UnclipMode = false + bs.RequestClear = false + if bs.IsStarting and ImGui.IsMouseDragPastThreshold(0) then + BoxSelectActivateDrag(bs, window) + elseif (bs.IsStarting or bs.IsActive) and g.IO.MouseDown[0] == false then + BoxSelectDeactivateDrag(bs) + end + if not bs.IsActive then + return false + end + + -- Current frame absolute prev/current rectangles are used to toggle selection. + -- They are derived from positions relative to scrolling space, so "previous" rectangle is reprojected for current frame coordinates. + -- [Luau] scalar versions of WindowPosRelToAbs()/ImClamp()/ImMin()/ImMax() (no temporary ImVec2) + local off = window.DC.CursorStartPos + local start_pos_abs_x, start_pos_abs_y = bs.StartPosRel.x + off.x, bs.StartPosRel.y + off.y + local prev_end_pos_abs_x, prev_end_pos_abs_y = bs.EndPosRel.x + off.x, bs.EndPosRel.y + off.y -- Clamped already + local curr_end_pos_abs_x, curr_end_pos_abs_y = g.IO.MousePos.x, g.IO.MousePos.y + if bit32.band(ms_flags, 2048 --[[ImGuiMultiSelectFlags.ScopeWindow]]) ~= 0 then -- Box-select scrolling only happens with ScopeWindow + curr_end_pos_abs_x = ImClamp(curr_end_pos_abs_x, scope_rect.Min.x, scope_rect.Max.x) + curr_end_pos_abs_y = ImClamp(curr_end_pos_abs_y, scope_rect.Min.y, scope_rect.Max.y) + end + ImVec2_CopyV(bs.BoxSelectRectPrev.Min, ImMin(start_pos_abs_x, prev_end_pos_abs_x), ImMin(start_pos_abs_y, prev_end_pos_abs_y)) + ImVec2_CopyV(bs.BoxSelectRectPrev.Max, ImMax(start_pos_abs_x, prev_end_pos_abs_x), ImMax(start_pos_abs_y, prev_end_pos_abs_y)) + ImVec2_CopyV(bs.BoxSelectRectCurr.Min, ImMin(start_pos_abs_x, curr_end_pos_abs_x), ImMin(start_pos_abs_y, curr_end_pos_abs_y)) + ImVec2_CopyV(bs.BoxSelectRectCurr.Max, ImMax(start_pos_abs_x, curr_end_pos_abs_x), ImMax(start_pos_abs_y, curr_end_pos_abs_y)) + --IMGUI_DEBUG_LOG("StartPosRel (%.2f,%.2f) EndPosRel (%.2f,%.2f) -> (%.2f,%.2f)\n", bs.StartPosRel.x, bs.StartPosRel.y, bs.EndPosRel.x, bs.EndPosRel.y, WindowPosAbsToRel(window, g.IO.MousePos).x, WindowPosAbsToRel(window, g.IO.MousePos).y) + + -- Box-select 2D mode detects change of the rectangle. + -- Storing unclip rects which will be tested by widgets supporting box-select. Always update rectangles when active (even if we don't use them). + -- To facilitate understanding this: enable IMGUI_DEBUG_BOXSELECT and visualize all geometry. + if bit32.band(ms_flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 then + -- For both sides, compute the area differing between Prev and Curr rectangles. + local unclip_x, unclip_y = bs.UnclipRects[1], bs.UnclipRects[2] -- [Port] UnclipRects[0], UnclipRects[1] + ImVec2_CopyV(unclip_x.Min, 3.4028234663853e+38 --[[FLT_MAX]], 3.4028234663853e+38 --[[FLT_MAX]]); ImVec2_CopyV(unclip_x.Max, -3.4028234663853e+38 --[[FLT_MAX]], -3.4028234663853e+38 --[[FLT_MAX]]) + ImVec2_CopyV(unclip_y.Min, 3.4028234663853e+38 --[[FLT_MAX]], 3.4028234663853e+38 --[[FLT_MAX]]); ImVec2_CopyV(unclip_y.Max, -3.4028234663853e+38 --[[FLT_MAX]], -3.4028234663853e+38 --[[FLT_MAX]]) + local curr, prev = bs.BoxSelectRectCurr, bs.BoxSelectRectPrev + for side = 0, 1 do + local a = (side == 0) and curr.Min or curr.Max + local b = (side == 0) and prev.Min or prev.Max + local d_min_x, d_min_y = ImMin(a.x, b.x), ImMin(a.y, b.y) + local d_max_x, d_max_y = ImMax(a.x, b.x), ImMax(a.y, b.y) + if d_min_x ~= d_max_x then + unclip_x:AddX(d_min_x) + unclip_x:AddX(d_max_x) + end + if d_min_y ~= d_max_y then + unclip_y:AddY(d_min_y) + unclip_y:AddY(d_max_y) + end + end + + -- box_select_intersection = BoxSelectRectPrev + BoxSelectRectCurr + local box_select_intersection_min_x, box_select_intersection_min_y = ImMin(prev.Min.x, curr.Min.x), ImMin(prev.Min.y, curr.Min.y) + local box_select_intersection_max_x, box_select_intersection_max_y = ImMax(prev.Max.x, curr.Max.x), ImMax(prev.Max.y, curr.Max.y) + if bit32.band(ms_flags, 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]]) ~= 0 then + if prev.Min.x ~= curr.Min.x or prev.Max.x ~= curr.Max.x then + unclip_x:AddY(box_select_intersection_min_y) + unclip_x:AddY(box_select_intersection_max_y) + end + end + if bit32.band(ms_flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 then + if prev.Min.y ~= curr.Min.y or prev.Max.y ~= curr.Max.y then + unclip_y:AddX(box_select_intersection_min_x) + unclip_y:AddX(box_select_intersection_max_x) + end + end + + -- Merge both rectangles into one. + -- FIXME-OPT: When UnclipRect.Area() is much larger than the sum of UnclipRects[0]/[1] Areas, widgets should + -- ideally first use UnclipRect as a first coarse cull layer + the individual ones as a second validation. + ImRect_Copy(bs.UnclipRect, unclip_x) + bs.UnclipRect:Add(unclip_y) + if not bs.UnclipRect:IsInverted() and (not window.ClipRect:Contains(bs.UnclipRect.Min) or not window.ClipRect:Contains(bs.UnclipRect.Max)) then -- !! Don't use Contains(ImRect) + bs.UnclipMode = true + end + if bs.UnclipMode and g.CurrentTable ~= nil then + ImGui.TableApplyExternalUnclipRect(g.CurrentTable, bs.UnclipRect) -- No need submitting both + end + end + + -- [Port] IMGUI_DEBUG_BOXSELECT visualization not ported (compile-time debug option, disabled upstream). + return true +end + +--- @param scope_rect ImRect +--- @param ms_flags ImGuiMultiSelectFlags +function ImGui.EndBoxSelect(scope_rect, ms_flags) + local g = GImGui + local window = g.CurrentWindow + local bs = g.BoxSelectState + assert(bs.IsActive) + bs.UnclipMode = false + + -- Render selection rectangle + bs.EndPosRel = ImGui.WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)) -- Clamp stored position according to current scrolling view + local box_select_r = ImRect(bs.BoxSelectRectCurr.Min, bs.BoxSelectRectCurr.Max) + box_select_r:ClipWith(scope_rect) + local draw_window = ImGui.FindFrontMostVisibleChildWindow(window) + draw_window.DrawList:AddRectFilled(box_select_r.Min, box_select_r.Max, ImGui.GetColorU32(29 --[[ImGuiCol.SeparatorHovered]], 0.30)) -- FIXME-MULTISELECT: Styling + draw_window.DrawList:AddRect(box_select_r.Min, box_select_r.Max, ImGui.GetColorU32(59 --[[ImGuiCol.NavCursor]])) -- FIXME-MULTISELECT FIXME-DPI: Styling + + -- Scroll + local enable_scroll = bit32.band(ms_flags, 2048 --[[ImGuiMultiSelectFlags.ScopeWindow]]) ~= 0 and bit32.band(ms_flags, 256 --[[ImGuiMultiSelectFlags.BoxSelectNoScroll]]) == 0 + if enable_scroll then + local scroll_r = ImRect(scope_rect.Min, scope_rect.Max) + scroll_r:Expand(-g.FontSize) + --GetForegroundDrawList():AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255)) + if not scroll_r:Contains(g.IO.MousePos) then + BoxSelectScrollWithMouseDrag(bs, window, scroll_r) + end + end +end + +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Multi-Select support +------------------------------------------------------------------------------- +-- - DebugLogMultiSelectRequests() [Internal] +-- - CalcScopeRect() [Internal] +-- - BeginMultiSelect() +-- - EndMultiSelect() +-- - SetNextItemSelectionUserData() +-- - MultiSelectItemHeader() [Internal] +-- - MultiSelectItemFooter() [Internal] +-- - DebugNodeMultiSelectState() [Internal] +------------------------------------------------------------------------------- + +-- [Luau] reused scratch request: ImVector:push_back() copies it into the preallocated element (ImGuiSelectionRequest_Copy) +local MultiSelectScratchRequest = { Type = 0, Selected = false, RangeDirection = 0, RangeFirstItem = -1, RangeLastItem = -1 } +local MultiSelectScratchItemRect = ImRect() -- [Luau] scratch for MultiSelectItemFooter()'s item_rect + +local function DebugLogMultiSelectRequests(func, io) + local g = GImGui + for _, req in io.Requests:iter() do + if req.Type == 1 --[[ImGuiSelectionRequestType.SetAll]] then IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", func, req.Selected and 1 or 0, req.Selected and "SelectAll" or "Clear") end + if req.Type == 2 --[[ImGuiSelectionRequestType.SetRange]] then IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %s..%s = %d (dir %d)\n", func, tostring(req.RangeFirstItem), tostring(req.RangeLastItem), req.Selected and 1 or 0, req.RangeDirection) end + end +end + +local function CalcScopeRect(ms, window) + if bit32.band(ms.Flags, 4096 --[[ImGuiMultiSelectFlags.ScopeRect]]) ~= 0 then + -- Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only + -- This probably doesn't work inside a table as there are ample ambiguities related to exact time of calling BeginMultiSelect()/EndMultiSelect(). + return ImRect(ms.ScopeRectMin.x, ms.ScopeRectMin.y, ImMax(window.DC.CursorMaxPos.x, ms.ScopeRectMin.x), ImMax(window.DC.CursorMaxPos.y, ms.ScopeRectMin.y)) + else + --// When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970) + local scope_rect = ImRect(window.InnerClipRect.Min, window.InnerClipRect.Max) + --if g.CurrentTable ~= nil then + -- scope_rect = g.CurrentTable.HostClipRect + --end + + -- Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect? + scope_rect.Min.x = ImMin(scope_rect.Min.x + window.DecoInnerSizeX1, scope_rect.Max.x) + scope_rect.Min.y = ImMin(scope_rect.Min.y + window.DecoInnerSizeY1, scope_rect.Max.y) + return scope_rect + end +end + +-- Return ImGuiMultiSelectIO structure. +-- Lifetime: don't hold on ImGuiMultiSelectIO references over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +-- Passing 'selection_size' and 'items_count' parameters is currently optional. +-- - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags.ClearOnEscape won't claim Escape key when selection_size 0, +-- allowing a first press to clear selection THEN the second press to leave child window and return to parent. +-- - 'items_count' is stored in ImGuiMultiSelectIO which makes it a convenient way to pass the information to your ApplyRequest() handler (but you may pass it differently). +-- - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1. +-- - If you can easily tell if your selection is empty or not, you may pass 0/1, or you may enable ImGuiMultiSelectFlags.ClearOnEscape flag dynamically. +--- @param flags ImGuiMultiSelectFlags +--- @param selection_size? int +--- @param items_count? int +--- @return ImGuiMultiSelectIO +function ImGui.BeginMultiSelect(flags, selection_size, items_count) + if selection_size == nil then selection_size = -1 end + if items_count == nil then items_count = -1 end + local g = GImGui + local window = g.CurrentWindow + + g.MultiSelectTempDataStacked = g.MultiSelectTempDataStacked + 1 + while g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size do -- [Port] resize(n, ImGuiMultiSelectTempData()) would share one instance + g.MultiSelectTempData:push_back(ImGuiMultiSelectTempData()) + end + local ms = g.MultiSelectTempData.Data[g.MultiSelectTempDataStacked] + g.CurrentMultiSelect = ms + if bit32.band(flags, bit32.bor(2048 --[[ImGuiMultiSelectFlags.ScopeWindow]], 4096 --[[ImGuiMultiSelectFlags.ScopeRect]])) == 0 then + flags = bit32.bor(flags, 2048 --[[ImGuiMultiSelectFlags.ScopeWindow]]) + end + if bit32.band(flags, 1 --[[ImGuiMultiSelectFlags.SingleSelect]]) ~= 0 then + flags = bit32.band(flags, bit32.bnot(bit32.bor(128 --[[ImGuiMultiSelectFlags.BoxSelect2d]], 64 --[[ImGuiMultiSelectFlags.BoxSelect1d]]))) + end + if bit32.band(flags, 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]]) ~= 0 then + flags = bit32.band(flags, bit32.bnot(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]])) + end + + -- FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250) + -- They should perhaps be stacked properly? + local table_ = g.CurrentTable + if table_ then + if not table_.IsLayoutLocked then + ImGui.TableUpdateLayout(table_) + elseif table_.CurrentColumn ~= -1 then + ImGui.TableEndCell(table_) -- This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it. + end + end + + -- FIXME: BeginFocusScope() + local id = window.IDStack.Data[window.IDStack.Size] -- window.IDStack:back() + ms:Clear() + ms.FocusScopeId = id + ms.Flags = flags + ImVec2_Copy(ms.BackupCursorMaxPos, window.DC.CursorMaxPos) + ImVec2_Copy(ms.ScopeRectMin, window.DC.CursorPos) + if bit32.band(flags, 4096 --[[ImGuiMultiSelectFlags.ScopeRect]]) ~= 0 then + ImVec2_Copy(window.DC.CursorMaxPos, ms.ScopeRectMin) -- CalcScopeRect() for ImGuiMultiSelectFlags.ScopeRect will measure in EndMultiSelect(). + end + ImGui.PushFocusScope(ms.FocusScopeId) + ms.IsFocused = ImGui.IsInNavFocusRoute(g.CurrentFocusScopeId) + if bit32.band(flags, 2048 --[[ImGuiMultiSelectFlags.ScopeWindow]]) ~= 0 then -- Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items. + window.DC.NavLayersActiveMask = bit32.bor(window.DC.NavLayersActiveMask, bit32.lshift(1, 0 --[[ImGuiNavLayer.Main]])) + end + + -- Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame. + if g.NavJustMovedToId ~= 0 then + ms.KeyMods = g.NavJustMovedToIsTabbing and 0 or g.NavJustMovedToKeyMods + else + ms.KeyMods = g.IO.KeyMods + end + if bit32.band(flags, 4 --[[ImGuiMultiSelectFlags.NoRangeSelect]]) ~= 0 then + ms.KeyMods = bit32.band(ms.KeyMods, bit32.bnot(8192 --[[ImGuiMod_Shift]])) + end + + -- Bind storage + local storage = g.MultiSelectStorage:GetOrAddByKey(id) + storage.ID = id + storage.LastFrameActive = g.FrameCount + storage.LastSelectionSize = selection_size + storage.Window = window + ms.Storage = storage + + -- Output to user + ms.IO.Requests:resize(0) + ms.IO.RangeSrcItem = storage.RangeSrcItem + ms.IO.NavIdItem = storage.NavIdItem + ms.IO.NavIdSelected = (storage.NavIdSelected == 1) + ms.IO.ItemsCount = items_count + + -- Clear when using Navigation to move within the scope + -- (we compare FocusScopeId so it possible to use multiple selections inside a same window) + local request_clear = false + local request_select_all = false + if g.NavJustMovedToId ~= 0 and g.NavJustMovedToFocusScopeId == ms.FocusScopeId and g.NavJustMovedToHasSelectionData then + if bit32.band(ms.KeyMods, 8192 --[[ImGuiMod_Shift]]) ~= 0 then + ms.IsKeyboardSetRange = true + end + if ms.IsKeyboardSetRange then + assert(storage.RangeSrcItem ~= (-1) --[[ImGuiSelectionUserData_Invalid]]) -- Not ready -> could clear? + end + if bit32.band(ms.KeyMods, bit32.bor(4096 --[[ImGuiMod_Ctrl]], 8192 --[[ImGuiMod_Shift]])) == 0 and bit32.band(flags, bit32.bor(16 --[[ImGuiMultiSelectFlags.NoAutoClear]], 8 --[[ImGuiMultiSelectFlags.NoAutoSelect]])) == 0 then + request_clear = true + end + elseif g.NavJustMovedFromFocusScopeId == ms.FocusScopeId then + -- Also clear on leaving scope (may be optional?) + if bit32.band(ms.KeyMods, bit32.bor(4096 --[[ImGuiMod_Ctrl]], 8192 --[[ImGuiMod_Shift]])) == 0 and bit32.band(flags, bit32.bor(16 --[[ImGuiMultiSelectFlags.NoAutoClear]], 8 --[[ImGuiMultiSelectFlags.NoAutoSelect]])) == 0 then + request_clear = true + end + end + + -- Box-select handling: update active state. + local bs = g.BoxSelectState + if bit32.band(flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 then + ms.BoxSelectId = ImGui.GetID("##BoxSelect") + if ImGui.BeginBoxSelect(CalcScopeRect(ms, window), window, ms.BoxSelectId, flags) then + request_clear = request_clear or bs.RequestClear + end + end + + if ms.IsFocused then + -- Shortcut: Clear selection (Escape) + -- - Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window. + -- - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock. + if bit32.band(flags, 512 --[[ImGuiMultiSelectFlags.ClearOnEscape]]) ~= 0 then + if selection_size ~= 0 or bs.IsActive then + if ImGui.Shortcut(526 --[[ImGuiKey.Escape]], 0 --[[ImGuiInputFlags.None]], bs.IsActive and bs.ID or 0) then + request_clear = true + if bs.IsActive then + BoxSelectDeactivateDrag(bs) + end + end + end + end + + -- Shortcut: Select all (Ctrl+A) + if bit32.band(flags, 1 --[[ImGuiMultiSelectFlags.SingleSelect]]) == 0 and bit32.band(flags, 2 --[[ImGuiMultiSelectFlags.NoSelectAll]]) == 0 then + if ImGui.Shortcut(bit32.bor(4096 --[[ImGuiMod_Ctrl]], 546 --[[ImGuiKey.A]])) then + request_select_all = true + end + end + end + + if request_clear or request_select_all then + ImGui.MultiSelectAddSetAll(ms, request_select_all) + if not request_select_all then + storage.LastSelectionSize = 0 + end + end + ms.LoopRequestSetAll = request_select_all and 1 or (request_clear and 0 or -1) + ms.IsSoleOrUnknownSelectionSize = (storage.LastSelectionSize == 1) or (storage.LastSelectionSize == -1) + --ms.PrevSubmittedItem = ImGuiSelectionUserData_Invalid + + if bit32.band(g.DebugLogFlags, 64 --[[ImGuiDebugLogFlags.EventSelection]]) ~= 0 then + DebugLogMultiSelectRequests("BeginMultiSelect", ms.IO) + end + + return ms.IO +end + +-- Return updated ImGuiMultiSelectIO structure. +-- Lifetime: don't hold on ImGuiMultiSelectIO references over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +--- @return ImGuiMultiSelectIO +function ImGui.EndMultiSelect() + local g = GImGui + local ms = g.CurrentMultiSelect + local storage = ms.Storage + local window = g.CurrentWindow + IM_ASSERT_USER_ERROR(ms.FocusScopeId == g.CurrentFocusScopeId, "EndMultiSelect() FocusScope mismatch!") + assert(g.CurrentMultiSelect ~= nil and storage.Window == g.CurrentWindow) + assert(g.MultiSelectTempDataStacked > 0 and g.MultiSelectTempData.Data[g.MultiSelectTempDataStacked] == g.CurrentMultiSelect) + + local scope_rect = CalcScopeRect(ms, window) + if ms.IsFocused then + -- We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here. + if ms.IO.RangeSrcReset or (ms.RangeSrcPassedBy == false and ms.IO.RangeSrcItem ~= (-1) --[[ImGuiSelectionUserData_Invalid]]) then -- Can't read storage.RangeSrcItem here -> we want the state at beginning of the scope (see tests for easy failure) + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n") -- Will set be to NavId. + storage.RangeSrcItem = (-1) --[[ImGuiSelectionUserData_Invalid]] + end + if ms.NavIdPassedBy == false and storage.NavIdItem ~= (-1) --[[ImGuiSelectionUserData_Invalid]] then + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset NavIdItem.\n") + storage.NavIdItem = (-1) --[[ImGuiSelectionUserData_Invalid]] + storage.NavIdSelected = -1 + end + + if bit32.band(ms.Flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 and ImGui.GetBoxSelectState(ms.BoxSelectId) then + ImGui.EndBoxSelect(scope_rect, ms.Flags) + end + end + + if ms.IsEndIO == false then + ms.IO.Requests:resize(0) + end + + -- Clear selection when clicking void? + -- We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection! + -- The InnerRect test is necessary for non-child/decorated windows. + local scope_hovered = window.InnerRect:Contains(g.IO.MousePos) and ImGui.IsWindowHovered(1 --[[ImGuiHoveredFlags.ChildWindows]]) + if scope_hovered and bit32.band(ms.Flags, 4096 --[[ImGuiMultiSelectFlags.ScopeRect]]) ~= 0 then + scope_hovered = scope_hovered and scope_rect:Contains(g.IO.MousePos) + end + if scope_hovered and g.HoveredId == 0 and g.ActiveId == 0 then + if bit32.band(ms.Flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 then + if not g.BoxSelectState.IsActive and not g.BoxSelectState.IsStarting and g.IO.MouseClickedCount[0] == 1 then + BoxSelectPreStartDrag(ms.BoxSelectId, (-1) --[[ImGuiSelectionUserData_Invalid]]) + ImGui.FocusWindow(window, 2 --[[ImGuiFocusRequestFlags.UnlessBelowModal]]) + ImGui.SetHoveredID(ms.BoxSelectId) + if bit32.band(ms.Flags, 4096 --[[ImGuiMultiSelectFlags.ScopeRect]]) ~= 0 then + ImGui.SetNavID(0, 0 --[[ImGuiNavLayer.Main]], ms.FocusScopeId, ImRect(g.IO.MousePos.x, g.IO.MousePos.y, g.IO.MousePos.x, g.IO.MousePos.y)) -- Automatically switch FocusScope for initial click from void to box-select. + end + end + end + + if bit32.band(ms.Flags, 1024 --[[ImGuiMultiSelectFlags.ClearOnClickVoid]]) ~= 0 then + if ImGui.IsMouseReleased(0) and ImGui.IsMouseDragPastThreshold(0) == false and g.IO.KeyMods == 0 --[[ImGuiMod_None]] then + ImGui.MultiSelectAddSetAll(ms, false) + end + end + end + + -- Courtesy nav wrapping helper flag + if bit32.band(ms.Flags, 65536 --[[ImGuiMultiSelectFlags.NavWrapX]]) ~= 0 then + assert(bit32.band(ms.Flags, 2048 --[[ImGuiMultiSelectFlags.ScopeWindow]]) ~= 0) -- Only supported at window scope + ImGui.NavMoveRequestTryWrapping(ImGui.GetCurrentWindow(), 4 --[[ImGuiNavMoveFlags.WrapX]]) + end + + -- Unwind + local table_ = g.CurrentTable + if table_ then + if table_.IsInsideRow then + ImGui.TableEndRow(table_) + end + end + ImVec2_CopyV(window.DC.CursorMaxPos, ImMax(ms.BackupCursorMaxPos.x, window.DC.CursorMaxPos.x), ImMax(ms.BackupCursorMaxPos.y, window.DC.CursorMaxPos.y)) + ImGui.PopFocusScope() + + if bit32.band(g.DebugLogFlags, 64 --[[ImGuiDebugLogFlags.EventSelection]]) ~= 0 then + DebugLogMultiSelectRequests("EndMultiSelect", ms.IO) + end + + ms.FocusScopeId = 0 + ms.Flags = 0 --[[ImGuiMultiSelectFlags.None]] + g.MultiSelectTempDataStacked = g.MultiSelectTempDataStacked - 1 + if g.MultiSelectTempDataStacked > 0 then + g.CurrentMultiSelect = g.MultiSelectTempData.Data[g.MultiSelectTempDataStacked] + else + g.CurrentMultiSelect = nil + end + + return ms.IO +end + +--- @param selection_user_data ImGuiSelectionUserData +function ImGui.SetNextItemSelectionUserData(selection_user_data) + -- Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code! + -- This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api. + local g = GImGui + local next_item_data = g.NextItemData + next_item_data.SelectionUserData = selection_user_data + next_item_data.FocusScopeId = g.CurrentFocusScopeId + + local ms = g.CurrentMultiSelect + if ms then + -- Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping) + next_item_data.ItemFlagsSet = bit32.bor(next_item_data.ItemFlagsSet, 2097152 --[[ImGuiItemFlags.HasSelectionUserData]], 4194304 --[[ImGuiItemFlags.IsMultiSelect]]) + if ms.IO.RangeSrcItem == selection_user_data then + ms.RangeSrcPassedBy = true + end + --ms.PrevSubmittedItem = ms.CurrSubmittedItem -- Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select. + --ms.CurrSubmittedItem = selection_user_data + else + next_item_data.ItemFlagsSet = bit32.bor(next_item_data.ItemFlagsSet, 2097152 --[[ImGuiItemFlags.HasSelectionUserData]]) + end +end + +-- In charge of: +-- - Applying SetAll for submitted items. +-- - Applying SetRange for submitted items and record end points. +-- - Altering button behavior flags to facilitate use with drag and drop. +-- [Port] out-params as return values: `selected, button_flags = MultiSelectItemHeader(id, selected, button_flags)` (button_flags may be nil: C++ p_button_flags == NULL) +--- @param id ImGuiID +--- @param selected bool +--- @param button_flags ImGuiButtonFlags? +--- @return bool, ImGuiButtonFlags? +function ImGui.MultiSelectItemHeader(id, selected, button_flags) + local g = GImGui + local ms = g.CurrentMultiSelect + + if ms.IsFocused then + local storage = ms.Storage + local item_data = g.NextItemData.SelectionUserData + assert(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId, "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope") + + -- Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect(). + -- This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper. + -- If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect() + if ms.LoopRequestSetAll ~= -1 then + selected = (ms.LoopRequestSetAll == 1) + end + + -- When using Shift+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection) + -- For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function) + if ms.IsKeyboardSetRange then + assert(id ~= 0 and bit32.band(ms.KeyMods, 8192 --[[ImGuiMod_Shift]]) ~= 0) + local is_range_dst = (ms.RangeDstPassedBy == false) and g.NavJustMovedToId == id -- Assume that g.NavJustMovedToId is not clipped. + if is_range_dst then + ms.RangeDstPassedBy = true + end + if is_range_dst and storage.RangeSrcItem == (-1) --[[ImGuiSelectionUserData_Invalid]] then -- If we don't have RangeSrc, assign RangeSrc = RangeDst + storage.RangeSrcItem = item_data + storage.RangeSelected = selected and 1 or 0 + end + local is_range_src = storage.RangeSrcItem == item_data + if is_range_src or is_range_dst or ms.RangeSrcPassedBy ~= ms.RangeDstPassedBy then + -- Apply range-select value to visible items + assert(storage.RangeSrcItem ~= (-1) --[[ImGuiSelectionUserData_Invalid]] and storage.RangeSelected ~= -1) + selected = (storage.RangeSelected ~= 0) + elseif bit32.band(ms.KeyMods, 4096 --[[ImGuiMod_Ctrl]]) == 0 and bit32.band(ms.Flags, 16 --[[ImGuiMultiSelectFlags.NoAutoClear]]) == 0 then + -- Clear other items + selected = false + end + end + end + + -- Alter button behavior flags + -- To handle drag and drop of multiple items we need to avoid clearing selection on click. + -- Enabling this test makes actions using Ctrl+Shift delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items. + if button_flags ~= nil then + button_flags = bit32.bor(button_flags, 524288 --[[ImGuiButtonFlags.NoHoveredOnFocus]]) + button_flags = bit32.band(button_flags, bit32.bnot(bit32.bor(16 --[[ImGuiButtonFlags.PressedOnClick]], 32 --[[ImGuiButtonFlags.PressedOnClickRelease]]))) + if bit32.band(ms.Flags, 16384 --[[ImGuiMultiSelectFlags.SelectOnClickAlways]]) ~= 0 then + button_flags = bit32.bor(button_flags, 16 --[[ImGuiButtonFlags.PressedOnClick]]) + elseif bit32.band(ms.Flags, 32768 --[[ImGuiMultiSelectFlags.SelectOnClickRelease]]) ~= 0 then + button_flags = bit32.bor(button_flags, 32 --[[ImGuiButtonFlags.PressedOnClickRelease]]) + else -- ImGuiMultiSelectFlags.SelectOnAuto + button_flags = bit32.bor(button_flags, (not selected or (g.ActiveId == id and g.ActiveIdHasBeenPressedBefore)) and 16 --[[ImGuiButtonFlags.PressedOnClick]] or 32 --[[ImGuiButtonFlags.PressedOnClickRelease]]) + end + end + return selected, button_flags +end + +-- In charge of: +-- - Auto-select on navigation. +-- - Box-select toggle handling. +-- - Right-click handling. +-- - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse. +-- - Record current selection state for RangeSrc +-- This is all rather complex, best to run and refer to "widgets_multiselect_xxx" tests in imgui_test_suite. +-- [Port] out-params as return values: `selected, pressed = MultiSelectItemFooter(id, selected, pressed, extra_flags)` +--- @param id ImGuiID +--- @param selected bool +--- @param pressed bool +--- @param extra_flags? ImGuiMultiSelectFlags +--- @return bool, bool +function ImGui.MultiSelectItemFooter(id, selected, pressed, extra_flags) + if extra_flags == nil then extra_flags = 0 end + local g = GImGui + local window = g.CurrentWindow + + local ms = g.CurrentMultiSelect + local storage = ms.Storage + if pressed then + ms.IsFocused = true + end + + local hovered = false + if bit32.band(g.LastItemData.StatusFlags, 1 --[[ImGuiItemStatusFlags.HoveredRect]]) ~= 0 then + hovered = ImGui.IsItemHovered(32 --[[ImGuiHoveredFlags.AllowWhenBlockedByPopup]]) + end + if not ms.IsFocused and not hovered then + return selected, pressed + end + + local item_data = g.NextItemData.SelectionUserData + + local flags = bit32.bor(ms.Flags, extra_flags) + local is_singleselect = bit32.band(flags, 1 --[[ImGuiMultiSelectFlags.SingleSelect]]) ~= 0 + local is_ctrl = bit32.band(ms.KeyMods, 4096 --[[ImGuiMod_Ctrl]]) ~= 0 + local is_shift = bit32.band(ms.KeyMods, 8192 --[[ImGuiMod_Shift]]) ~= 0 + + local apply_to_range_src = false + + if g.NavId == id and storage.RangeSrcItem == (-1) --[[ImGuiSelectionUserData_Invalid]] then + apply_to_range_src = true + end + if ms.IsEndIO == false then + ms.IO.Requests:resize(0) + ms.IsEndIO = true + end + + -- Auto-select as you navigate a list + if g.NavJustMovedToId == id then + if bit32.band(flags, 8 --[[ImGuiMultiSelectFlags.NoAutoSelect]]) == 0 then + if is_ctrl and is_shift then + pressed = true + elseif not is_ctrl then + selected = true; pressed = true + end + else + -- With NoAutoSelect, using Shift+keyboard performs a write/copy + if is_shift then + pressed = true + elseif not is_ctrl then + apply_to_range_src = true -- Since if (pressed) {} main block is not running we update this + end + end + end + + if apply_to_range_src then + storage.RangeSrcItem = item_data + storage.RangeSelected = selected and 1 or 0 -- Will be updated at the end of this function anyway. + end + + -- Box-select toggle handling + if ms.BoxSelectId ~= 0 then + local bs = ImGui.GetBoxSelectState(ms.BoxSelectId) + if bs then + local item_rect = MultiSelectScratchItemRect + ImRect_Copy(item_rect, g.LastItemData.Rect) + if not window.DC.NavIsScrollPushableX then -- FIXME: Rename to be more generic. + local table_ = g.CurrentTable + if table_ and table_.CurrentColumn ~= -1 then + -- FIXME: We cannot solely use current ClipRect as it includes HostClipRect. + -- However we account for ClipRect being larger than current column (e.g. when using SpanAllColumns) + -- A more generic version would be nice, but window.WorkRect.Min/Max exclude CellPadding. (#7994, #9383) + local column = table_.Columns[table_.CurrentColumn] + -- [Port] upstream tests ItemFlags against ImGuiItemStatusFlags_HasClipRect (sic), reproduced as-is. + local has_clip_rect = bit32.band(g.LastItemData.ItemFlags, 512 --[[ImGuiItemStatusFlags.HasClipRect]]) ~= 0 + local clip_min_x = has_clip_rect and g.LastItemData.ClipRect.Min.x or window.ClipRect.Min.x + local clip_max_x = has_clip_rect and g.LastItemData.ClipRect.Max.x or window.ClipRect.Max.x + if clip_min_x ~= clip_max_x then -- When zero sized we expect that bounds have been clamped and thus are unreliable + item_rect.Min.x = ImMax(item_rect.Min.x, ImMin(column.MinX, clip_min_x)) + item_rect.Max.x = ImMin(item_rect.Max.x, ImMax(column.MaxX, clip_max_x)) + else + item_rect.Min.x = ImMax(item_rect.Min.x, column.MinX) + item_rect.Max.x = ImMin(item_rect.Max.x, column.MaxX) + end + --GetForegroundDrawList():AddRect(item_rect.Min, item_rect.Max, IM_COL32(255, 0, 255, 255)) + end + end + local rect_overlap_curr = bs.BoxSelectRectCurr:Overlaps(item_rect) + local rect_overlap_prev = bs.BoxSelectRectPrev:Overlaps(item_rect) + if (rect_overlap_curr and not rect_overlap_prev and not selected) or (rect_overlap_prev and not rect_overlap_curr) then + if storage.LastSelectionSize <= 0 and bs.IsStartedSetNavIdOnce then + pressed = true -- First item act as a pressed: code below will emit selection request and set NavId (whatever we emit here will be overridden anyway) + bs.IsStartedSetNavIdOnce = false + else + selected = not selected + ImGui.MultiSelectAddSetRange(ms, selected, 1, item_data, item_data) + end + storage.LastSelectionSize = ImMax(storage.LastSelectionSize + 1, 1) + end + end + end + + -- Right-click handling. + -- FIXME-MULTISELECT: Maybe should be moved to Selectable()? Also see #5816, #8200, #9015 + if hovered and ImGui.IsMouseClicked(1) and bit32.band(flags, bit32.bor(8 --[[ImGuiMultiSelectFlags.NoAutoSelect]], 131072 --[[ImGuiMultiSelectFlags.NoSelectOnRightClick]])) == 0 then + if g.ActiveId ~= 0 and g.ActiveId ~= id then + ImGui.ClearActiveID() + end + ImGui.SetFocusID(id, window) + if not pressed and not selected then + pressed = true + is_ctrl = false; is_shift = false + end + end + + -- Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected. + -- The later, "unless current item is not select", may become optional? It seems like a better default if Enter doesn't necessarily open something + -- (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional? + local enter_pressed = pressed and (g.NavActivateId == id) and bit32.band(g.NavActivateFlags, 1 --[[ImGuiActivateFlags.PreferInput]]) ~= 0 + + -- Alter selection + if pressed and (not enter_pressed or not selected) then + -- Box-select + local input_source = (g.NavJustMovedToId == id or g.NavActivateId == id) and g.NavInputSource or 1 --[[ImGuiInputSource.Mouse]] + if bit32.band(flags, bit32.bor(64 --[[ImGuiMultiSelectFlags.BoxSelect1d]], 128 --[[ImGuiMultiSelectFlags.BoxSelect2d]])) ~= 0 then + if not g.BoxSelectState.IsActive and not g.BoxSelectState.IsStarting and input_source == 1 --[[ImGuiInputSource.Mouse]] and g.IO.MouseClickedCount[0] == 1 then + BoxSelectPreStartDrag(ms.BoxSelectId, item_data) + end + end + + ------------------------------------------------------------------------------------------ + -- ACTION | Begin | Pressed/Activated | End + ------------------------------------------------------------------------------------------ + -- Keys Navigated: | Clear | Src=item, Sel=1 SetRange 1 + -- Keys Navigated: Ctrl | n/a | n/a + -- Keys Navigated: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + -- Keys Navigated: Ctrl+Shift | n/a | Dst=item, Sel=Src => Clear + SetRange Src-Dst + -- Keys Activated: | n/a | Src=item, Sel=1 => Clear + SetRange 1 + -- Keys Activated: Ctrl | n/a | Src=item, Sel=!Sel => SetSange 1 + -- Keys Activated: Shift | n/a | Dst=item, Sel=1 => Clear + SetSange 1 + ------------------------------------------------------------------------------------------ + -- Mouse Pressed: | n/a | Src=item, Sel=1, => Clear + SetRange 1 + -- Mouse Pressed: Ctrl | n/a | Src=item, Sel=!Sel => SetRange 1 + -- Mouse Pressed: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + -- Mouse Pressed: Ctrl+Shift | n/a | Dst=item, Sel=!Sel => SetRange Src-Dst + ------------------------------------------------------------------------------------------ + + if bit32.band(flags, 16 --[[ImGuiMultiSelectFlags.NoAutoClear]]) == 0 then + local request_clear = false + if is_singleselect then + request_clear = true + elseif (input_source == 1 --[[ImGuiInputSource.Mouse]] or g.NavActivateId == id) and not is_ctrl then + if bit32.band(flags, 32 --[[ImGuiMultiSelectFlags.NoAutoClearOnReselect]]) ~= 0 then request_clear = not selected else request_clear = true end + elseif (input_source == 2 --[[ImGuiInputSource.Keyboard]] or input_source == 3 --[[ImGuiInputSource.Gamepad]]) and is_shift and not is_ctrl then + request_clear = true -- With is_shift==false the RequestClear was done in BeginIO, not necessary to do again. + end + if request_clear then + ImGui.MultiSelectAddSetAll(ms, false) + end + end + + local range_direction + local range_selected + if is_shift and not is_singleselect then + --IM_ASSERT(storage.HasRangeSrc and storage.HasRangeValue) + if storage.RangeSrcItem == (-1) --[[ImGuiSelectionUserData_Invalid]] then + storage.RangeSrcItem = item_data + end + if bit32.band(flags, 1048576 --[[ImGuiMultiSelectFlags.CheckboxMode_]]) == 0 then + -- Shift+Arrow always select + -- Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage.RangeSelected) + if is_ctrl and storage.RangeSelected ~= -1 then range_selected = (storage.RangeSelected ~= 0) else range_selected = true end + else + -- Shift+Arrow copy source selection state + -- Shift+Click always copy from target selection state + if ms.IsKeyboardSetRange then + if storage.RangeSelected ~= -1 then range_selected = (storage.RangeSelected ~= 0) else range_selected = true end + else + range_selected = not selected + end + end + range_direction = ms.RangeSrcPassedBy and 1 or -1 + else + -- Ctrl inverts selection, otherwise always select + if bit32.band(flags, 1048576 --[[ImGuiMultiSelectFlags.CheckboxMode_]]) == 0 then + if is_ctrl then selected = not selected else selected = true end + else + selected = not selected + end + storage.RangeSrcItem = item_data + range_selected = selected + range_direction = 1 + end + ImGui.MultiSelectAddSetRange(ms, range_selected, range_direction, storage.RangeSrcItem, item_data) + end + + -- Update/store the selection state of the Source item (used by Ctrl+Shift, when Source is unselected we perform a range unselect) + if storage.RangeSrcItem == item_data then + storage.RangeSelected = selected and 1 or 0 + end + + -- Update/store the selection state of focused item + if g.NavId == id then + storage.NavIdItem = item_data + storage.NavIdSelected = selected and 1 or 0 + end + if storage.NavIdItem == item_data then + ms.NavIdPassedBy = true + end + + return selected, pressed +end + +--- @param ms ImGuiMultiSelectTempData +--- @param selected bool +function ImGui.MultiSelectAddSetAll(ms, selected) + local req = MultiSelectScratchRequest -- ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid } + req.Type = 1 --[[ImGuiSelectionRequestType.SetAll]]; req.Selected = selected; req.RangeDirection = 0; req.RangeFirstItem = (-1) --[[ImGuiSelectionUserData_Invalid]]; req.RangeLastItem = (-1) --[[ImGuiSelectionUserData_Invalid]] + ms.IO.Requests:resize(0) -- Can always clear previous requests + ms.IO.Requests:push_back(req) -- Add new request +end + +--- @param ms ImGuiMultiSelectTempData +--- @param selected bool +--- @param range_dir int +--- @param first_item ImGuiSelectionUserData +--- @param last_item ImGuiSelectionUserData +function ImGui.MultiSelectAddSetRange(ms, selected, range_dir, first_item, last_item) + -- Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges) + -- FIXME-OPT: Disabled on 2026/04/09 as this would break with any form of coarse clipping that we don't know about (e.g. TableNextColumn() return value). + -- The low-hanging fruit would be to know that ImGuiSelectionUserData are sequential indices, in which case we can trivially compare PrevSubmittedItem + RangeDir == FirstItem. + -- User can always perform this merge if required. + -- (#if 0 block not ported) + + local req = MultiSelectScratchRequest -- ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item } + req.Type = 2 --[[ImGuiSelectionRequestType.SetRange]]; req.Selected = selected; req.RangeDirection = range_dir + if range_dir > 0 then + req.RangeFirstItem = first_item; req.RangeLastItem = last_item + else + req.RangeFirstItem = last_item; req.RangeLastItem = first_item + end + ms.IO.Requests:push_back(req) -- Add new request +end + +--- @param storage ImGuiMultiSelectState +function ImGui.DebugNodeMultiSelectState(storage) + local is_active = (storage.LastFrameActive >= ImGui.GetFrameCount() - 2) -- Note that fully clipped early out scrolling tables will appear as inactive here. + if not is_active then ImGui.PushStyleColor(0 --[[ImGuiCol.Text]], ImGui.GetStyleColorVec4(1 --[[ImGuiCol.TextDisabled]])) end + local open = ImGui.TreeNode(storage.ID, "MultiSelect 0x%08X in '%s'%s", storage.ID, storage.Window and storage.Window.Name or "N/A", is_active and "" or " *Inactive*") + if not is_active then ImGui.PopStyleColor() end + if not open then + return + end + -- [Port] ImGuiSelectionUserData may be any Lua value: printed with tostring() instead of %lld (0x%llX) + ImGui.Text("RangeSrcItem = %s, RangeSelected = %d", tostring(storage.RangeSrcItem), storage.RangeSelected) + ImGui.Text("NavIdItem = %s, NavIdSelected = %d", tostring(storage.NavIdItem), storage.NavIdSelected) + ImGui.Text("LastSelectionSize = %d", storage.LastSelectionSize) -- Provided by user + ImGui.TreePop() +end + +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Multi-Select helpers +------------------------------------------------------------------------------- +-- - ImGuiSelectionBasicStorage +-- - ImGuiSelectionExternalStorage +------------------------------------------------------------------------------- +-- [Port] Storage representation: +-- - Upstream _Storage is an ImGuiStorage (vector of {ImGuiID key, int val_i} pairs sorted by key, val_i = selection order +-- or 0 when unselected). Here _Storage is a plain Lua table: [id] = selection order (always > 0); unselected ids are +-- removed (nil) instead of being kept with val_i == 0. Contains()/SetItemSelected() are O(1). +-- - Iteration: `local it, id = nil, 0; local has_more; while true do has_more, it, id = selection:GetNextSelectedItem(it) if not has_more then break end ... end` +-- (C++: void* it = NULL; ImGuiID id; while (selection.GetNextSelectedItem(&it, &id)) {...}). The opaque iterator is an +-- integer position into _IterKeys, a snapshot of the selected ids sorted by key (or by selection order when +-- PreserveOrder is set), rebuilt when an iteration starts (it == nil). Ids unselected during an iteration are skipped. +-- A storage supports one iteration at a time (starting a new one rebuilds the shared snapshot). +-- - Keys returned by AdapterIndexToStorageId must be mutually comparable with '<' (numbers by default) for the sort. + +--- @class ImGuiSelectionBasicStorage +--- @field Size int # Number of selected items, maintained by this helper. +--- @field PreserveOrder bool # = false // GetNextSelectedItem() will return ordered selection (currently implemented by two additional sorts of selection. Could be improved) +--- @field UserData any # = nil // User data for use by adapter function // e.g. selection.UserData = my_items +--- @field AdapterIndexToStorageId fun(self: ImGuiSelectionBasicStorage, idx: int): ImGuiID # e.g. selection.AdapterIndexToStorageId = function(self, idx) return self.UserData[idx + 1].ID end +--- @field _SelectionOrder int # [Internal] Increasing counter to store selection order +--- @field _Storage table # [Internal] Selection set. Think of this as similar to e.g. std::set. Prefer not accessing directly: iterate with GetNextSelectedItem(). +MT.ImGuiSelectionBasicStorage = {} +MT.ImGuiSelectionBasicStorage.__index = MT.ImGuiSelectionBasicStorage + +local function ImGuiSelectionBasicStorage_DefaultAdapterIndexToStorageId(_, idx) return idx end + +--- @return ImGuiSelectionBasicStorage +function ImGuiSelectionBasicStorage() + local this = setmetatable({}, MT.ImGuiSelectionBasicStorage) + this.Size = 0 + this.PreserveOrder = false + this.UserData = nil + this.AdapterIndexToStorageId = ImGuiSelectionBasicStorage_DefaultAdapterIndexToStorageId -- [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; } + this._SelectionOrder = 1 -- Always >0 + this._Storage = {} + this._IterKeys = {} -- [Port] GetNextSelectedItem() snapshot + return this +end + +function MT.ImGuiSelectionBasicStorage:Clear() + self.Size = 0 + self._SelectionOrder = 1 -- Always >0 + table.clear(self._Storage) +end + +--- @param r ImGuiSelectionBasicStorage +function MT.ImGuiSelectionBasicStorage:Swap(r) + self.Size, r.Size = r.Size, self.Size + self._SelectionOrder, r._SelectionOrder = r._SelectionOrder, self._SelectionOrder + self._Storage, r._Storage = r._Storage, self._Storage +end + +--- @param id ImGuiID +--- @return bool +function MT.ImGuiSelectionBasicStorage:Contains(id) + return self._Storage[id] ~= nil +end + +local PairComparerStorage = nil +local function PairComparerByValueInt(lhs, rhs) + return PairComparerStorage[lhs] < PairComparerStorage[rhs] +end + +-- GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user. +-- (e.g. store unselected vs compact down, compact down on demand, use raw ImVector instead of ImGuiStorage...) +-- [Port] `has_more, opaque_it, out_id = selection:GetNextSelectedItem(opaque_it)`: pass nil to start, then the returned opaque_it. +--- @param opaque_it any +--- @return bool, any, ImGuiID +function MT.ImGuiSelectionBasicStorage:GetNextSelectedItem(opaque_it) + local storage = self._Storage + local keys = self._IterKeys + local it = opaque_it + if it == nil then + table.clear(keys) + local n = 0 + for id in pairs(storage) do n = n + 1; keys[n] = id end + if self.PreserveOrder then + PairComparerStorage = storage + table.sort(keys, PairComparerByValueInt) -- ~ImGuiStorage::BuildSortByValueInt() + PairComparerStorage = nil + else + table.sort(keys) -- ImGuiStorage is sorted by key + end + it = 1 + end + local it_end = #keys + 1 + assert(it >= 1 and it <= it_end) + while it < it_end and storage[keys[it]] == nil do + it = it + 1 + end + local has_more = (it ~= it_end) + if has_more then + return true, it + 1, keys[it] + end + return false, it, 0 +end + +--- @param id ImGuiID +--- @param selected bool +function MT.ImGuiSelectionBasicStorage:SetItemSelected(id, selected) + local storage = self._Storage + local p_int = storage[id] + if selected and p_int == nil then storage[id] = self._SelectionOrder; self._SelectionOrder = self._SelectionOrder + 1; self.Size = self.Size + 1 + elseif not selected and p_int ~= nil then storage[id] = nil; self.Size = self.Size - 1 end +end + +--- @param idx int +--- @return ImGuiID +function MT.ImGuiSelectionBasicStorage:GetStorageIdFromIndex(idx) return self.AdapterIndexToStorageId(self, idx) end -- Convert index to item id based on provided adapter. + +-- Optimized for batch edits (with same value of 'selected') +-- [Port] With a hash table there is nothing to defer: insert/remove in place. size_before_amends is unused. +local function ImGuiSelectionBasicStorage_BatchSetItemSelected(selection, id, selected, size_before_amends, selection_order) + local storage = selection._Storage + local is_contained = storage[id] ~= nil + if selected == is_contained then + return + end + if selected then + storage[id] = selection_order + else + storage[id] = nil + end + selection.Size = selection.Size + (selected and 1 or -1) +end + +-- [Port] Upstream sorts the ImGuiStorage vector here (BuildSortByKey). The Lua table needs no sorting: GetNextSelectedItem() sorts its snapshot. +local function ImGuiSelectionBasicStorage_BatchFinish(selection, selected, size_before_amends) +end + +-- Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +-- - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen. +-- - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem. +-- - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection. +-- - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform +-- a lookup in order to have some way to iterate/interpolate between two items. +-- - A full-featured application is likely to allow search/filtering which is likely to lead to using indices +-- and constructing a view index <> object id/ptr data structure anyway. +-- WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ImGuiSelectionBasicStorage' INDIRECTION LOGIC. +-- Notice that with the simplest adapter (using indices everywhere), all functions return their parameters. +-- The most simple implementation (using indices everywhere) would look like: +-- for _, req in ms_io.Requests:iter() do +-- if req.Type == ImGuiSelectionRequestType.SetAll then Clear(); if req.Selected then for n = 0, items_count - 1 do SetItemSelected(n, true) end end end +-- if req.Type == ImGuiSelectionRequestType.SetRange then for n = req.RangeFirstItem, req.RangeLastItem do SetItemSelected(n, req.Selected) end end +-- end +--- @param ms_io ImGuiMultiSelectIO +function MT.ImGuiSelectionBasicStorage:ApplyRequests(ms_io) + -- For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional. + -- It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect(). + -- Other scheme may handle SetAll differently. + assert(ms_io.ItemsCount ~= -1, "Missing value for items_count in BeginMultiSelect() call!") + assert(self.AdapterIndexToStorageId ~= nil) + + -- This is optimized/specialized to cope with very large selections (e.g. 100k+ items) + -- - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish(). + -- - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass. + -- - A more optimal version wouldn't even use ImGuiStorage but directly a ImVector to reduce bandwidth, but this is a reasonable trade off to reuse code. + -- - There are many ways this could be better optimized. The worse case scenario being: using BoxSelect2d in a grid, box-select scrolling down while wiggling + -- left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each. + -- FIXME-OPT: For each block of consecutive SetRange request: + -- - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage. + -- - rewrite sorted storage a single time. + for _, req in ms_io.Requests:iter() do + if req.Type == 1 --[[ImGuiSelectionRequestType.SetAll]] then + self:Clear() + if req.Selected then + local size_before_amends = self.Size -- [Port] _Storage.Data.Size (0 after Clear()) + for idx = 0, ms_io.ItemsCount - 1 do + ImGuiSelectionBasicStorage_BatchSetItemSelected(self, self:GetStorageIdFromIndex(idx), req.Selected, size_before_amends, self._SelectionOrder) + self._SelectionOrder = self._SelectionOrder + 1 + end + ImGuiSelectionBasicStorage_BatchFinish(self, req.Selected, size_before_amends) + end + elseif req.Type == 2 --[[ImGuiSelectionRequestType.SetRange]] then + local selection_changes = req.RangeLastItem - req.RangeFirstItem + 1 + --local g = GImGui; IMGUI_DEBUG_LOG_SELECTION("Req %d/%d: set %d to %d\n", ms_io.Requests:index_from_ptr(req), ms_io.Requests.Size, selection_changes, req.Selected) + if selection_changes == 1 or (selection_changes < math.floor(self.Size / 100)) then + -- Multiple sorted insertion + copy likely to be faster. + -- Technically we could do a single copy with a little more work (sort sequential SetRange requests) + for idx = req.RangeFirstItem, req.RangeLastItem do + self:SetItemSelected(self:GetStorageIdFromIndex(idx), req.Selected) + end + else + -- Append insertion + single sort likely be faster. + -- Use req.RangeDirection to set order field so that Shift+Clicking from 1 to 5 is different than Shift+Clicking from 5 to 1 + local size_before_amends = self.Size + local selection_order = self._SelectionOrder + ((req.RangeDirection < 0) and selection_changes - 1 or 0) + for idx = req.RangeFirstItem, req.RangeLastItem do + ImGuiSelectionBasicStorage_BatchSetItemSelected(self, self:GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order) + selection_order = selection_order + req.RangeDirection + end + if req.Selected then + self._SelectionOrder = self._SelectionOrder + selection_changes + end + ImGuiSelectionBasicStorage_BatchFinish(self, req.Selected, size_before_amends) + end + end + end +end + +------------------------------------------------------------------------------- + +-- Optional helper to apply multi-selection requests to existing randomly accessible storage. +-- Convenient if you want to quickly wire multi-select API on e.g. an array of bool or items storing their own selection state. +--- @class ImGuiSelectionExternalStorage +--- @field UserData any # User data for use by adapter function // e.g. selection.UserData = my_items +--- @field AdapterSetItemSelected fun(self: ImGuiSelectionExternalStorage, idx: int, selected: bool) # e.g. AdapterSetItemSelected = function(self, idx, selected) self.UserData[idx + 1].Selected = selected end +MT.ImGuiSelectionExternalStorage = {} +MT.ImGuiSelectionExternalStorage.__index = MT.ImGuiSelectionExternalStorage + +--- @return ImGuiSelectionExternalStorage +function ImGuiSelectionExternalStorage() + local this = setmetatable({}, MT.ImGuiSelectionExternalStorage) + this.UserData = nil + this.AdapterSetItemSelected = nil + return this +end + +-- Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +-- We also pull 'ms_io.ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage +-- This makes no assumption about underlying storage. +--- @param ms_io ImGuiMultiSelectIO +function MT.ImGuiSelectionExternalStorage:ApplyRequests(ms_io) + assert(self.AdapterSetItemSelected) + for _, req in ms_io.Requests:iter() do + if req.Type == 1 --[[ImGuiSelectionRequestType.SetAll]] then + for idx = 0, ms_io.ItemsCount - 1 do + self.AdapterSetItemSelected(self, idx, req.Selected) + end + end + if req.Type == 2 --[[ImGuiSelectionRequestType.SetRange]] then + for idx = req.RangeFirstItem, req.RangeLastItem do + self.AdapterSetItemSelected(self, idx, req.Selected) + end + end + end +end + +end -- do (Typing-Select, Box-Select, Multi-Select) + ------------------------------------------------------------------------------- -- [SECTION] Widgets: ListBox ------------------------------------------------------------------------------- @@ -65852,6 +67512,7 @@ return { ImGuiActivateFlags = ImGuiActivateFlags, ImGuiAxis = ImGuiAxis, ImGuiBackendFlags = ImGuiBackendFlags, + ImGuiBoxSelectState = ImGuiBoxSelectState, ImGuiButtonFlags = ImGuiButtonFlags, ImGuiChildFlags = ImGuiChildFlags, ImGuiCol = ImGuiCol, @@ -65924,6 +67585,10 @@ return { ImGuiMouseButton = ImGuiMouseButton, ImGuiMouseCursor = ImGuiMouseCursor, ImGuiMouseSource = ImGuiMouseSource, + ImGuiMultiSelectFlags = ImGuiMultiSelectFlags, + ImGuiMultiSelectIO = ImGuiMultiSelectIO, + ImGuiMultiSelectState = ImGuiMultiSelectState, + ImGuiMultiSelectTempData = ImGuiMultiSelectTempData, ImGuiNavLayer = ImGuiNavLayer, ImGuiNavMoveFlags = ImGuiNavMoveFlags, ImGuiNavRenderCursorFlags = ImGuiNavRenderCursorFlags, @@ -65951,6 +67616,11 @@ return { ImGuiPopupPositionPolicy = ImGuiPopupPositionPolicy, ImGuiScrollFlags = ImGuiScrollFlags, ImGuiSelectableFlags = ImGuiSelectableFlags, + ImGuiSelectionBasicStorage = ImGuiSelectionBasicStorage, + ImGuiSelectionExternalStorage = ImGuiSelectionExternalStorage, + ImGuiSelectionRequest = ImGuiSelectionRequest, + ImGuiSelectionRequestType = ImGuiSelectionRequestType, + ImGuiSelectionRequest_Copy = ImGuiSelectionRequest_Copy, ImGuiSelectionUserData_Invalid = ImGuiSelectionUserData_Invalid, ImGuiSeparatorFlags = ImGuiSeparatorFlags, ImGuiSettingsHandler = ImGuiSettingsHandler, @@ -65983,6 +67653,8 @@ return { ImGuiTextFlags = ImGuiTextFlags, ImGuiTooltipFlags = ImGuiTooltipFlags, ImGuiTreeNodeFlags = ImGuiTreeNodeFlags, + ImGuiTypingSelectFlags = ImGuiTypingSelectFlags, + ImGuiTypingSelectState = ImGuiTypingSelectState, ImGuiViewport = ImGuiViewport, ImGuiViewportFlags = ImGuiViewportFlags, ImGuiViewportP = ImGuiViewportP, @@ -66009,6 +67681,7 @@ return { ImIsPowerOfTwo = ImIsPowerOfTwo, ImLengthSqr = ImLengthSqr, ImLerp = ImLerp, + ImLinearRemapClamp = ImLinearRemapClamp, ImLog = ImLog, ImMax = ImMax, ImMemchr = ImMemchr, @@ -66028,6 +67701,7 @@ return { ImS32 = ImS32, ImS8 = ImS8, ImSaturate = ImSaturate, + ImSign = ImSign, ImSin = ImSin, ImSqrt = ImSqrt, ImStb = ImStb, @@ -66047,6 +67721,7 @@ return { ImTextureRect = ImTextureRect, ImTextureRef = ImTextureRef, ImTextureStatus = ImTextureStatus, + ImToUpper = ImToUpper, ImTrunc = ImTrunc, ImTrunc64 = ImTrunc64, ImU16 = ImU16, diff --git a/docs/PORT_STATUS.txt b/docs/PORT_STATUS.txt index c96f3da..c177f60 100644 --- a/docs/PORT_STATUS.txt +++ b/docs/PORT_STATUS.txt @@ -12,20 +12,20 @@ Known low scores that are not missing code: data-driven port code (e.g. DemoWind polyline stroker (AddPolyline), Lua-native replacements (ImGuiStorage, ImStr*, ImFormatString -> string.format). upstream file funcs ported by size similarity stubs -imgui.cpp 786 87.4% 93.5% 74.9% 2 -imgui_widgets.cpp 308 82.8% 88.9% 78.7% 0 +imgui.cpp 786 87.4% 93.5% 75.0% 2 +imgui_widgets.cpp 308 93.8% 98.7% 78.1% 1 imgui_draw.cpp 258 75.2% 75.5% 63.1% 2 imgui_tables.cpp 113 96.5% 99.0% 74.0% 0 imgui_demo.cpp 56 82.1% 98.2% 50.8% 0 -imgui.h 110 52.7% 37.1% 56.4% 0 -imgui_internal.h 269 55.8% 58.9% 67.5% 0 -TOTAL 1900 78.9% 90.6% 69.7% 4 +imgui.h 110 53.6% 37.4% 56.7% 0 +imgui_internal.h 269 61.3% 64.9% 68.3% 0 +TOTAL 1900 81.5% 92.8% 69.9% 5 -upstream code reproduced (similarity x ported size / all upstream size): 63.2% -Public API (imgui.h, namespace ImGui) 365 / 376 97.1% -Internal API (imgui_internal.h, namespace ImGui) 405 / 443 91.4% -Enum values (imgui.h + imgui_internal.h) 978 / 1078 90.7% -Struct fields (imgui.h + imgui_internal.h) 1764 / 1926 91.6% +upstream code reproduced (similarity x ported size / all upstream size): 64.8% +Public API (imgui.h, namespace ImGui) 368 / 376 97.9% +Internal API (imgui_internal.h, namespace ImGui) 417 / 443 94.1% +Enum values (imgui.h + imgui_internal.h) 1005 / 1078 93.2% +Struct fields (imgui.h + imgui_internal.h) 1820 / 1926 94.5% Legend: [x] ported (sim %) [~] stub [ ] not ported [-] excluded (c++-only / obsolete / os-specific / #if 0 / test-engine) @@ -41,7 +41,7 @@ imgui.cpp [ ] FreeWrapper -- USER FACING STRUCTURES (ImGuiStyle, ImGuiIO, ImGuiPlatformIO) - [x] 38% ImGuiStyle::ImGuiStyle imgui_internal.lua:1460 + [x] 38% ImGuiStyle::ImGuiStyle imgui_internal.lua:1479 [x] 83% ImGuiStyle::ScaleAllSizes imgui.lua:4779 [x] 12% ImGuiIO::ImGuiIO imgui_h.lua:1181 [x] 83% ImGuiIO::AddInputCharacter imgui.lua:7983 @@ -116,11 +116,11 @@ imgui.cpp [x] 41% ImTextCountLines imgui_tables.lua:3782 -- MISC HELPERS/UTILITIES (Color functions) - [x] 73% ImAlphaBlendColors imgui.lua:14379 - [x] 41% ImGui::ColorConvertU32ToFloat4 imgui.lua:14390 - [x] 16% ImGui::ColorConvertFloat4ToU32 imgui.lua:14409 - [x] 87% ImGui::ColorConvertRGBtoHSV imgui.lua:14420 - [x] 61% ImGui::ColorConvertHSVtoRGB imgui.lua:14445 + [x] 73% ImAlphaBlendColors imgui.lua:14383 + [x] 41% ImGui::ColorConvertU32ToFloat4 imgui.lua:14394 + [x] 16% ImGui::ColorConvertFloat4ToU32 imgui.lua:14413 + [x] 87% ImGui::ColorConvertRGBtoHSV imgui.lua:14424 + [x] 61% ImGui::ColorConvertHSVtoRGB imgui.lua:14449 -- ImGuiStorage [x] 69% ImLowerBound imgui_widgets.lua:5186 @@ -141,11 +141,11 @@ imgui.cpp [ ] ImGuiStorage::SetAllInt -- ImGuiTextFilter - [x] 32% ImGuiTextFilter::ImGuiTextFilter imgui.lua:19333 - [x] 36% ImGuiTextFilter::Draw imgui.lua:19343 - [x] 62% ImGuiTextFilter::DrawWithHint imgui.lua:19350 - [x] 49% ImGuiTextFilter::Build imgui.lua:19365 - [x] 56% ImGuiTextFilter::PassFilter imgui.lua:19426 + [x] 32% ImGuiTextFilter::ImGuiTextFilter imgui.lua:19337 + [x] 36% ImGuiTextFilter::Draw imgui.lua:19347 + [x] 62% ImGuiTextFilter::DrawWithHint imgui.lua:19354 + [x] 49% ImGuiTextFilter::Build imgui.lua:19369 + [x] 56% ImGuiTextFilter::PassFilter imgui.lua:19430 -- ImGuiTextBuffer, ImGuiTextIndex [x] 5% ImGuiTextBuffer::append imgui_h.lua:2685 @@ -154,31 +154,31 @@ imgui.cpp [ ] ImGuiTextIndex::append -- ImGuiListClipper - [x] 86% GetSkipItemForListClipping imgui.lua:19464 - [x] 71% ImGuiListClipper_SortAndFuseRanges imgui.lua:19474 - [x] 89% ImGuiListClipper_SeekCursorAndSetupPrevLine imgui.lua:19498 - [x] 8% ImGuiListClipper::ImGuiListClipper imgui.lua:19531 + [x] 86% GetSkipItemForListClipping imgui.lua:19468 + [x] 71% ImGuiListClipper_SortAndFuseRanges imgui.lua:19478 + [x] 89% ImGuiListClipper_SeekCursorAndSetupPrevLine imgui.lua:19502 + [x] 8% ImGuiListClipper::ImGuiListClipper imgui.lua:19535 [-] ImGuiListClipper::~ImGuiListClipper c++-only - [x] 64% ImGuiListClipper::Begin imgui.lua:19538 - [x] 79% ImGuiListClipper::End imgui.lua:19567 - [x] 61% ImGuiListClipper::IncludeItemsByIndex imgui.lua:19593 - [x] 80% ImGuiListClipper::SeekCursorForItem imgui.lua:19604 - [x] 79% ImGuiListClipper_StepInternal imgui.lua:19611 - [x] 40% ImGuiListClipper::Step imgui.lua:19760 - [x] 92% ImGui::CalcClipRectVisibleItemsY imgui.lua:14477 + [x] 64% ImGuiListClipper::Begin imgui.lua:19542 + [x] 79% ImGuiListClipper::End imgui.lua:19571 + [x] 61% ImGuiListClipper::IncludeItemsByIndex imgui.lua:19597 + [x] 80% ImGuiListClipper::SeekCursorForItem imgui.lua:19608 + [x] 79% ImGuiListClipper_StepInternal imgui.lua:19615 + [x] 40% ImGuiListClipper::Step imgui.lua:19764 + [x] 92% ImGui::CalcClipRectVisibleItemsY imgui.lua:14481 -- STYLING - [x] 90% ImGui::GetStyle imgui.lua:14488 - [x] 71% ImGui::GetStyleColorVec4 imgui.lua:18883 - [x] 26% ImGui::GetColorU32 imgui.lua:14502 - [x] 50% ImGui::PushStyleColor imgui.lua:14540 - [x] 68% ImGui::PopStyleColor imgui.lua:14557 - [x] 74% ImGui::GetStyleVarInfo imgui.lua:14624 - [x] 56% ImGui::PushStyleVar imgui.lua:14632 - [x] 91% ImGui::PushStyleVarX imgui.lua:14651 - [x] 91% ImGui::PushStyleVarY imgui.lua:14662 - [x] 70% ImGui::PopStyleVar imgui.lua:14672 - [x] 1% ImGui::GetStyleColorName imgui.lua:19193 + [x] 90% ImGui::GetStyle imgui.lua:14492 + [x] 71% ImGui::GetStyleColorVec4 imgui.lua:18887 + [x] 26% ImGui::GetColorU32 imgui.lua:14506 + [x] 50% ImGui::PushStyleColor imgui.lua:14544 + [x] 68% ImGui::PopStyleColor imgui.lua:14561 + [x] 74% ImGui::GetStyleVarInfo imgui.lua:14628 + [x] 56% ImGui::PushStyleVar imgui.lua:14636 + [x] 91% ImGui::PushStyleVarX imgui.lua:14655 + [x] 91% ImGui::PushStyleVarY imgui.lua:14666 + [x] 70% ImGui::PopStyleVar imgui.lua:14676 + [x] 1% ImGui::GetStyleColorName imgui.lua:19197 -- RENDER HELPERS [x] 46% ImGui::FindRenderedTextEnd imgui.lua:10069 @@ -200,22 +200,22 @@ imgui.cpp [ ] ImGui::GetAllocatorFunctions [x] 87% ImGui::CreateContext imgui.lua:5580 [x] 83% ImGui::DestroyContext imgui.lua:5592 - [x] 6% ImGuiContext::ImGuiContext imgui_internal.lua:2020 + [x] 6% ImGuiContext::ImGuiContext imgui_internal.lua:2197 [-] ImGuiContext::~ImGuiContext c++-only [x] 78% ImGui::Initialize imgui.lua:5538 - [x] 41% ImGui::Shutdown imgui.lua:14296 + [x] 44% ImGui::Shutdown imgui.lua:14298 [ ] ImGui::SetContextName [ ] ImGui::AddContextHook [ ] ImGui::RemoveContextHook [ ] ImGui::CallContextHooks -- MAIN CODE (most of the code! lots of stuff, needs tidying up!) - [x] 11% ImGuiWindow::ImGuiWindow imgui_internal.lua:2605 + [x] 11% ImGuiWindow::ImGuiWindow imgui_internal.lua:2800 [-] ImGuiWindow::~ImGuiWindow c++-only [x] 74% SetCurrentWindow imgui.lua:10805 - [x] 70% ImGui::GcCompactTransientMiscBuffers imgui.lua:10826 - [x] 94% ImGui::GcCompactTransientWindowBuffers imgui.lua:10843 - [x] 96% ImGui::GcAwakeTransientWindowBuffers imgui.lua:10855 + [x] 85% ImGui::GcCompactTransientMiscBuffers imgui.lua:10826 + [x] 94% ImGui::GcCompactTransientWindowBuffers imgui.lua:10845 + [x] 96% ImGui::GcAwakeTransientWindowBuffers imgui.lua:10857 [x] 86% ImGui::SetActiveID imgui.lua:7429 [x] 100% ImGui::ClearActiveID imgui.lua:7495 [x] 98% ImGui::SetHoveredID imgui.lua:7609 @@ -229,51 +229,51 @@ imgui.cpp [x] 64% ImGui::IsClippedEx imgui.lua:7937 [x] 91% ImGui::SetLastItemData imgui.lua:7958 [x] 98% ImGui::SetLastItemDataForWindow imgui.lua:7969 - [x] 93% ImGui::SetLastItemDataForChildWindowItem imgui.lua:11283 + [x] 93% ImGui::SetLastItemDataForChildWindowItem imgui.lua:11285 [x] 95% ImGui::CalcWrapWidthForPos imgui.lua:10037 [x] 77% ImGui::MemAlloc imgui.lua:457 [x] 79% ImGui::MemFree imgui.lua:470 [ ] ImGui::DemoMarker [x] 79% ImGui::DebugAllocHook imgui.lua:485 - [x] 91% ImGui::GetClipboardText imgui.lua:14332 - [x] 100% ImGui::SetClipboardText imgui.lua:14338 - [x] 100% ImGui::GetVersion imgui.lua:19190 - [x] 57% ImGui::GetIO imgui.lua:14345 - [x] 90% ImGui::GetPlatformIO imgui.lua:14348 - [x] 86% ImGui::GetDrawData imgui.lua:14285 - [x] 100% ImGui::GetTime imgui.lua:14291 - [x] 100% ImGui::GetFrameCount imgui.lua:19182 - [x] 82% GetViewportBgFgDrawList imgui.lua:13628 - [x] 80% ImGui::GetBackgroundDrawList imgui.lua:13651 - [x] 80% ImGui::GetForegroundDrawList imgui.lua:13663 - [x] 100% ImGui::GetDrawListSharedData imgui.lua:11531 - [x] 90% ImGui::StartMouseMovingWindow imgui.lua:11460 + [x] 91% ImGui::GetClipboardText imgui.lua:14336 + [x] 100% ImGui::SetClipboardText imgui.lua:14342 + [x] 100% ImGui::GetVersion imgui.lua:19194 + [x] 57% ImGui::GetIO imgui.lua:14349 + [x] 90% ImGui::GetPlatformIO imgui.lua:14352 + [x] 86% ImGui::GetDrawData imgui.lua:14287 + [x] 100% ImGui::GetTime imgui.lua:14293 + [x] 100% ImGui::GetFrameCount imgui.lua:19186 + [x] 82% GetViewportBgFgDrawList imgui.lua:13630 + [x] 80% ImGui::GetBackgroundDrawList imgui.lua:13653 + [x] 80% ImGui::GetForegroundDrawList imgui.lua:13665 + [x] 100% ImGui::GetDrawListSharedData imgui.lua:11533 + [x] 90% ImGui::StartMouseMovingWindow imgui.lua:11462 [x] 94% ImGui::StartMouseMovingWindowOrNode imgui.lua:4527 [x] 85% ImGui::StopMouseMovingWindow imgui.lua:7400 - [x] 95% ImGui::UpdateMouseMovingWindowNewFrame imgui.lua:11492 - [x] 88% ImGui::UpdateMouseMovingWindowEndFrame imgui.lua:11589 - [x] 65% TranslateWindow imgui.lua:11660 - [x] 88% ScaleWindow imgui.lua:14900 + [x] 95% ImGui::UpdateMouseMovingWindowNewFrame imgui.lua:11494 + [x] 88% ImGui::UpdateMouseMovingWindowEndFrame imgui.lua:11591 + [x] 65% TranslateWindow imgui.lua:11662 + [x] 88% ScaleWindow imgui.lua:14904 [x] 100% IsWindowActiveAndVisible imgui.lua:9570 - [x] 93% ImGui::UpdateHoveredWindowAndCaptureFlags imgui.lua:13034 - [x] 55% SetupDrawListSharedData imgui.lua:13511 - [x] 88% ImGui::NewFrame imgui.lua:13757 - [x] 63% ChildWindowComparer imgui.lua:13677 - [x] 89% AddWindowToSortBuffer imgui.lua:13692 - [x] 86% AddWindowToDrawData imgui.lua:13706 - [x] 64% GetWindowDisplayLayer imgui.lua:13722 - [x] 100% AddRootWindowToDrawData imgui.lua:13728 - [x] 63% FlattenDrawDataIntoSingleLayer imgui.lua:13733 - [x] 83% InitViewportDrawData imgui.lua:13537 - [x] 52% ImGui::PushClipRect imgui.lua:13606 - [x] 42% ImGui::PopClipRect imgui.lua:13615 - [x] 60% ImGui::RenderDimmedBackgroundBehindWindow imgui.lua:18964 - [x] 83% ImGui::FindBottomMostVisibleWindowWithinBeginStack imgui.lua:18937 - [x] 80% ImGui::RenderDimmedBackgrounds imgui.lua:18985 - [x] 84% ImGui::EndFrame imgui.lua:14061 - [x] 74% ImGui::Render imgui.lua:14172 - [x] 17% ImGui::CalcTextSize imgui.lua:14245 - [x] 79% ImGui::FindHoveredWindowEx imgui.lua:12948 + [x] 93% ImGui::UpdateHoveredWindowAndCaptureFlags imgui.lua:13036 + [x] 55% SetupDrawListSharedData imgui.lua:13513 + [x] 88% ImGui::NewFrame imgui.lua:13759 + [x] 63% ChildWindowComparer imgui.lua:13679 + [x] 89% AddWindowToSortBuffer imgui.lua:13694 + [x] 86% AddWindowToDrawData imgui.lua:13708 + [x] 64% GetWindowDisplayLayer imgui.lua:13724 + [x] 100% AddRootWindowToDrawData imgui.lua:13730 + [x] 63% FlattenDrawDataIntoSingleLayer imgui.lua:13735 + [x] 83% InitViewportDrawData imgui.lua:13539 + [x] 52% ImGui::PushClipRect imgui.lua:13608 + [x] 42% ImGui::PopClipRect imgui.lua:13617 + [x] 60% ImGui::RenderDimmedBackgroundBehindWindow imgui.lua:18968 + [x] 83% ImGui::FindBottomMostVisibleWindowWithinBeginStack imgui.lua:18941 + [x] 80% ImGui::RenderDimmedBackgrounds imgui.lua:18989 + [x] 84% ImGui::EndFrame imgui.lua:14063 + [x] 74% ImGui::Render imgui.lua:14174 + [x] 17% ImGui::CalcTextSize imgui.lua:14247 + [x] 79% ImGui::FindHoveredWindowEx imgui.lua:12950 [x] 95% ImGui::IsItemActive imgui.lua:6979 [x] 97% ImGui::IsItemActivated imgui.lua:7020 [x] 95% ImGui::IsItemDeactivated imgui.lua:7006 @@ -288,19 +288,19 @@ imgui.cpp [x] 74% ImGui::IsItemVisible imgui.lua:7071 [x] 74% ImGui::IsItemEdited imgui.lua:7075 [x] 71% ImGui::SetNextItemAllowOverlap imgui.lua:7082 - [x] 93% ImGui::SetActiveIdUsingAllKeyboardKeys imgui.lua:11113 + [x] 93% ImGui::SetActiveIdUsingAllKeyboardKeys imgui.lua:11115 [x] 67% ImGui::GetItemID imgui.lua:7087 - [x] 100% ImGui::GetItemRectMin imgui.lua:11122 - [x] 100% ImGui::GetItemRectMax imgui.lua:11128 + [x] 100% ImGui::GetItemRectMin imgui.lua:11124 + [x] 100% ImGui::GetItemRectMax imgui.lua:11130 [x] 67% ImGui::GetItemRectSize imgui.lua:7096 [x] 67% ImGui::GetItemFlags imgui.lua:7091 - [x] 43% ImGui::BeginChild imgui.lua:11272 - [x] 81% ImGui::BeginChildEx imgui.lua:11138 - [x] 90% ImGui::EndChild imgui.lua:11288 + [x] 43% ImGui::BeginChild imgui.lua:11274 + [x] 81% ImGui::BeginChildEx imgui.lua:11140 + [x] 90% ImGui::EndChild imgui.lua:11290 [x] 83% ImGui::FindFrontMostVisibleChildWindow imgui.lua:5629 [x] 42% SetWindowConditionAllowFlags imgui.lua:5641 - [x] 73% ImGui::FindWindowByID imgui.lua:11673 - [x] 100% ImGui::FindWindowByName imgui.lua:11683 + [x] 73% ImGui::FindWindowByID imgui.lua:11675 + [x] 100% ImGui::FindWindowByName imgui.lua:11685 [x] 86% ApplyWindowSettings imgui.lua:5655 [x] 61% InitOrLoadWindowSettings imgui.lua:5694 [x] 73% CreateNewWindow imgui.lua:5715 @@ -325,66 +325,66 @@ imgui.cpp [x] 85% ImGui::UpdateWindowParentAndRootLinks imgui.lua:10711 [x] 33% ImGui::UpdateWindowSkipRefresh imgui.lua:10741 [x] 92% SetWindowActiveForSkipRefresh imgui.lua:10793 - [x] 82% ImGui::Begin imgui.lua:11700 - [x] 86% ImGui::End imgui.lua:12768 - [x] 84% ImGui::PushItemFlag imgui.lua:12857 - [x] 96% ImGui::PopItemFlag imgui.lua:12872 - [x] 79% ImGui::BeginDisabled imgui.lua:12887 - [x] 87% ImGui::EndDisabled imgui.lua:12903 - [x] 78% ImGui::BeginDisabledOverrideReenable imgui.lua:12919 - [x] 88% ImGui::EndDisabledOverrideReenable imgui.lua:12930 - [x] 54% ImGui::PushTextWrapPos imgui.lua:14697 - [x] 69% ImGui::PopTextWrapPos imgui.lua:14708 - [x] 100% GetCombinedRootWindow imgui.lua:14720 - [x] 100% ImGui::IsWindowChildOf imgui.lua:14739 - [x] 84% ImGui::IsWindowInBeginStack imgui.lua:14759 - [x] 100% ImGui::IsWindowWithinBeginStackOf imgui.lua:14771 - [x] 91% ImGui::IsWindowAbove imgui.lua:14789 - [x] 90% ImGui::IsWindowHovered imgui.lua:14815 + [x] 82% ImGui::Begin imgui.lua:11702 + [x] 86% ImGui::End imgui.lua:12770 + [x] 84% ImGui::PushItemFlag imgui.lua:12859 + [x] 96% ImGui::PopItemFlag imgui.lua:12874 + [x] 79% ImGui::BeginDisabled imgui.lua:12889 + [x] 87% ImGui::EndDisabled imgui.lua:12905 + [x] 78% ImGui::BeginDisabledOverrideReenable imgui.lua:12921 + [x] 88% ImGui::EndDisabledOverrideReenable imgui.lua:12932 + [x] 54% ImGui::PushTextWrapPos imgui.lua:14701 + [x] 69% ImGui::PopTextWrapPos imgui.lua:14712 + [x] 100% GetCombinedRootWindow imgui.lua:14724 + [x] 100% ImGui::IsWindowChildOf imgui.lua:14743 + [x] 84% ImGui::IsWindowInBeginStack imgui.lua:14763 + [x] 100% ImGui::IsWindowWithinBeginStackOf imgui.lua:14775 + [x] 91% ImGui::IsWindowAbove imgui.lua:14793 + [x] 90% ImGui::IsWindowHovered imgui.lua:14819 [x] 67% ImGui::GetWindowDockID imgui.lua:4511 [x] 67% ImGui::IsWindowDocked imgui.lua:4515 - [x] 100% ImGui::GetWindowWidth imgui.lua:10865 - [x] 74% ImGui::GetWindowHeight imgui.lua:10998 - [x] 56% ImGui::GetWindowPos imgui.lua:11003 - [x] 61% ImGui::SetWindowPos imgui.lua:10873 - [x] 55% ImGui::GetWindowSize imgui.lua:11008 - [x] 76% ImGui::SetWindowSize imgui.lua:10915 - [x] 55% ImGui::SetWindowCollapsed imgui.lua:10960 - [x] 59% ImGui::SetWindowHitTestHole imgui.lua:10984 - [x] 97% ImGui::SetWindowHiddenAndSkipItemsForCurrentFrame imgui.lua:11031 - [x] 55% ImGui::IsWindowCollapsed imgui.lua:10990 - [x] 55% ImGui::IsWindowAppearing imgui.lua:10994 - [x] 72% ImGui::SetNextWindowPos imgui.lua:11040 - [x] 74% ImGui::SetNextWindowSize imgui.lua:11056 - [x] 87% ImGui::SetNextWindowSizeConstraints imgui.lua:11075 - [x] 65% ImGui::SetNextWindowContentSize imgui.lua:11374 - [x] 74% ImGui::SetNextWindowScroll imgui.lua:11381 - [x] 77% ImGui::SetNextWindowCollapsed imgui.lua:11389 - [x] 80% ImGui::SetNextWindowBgAlpha imgui.lua:11084 - [x] 80% ImGui::SetNextWindowViewport imgui.lua:11404 + [x] 100% ImGui::GetWindowWidth imgui.lua:10867 + [x] 74% ImGui::GetWindowHeight imgui.lua:11000 + [x] 56% ImGui::GetWindowPos imgui.lua:11005 + [x] 61% ImGui::SetWindowPos imgui.lua:10875 + [x] 55% ImGui::GetWindowSize imgui.lua:11010 + [x] 76% ImGui::SetWindowSize imgui.lua:10917 + [x] 55% ImGui::SetWindowCollapsed imgui.lua:10962 + [x] 59% ImGui::SetWindowHitTestHole imgui.lua:10986 + [x] 97% ImGui::SetWindowHiddenAndSkipItemsForCurrentFrame imgui.lua:11033 + [x] 55% ImGui::IsWindowCollapsed imgui.lua:10992 + [x] 55% ImGui::IsWindowAppearing imgui.lua:10996 + [x] 72% ImGui::SetNextWindowPos imgui.lua:11042 + [x] 74% ImGui::SetNextWindowSize imgui.lua:11058 + [x] 87% ImGui::SetNextWindowSizeConstraints imgui.lua:11077 + [x] 65% ImGui::SetNextWindowContentSize imgui.lua:11376 + [x] 74% ImGui::SetNextWindowScroll imgui.lua:11383 + [x] 77% ImGui::SetNextWindowCollapsed imgui.lua:11391 + [x] 80% ImGui::SetNextWindowBgAlpha imgui.lua:11086 + [x] 80% ImGui::SetNextWindowViewport imgui.lua:11406 [x] 76% ImGui::SetNextWindowDockID imgui.lua:4519 - [x] 50% ImGui::SetNextWindowClass imgui.lua:11411 - [x] 69% ImGui::SetNextWindowRefreshPolicy imgui.lua:11421 - [x] 47% ImGui::SetNextWindowFlags imgui.lua:11355 - [x] 47% ImGui::SetNextWindowChildFlags imgui.lua:11337 - [x] 100% ImGui::GetWindowDrawList imgui.lua:11090 - [x] 22% ImGui::GetWindowDpiScale imgui.lua:11441 - [x] 42% ImGui::GetWindowViewport imgui.lua:11445 - [x] 100% ImGui::GetFont imgui.lua:11095 + [x] 50% ImGui::SetNextWindowClass imgui.lua:11413 + [x] 69% ImGui::SetNextWindowRefreshPolicy imgui.lua:11423 + [x] 47% ImGui::SetNextWindowFlags imgui.lua:11357 + [x] 47% ImGui::SetNextWindowChildFlags imgui.lua:11339 + [x] 100% ImGui::GetWindowDrawList imgui.lua:11092 + [x] 22% ImGui::GetWindowDpiScale imgui.lua:11443 + [x] 42% ImGui::GetWindowViewport imgui.lua:11447 + [x] 100% ImGui::GetFont imgui.lua:11097 [ ] ImGui::GetFontBaked - [x] 100% ImGui::GetFontSize imgui.lua:11102 - [x] 100% ImGui::GetFontTexUvWhitePixel imgui.lua:11107 + [x] 100% ImGui::GetFontSize imgui.lua:11104 + [x] 100% ImGui::GetFontTexUvWhitePixel imgui.lua:11109 [-] ImGui::SetWindowFontScale obsolete - [x] 93% ImGui::PushFocusScope imgui.lua:15905 - [x] 56% ImGui::PopFocusScope imgui.lua:15914 - [x] 91% ImGui::IsInNavFocusRoute imgui.lua:15926 - [x] 71% ImGui::SetNavFocusScope imgui.lua:15940 - [x] 76% ImGui::FocusItem imgui.lua:19153 - [x] 100% ImGui::ActivateItemByID imgui.lua:19166 - [x] 81% ImGui::SetKeyboardFocusHere imgui.lua:19125 - [x] 98% ImGui::SetItemDefaultFocus imgui.lua:15980 - [x] 91% ImGui::SetStateStorage imgui.lua:19184 - [x] 74% ImGui::GetStateStorage imgui.lua:19183 + [x] 93% ImGui::PushFocusScope imgui.lua:15909 + [x] 56% ImGui::PopFocusScope imgui.lua:15918 + [x] 91% ImGui::IsInNavFocusRoute imgui.lua:15930 + [x] 71% ImGui::SetNavFocusScope imgui.lua:15944 + [x] 76% ImGui::FocusItem imgui.lua:19157 + [x] 100% ImGui::ActivateItemByID imgui.lua:19170 + [x] 81% ImGui::SetKeyboardFocusHere imgui.lua:19129 + [x] 98% ImGui::SetItemDefaultFocus imgui.lua:15984 + [x] 91% ImGui::SetStateStorage imgui.lua:19188 + [x] 74% ImGui::GetStateStorage imgui.lua:19187 [x] 72% ImGui::IsRectVisible imgui.lua:6795 -- FONTS, TEXTURES @@ -451,17 +451,17 @@ imgui.cpp [x] 75% ImGui::IsAnyMouseDown imgui.lua:9031 [x] 87% ImGui::GetMouseDragDelta imgui.lua:9221 [x] 71% ImGui::ResetMouseDragDelta imgui.lua:9240 - [x] 100% ImGui::GetMouseCursor imgui.lua:14357 - [x] 100% ImGui::SetMouseCursor imgui.lua:14366 - [x] 92% UpdateAliasKey imgui.lua:13128 - [x] 42% GetMergedModsFromKeys imgui.lua:13136 - [x] 85% ImGui::UpdateKeyboardInputs imgui.lua:13149 - [x] 88% ImGui::UpdateMouseInputs imgui.lua:13227 - [x] 91% LockWheelingWindow imgui.lua:13335 - [x] 76% FindBestWheelingWindow imgui.lua:13356 - [x] 92% ImGui::UpdateMouseWheel imgui.lua:13406 - [x] 60% ImGui::SetNextFrameWantCaptureKeyboard imgui.lua:19188 - [x] 60% ImGui::SetNextFrameWantCaptureMouse imgui.lua:19189 + [x] 100% ImGui::GetMouseCursor imgui.lua:14361 + [x] 100% ImGui::SetMouseCursor imgui.lua:14370 + [x] 92% UpdateAliasKey imgui.lua:13130 + [x] 42% GetMergedModsFromKeys imgui.lua:13138 + [x] 85% ImGui::UpdateKeyboardInputs imgui.lua:13151 + [x] 88% ImGui::UpdateMouseInputs imgui.lua:13229 + [x] 91% LockWheelingWindow imgui.lua:13337 + [x] 76% FindBestWheelingWindow imgui.lua:13358 + [x] 92% ImGui::UpdateMouseWheel imgui.lua:13408 + [x] 60% ImGui::SetNextFrameWantCaptureKeyboard imgui.lua:19192 + [x] 60% ImGui::SetNextFrameWantCaptureMouse imgui.lua:19193 [ ] GetInputSourceName [ ] GetMouseSourceName [ ] DebugPrintInputEvent @@ -478,16 +478,16 @@ imgui.cpp -- ERROR CHECKING, STATE RECOVERY [ ] ImGui::DebugCheckVersionAndDataLayout - [x] 86% ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries imgui.lua:18900 + [x] 86% ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries imgui.lua:18904 [ ] ImGui::ErrorCheckNewFrameSanityChecks [ ] ImGui::ErrorCheckEndFrameSanityChecks - [x] 100% ImGui::ErrorRecoveryStoreState imgui.lua:19787 - [x] 91% ImGui::ErrorRecoveryTryToRecoverState imgui.lua:19805 - [x] 91% ImGui::ErrorRecoveryTryToRecoverWindowState imgui.lua:19834 - [x] 86% ImGui::ErrorLog imgui.lua:19923 - [x] 80% ImGui::ErrorCheckEndFrameFinalizeErrorTooltip imgui.lua:19967 - [x] 77% ImGui::BeginErrorTooltip imgui.lua:20005 - [x] 100% ImGui::EndErrorTooltip imgui.lua:20027 + [x] 100% ImGui::ErrorRecoveryStoreState imgui.lua:19791 + [x] 91% ImGui::ErrorRecoveryTryToRecoverState imgui.lua:19809 + [x] 91% ImGui::ErrorRecoveryTryToRecoverWindowState imgui.lua:19838 + [x] 86% ImGui::ErrorLog imgui.lua:19927 + [x] 80% ImGui::ErrorCheckEndFrameFinalizeErrorTooltip imgui.lua:19971 + [x] 77% ImGui::BeginErrorTooltip imgui.lua:20009 + [x] 100% ImGui::EndErrorTooltip imgui.lua:20031 -- ITEM SUBMISSION [x] 100% ImGui::KeepAliveID imgui.lua:6247 @@ -525,126 +525,126 @@ imgui.cpp [x] 91% ImGui::EndGroup imgui.lua:6839 -- SCROLLING - [x] 100% CalcScrollEdgeSnap imgui.lua:14939 - [x] 89% CalcNextScrollFromScrollTargetAndClamp imgui.lua:14950 - [x] 78% ImGui::ScrollToItem imgui.lua:15076 - [x] 86% ImGui::ScrollToRect imgui.lua:15084 - [x] 88% ImGui::ScrollToRectEx imgui.lua:14982 - [x] 74% ImGui::GetScrollX imgui.lua:15067 - [x] 74% ImGui::GetScrollY imgui.lua:15071 - [x] 74% ImGui::GetScrollMaxX imgui.lua:15063 - [x] 100% ImGui::GetScrollMaxY imgui.lua:15058 - [x] 75% ImGui::SetScrollX imgui.lua:15119 - [x] 75% ImGui::SetScrollY imgui.lua:15128 - [x] 78% ImGui::SetScrollFromPosX imgui.lua:15148 - [x] 78% ImGui::SetScrollFromPosY imgui.lua:15160 - [x] 95% ImGui::SetScrollHereX imgui.lua:15090 - [x] 95% ImGui::SetScrollHereY imgui.lua:15104 + [x] 100% CalcScrollEdgeSnap imgui.lua:14943 + [x] 89% CalcNextScrollFromScrollTargetAndClamp imgui.lua:14954 + [x] 78% ImGui::ScrollToItem imgui.lua:15080 + [x] 86% ImGui::ScrollToRect imgui.lua:15088 + [x] 88% ImGui::ScrollToRectEx imgui.lua:14986 + [x] 74% ImGui::GetScrollX imgui.lua:15071 + [x] 74% ImGui::GetScrollY imgui.lua:15075 + [x] 74% ImGui::GetScrollMaxX imgui.lua:15067 + [x] 100% ImGui::GetScrollMaxY imgui.lua:15062 + [x] 75% ImGui::SetScrollX imgui.lua:15123 + [x] 75% ImGui::SetScrollY imgui.lua:15132 + [x] 78% ImGui::SetScrollFromPosX imgui.lua:15152 + [x] 78% ImGui::SetScrollFromPosY imgui.lua:15164 + [x] 95% ImGui::SetScrollHereX imgui.lua:15094 + [x] 95% ImGui::SetScrollHereY imgui.lua:15108 -- TOOLTIPS - [x] 100% ImGui::BeginTooltip imgui.lua:15228 - [x] 100% ImGui::BeginItemTooltip imgui.lua:15232 - [x] 83% ImGui::BeginTooltipEx imgui.lua:15175 - [x] 73% ImGui::EndTooltip imgui.lua:15239 - [x] 21% ImGui::SetTooltip imgui.lua:15246 + [x] 100% ImGui::BeginTooltip imgui.lua:15232 + [x] 100% ImGui::BeginItemTooltip imgui.lua:15236 + [x] 83% ImGui::BeginTooltipEx imgui.lua:15179 + [x] 73% ImGui::EndTooltip imgui.lua:15243 + [x] 21% ImGui::SetTooltip imgui.lua:15250 [ ] ImGui::SetTooltipV - [x] 52% ImGui::SetItemTooltip imgui.lua:15258 + [x] 52% ImGui::SetItemTooltip imgui.lua:15262 [ ] ImGui::SetItemTooltipV -- POPUPS - [x] 73% ImGui::IsPopupOpen imgui.lua:11538 - [x] 82% ImGui::GetTopMostPopupModal imgui.lua:11573 - [x] 81% ImGui::GetTopMostAndVisiblePopupModal imgui.lua:18953 - [x] 79% ImGui::FindBlockingModal imgui.lua:15282 - [x] 26% ImGui::OpenPopup imgui.lua:15315 - [x] 86% ImGui::OpenPopupEx imgui.lua:15336 - [x] 73% ImGui::ClosePopupsOverWindow imgui.lua:15399 - [x] 80% ImGui::ClosePopupsExceptModals imgui.lua:19019 - [x] 62% ImGui::ClosePopupToLevel imgui.lua:15457 - [x] 81% ImGui::CloseCurrentPopup imgui.lua:15493 - [x] 81% ImGui::BeginPopupEx imgui.lua:15533 - [x] 80% ImGui::BeginPopupMenuEx imgui.lua:15556 - [x] 83% ImGui::BeginPopup imgui.lua:15582 - [x] 83% ImGui::BeginPopupModal imgui.lua:19037 - [x] 95% ImGui::EndPopup imgui.lua:15595 - [x] 38% ImGui::GetMouseButtonFromPopupFlags imgui.lua:15816 - [x] 94% ImGui::IsPopupOpenRequestForItem imgui.lua:15828 - [x] 85% ImGui::IsPopupOpenRequestForWindow imgui.lua:19067 - [x] 83% ImGui::OpenPopupOnItemClick imgui.lua:15845 - [x] 83% ImGui::BeginPopupContextItem imgui.lua:15879 - [x] 85% ImGui::BeginPopupContextWindow imgui.lua:19081 - [x] 88% ImGui::BeginPopupContextVoid imgui.lua:19091 - [x] 87% ImGui::FindBestWindowPosForPopupEx imgui.lua:15632 - [x] 87% ImGui::GetPopupAllowedExtentRect imgui.lua:15725 - [x] 92% ImGui::FindBestWindowPosForPopup imgui.lua:15748 + [x] 73% ImGui::IsPopupOpen imgui.lua:11540 + [x] 82% ImGui::GetTopMostPopupModal imgui.lua:11575 + [x] 81% ImGui::GetTopMostAndVisiblePopupModal imgui.lua:18957 + [x] 79% ImGui::FindBlockingModal imgui.lua:15286 + [x] 26% ImGui::OpenPopup imgui.lua:15319 + [x] 86% ImGui::OpenPopupEx imgui.lua:15340 + [x] 73% ImGui::ClosePopupsOverWindow imgui.lua:15403 + [x] 80% ImGui::ClosePopupsExceptModals imgui.lua:19023 + [x] 62% ImGui::ClosePopupToLevel imgui.lua:15461 + [x] 81% ImGui::CloseCurrentPopup imgui.lua:15497 + [x] 81% ImGui::BeginPopupEx imgui.lua:15537 + [x] 80% ImGui::BeginPopupMenuEx imgui.lua:15560 + [x] 83% ImGui::BeginPopup imgui.lua:15586 + [x] 83% ImGui::BeginPopupModal imgui.lua:19041 + [x] 95% ImGui::EndPopup imgui.lua:15599 + [x] 38% ImGui::GetMouseButtonFromPopupFlags imgui.lua:15820 + [x] 94% ImGui::IsPopupOpenRequestForItem imgui.lua:15832 + [x] 85% ImGui::IsPopupOpenRequestForWindow imgui.lua:19071 + [x] 83% ImGui::OpenPopupOnItemClick imgui.lua:15849 + [x] 83% ImGui::BeginPopupContextItem imgui.lua:15883 + [x] 85% ImGui::BeginPopupContextWindow imgui.lua:19085 + [x] 88% ImGui::BeginPopupContextVoid imgui.lua:19095 + [x] 87% ImGui::FindBestWindowPosForPopupEx imgui.lua:15636 + [x] 87% ImGui::GetPopupAllowedExtentRect imgui.lua:15729 + [x] 92% ImGui::FindBestWindowPosForPopup imgui.lua:15752 -- WINDOW FOCUS - [x] 51% ImGui::SetWindowFocus imgui.lua:11014 - [x] 71% ImGui::SetNextWindowFocus imgui.lua:11398 - [x] 87% ImGui::IsWindowFocused imgui.lua:14871 + [x] 51% ImGui::SetWindowFocus imgui.lua:11016 + [x] 71% ImGui::SetNextWindowFocus imgui.lua:11400 + [x] 87% ImGui::IsWindowFocused imgui.lua:14875 [x] 94% ImGui::FindWindowFocusIndex imgui.lua:7203 [x] 81% ImGui::UpdateWindowInFocusOrderList imgui.lua:7238 [x] 82% ImGui::BringWindowToFocusFront imgui.lua:7263 [x] 56% ImGui::BringWindowToDisplayFront imgui.lua:7289 - [x] 54% ImGui::BringWindowToDisplayBack imgui.lua:18927 + [x] 54% ImGui::BringWindowToDisplayBack imgui.lua:18931 [x] 35% ImGui::BringWindowToDisplayBehind imgui.lua:7310 - [x] 41% ImGui::FindWindowDisplayIndex imgui.lua:18921 + [x] 41% ImGui::FindWindowDisplayIndex imgui.lua:18925 [x] 87% ImGui::FocusWindow imgui.lua:7124 [x] 84% ImGui::FocusTopMostWindowUnderOne imgui.lua:7322 -- KEYBOARD/GAMEPAD NAVIGATION - [x] 93% ImGui::SetNavCursorVisible imgui.lua:19172 - [x] 94% ImGui::SetNavCursorVisibleAfterMove imgui.lua:16001 - [x] 80% ImGui::SetNavWindow imgui.lua:16017 - [x] 100% ImGui::NavHighlightActivated imgui.lua:16097 - [x] 100% ImGui::NavClearPreferredPosForAxis imgui.lua:16031 - [x] 95% ImGui::SetNavID imgui.lua:16040 + [x] 93% ImGui::SetNavCursorVisible imgui.lua:19176 + [x] 94% ImGui::SetNavCursorVisibleAfterMove imgui.lua:16005 + [x] 80% ImGui::SetNavWindow imgui.lua:16021 + [x] 100% ImGui::NavHighlightActivated imgui.lua:16101 + [x] 100% ImGui::NavClearPreferredPosForAxis imgui.lua:16035 + [x] 95% ImGui::SetNavID imgui.lua:16044 [x] 96% ImGui::SetFocusID imgui.lua:7362 - [x] 90% ImGetDirQuadrantFromDelta imgui.lua:17067 - [x] 100% NavScoreItemDistInterval imgui.lua:17074 - [x] 77% ImGui::NavScoreItem imgui.lua:17088 - [x] 93% ImGui::NavApplyItemToResult imgui.lua:17194 - [x] 100% ImGui::NavUpdateCurrentWindowIsScrollPushableX imgui.lua:17521 - [x] 94% ImGui::NavProcessItem imgui.lua:17286 - [x] 90% ImGui::NavProcessItemForTabbingRequest imgui.lua:17226 - [x] 100% ImGui::NavMoveRequestButNoResultYet imgui.lua:17062 - [x] 89% ImGui::NavMoveRequestSubmit imgui.lua:16540 - [x] 92% ImGui::NavMoveRequestResolveWithLastItem imgui.lua:17209 - [x] 92% ImGui::NavMoveRequestResolveWithPastTreeNode imgui.lua:17530 - [x] 93% ImGui::NavMoveRequestCancel imgui.lua:17541 - [x] 95% ImGui::NavMoveRequestForward imgui.lua:17553 - [x] 78% ImGui::NavMoveRequestTryWrapping imgui.lua:17568 - [x] 93% ImGui::NavSaveLastChildNavWindowIntoParent imgui.lua:16058 - [x] 57% ImGui::NavRestoreLastChildNavWindow imgui.lua:16071 - [x] 86% ImGui::NavRestoreLayer imgui.lua:16079 - [x] 100% ImGui::NavUpdateAnyRequestFlag imgui.lua:16103 - [x] 88% ImGui::NavInitWindow imgui.lua:16114 - [x] 97% ImGui::NavCalcPreferredRefPosSource imgui.lua:16146 - [x] 94% ImGui::NavCalcPreferredRefPos imgui.lua:16165 - [x] 91% ImGui::GetNavTweakPressedAmount imgui.lua:16207 - [x] 85% ImGui::NavUpdate imgui.lua:16809 - [x] 88% ImGui::NavInitRequestApplyResult imgui.lua:17360 - [x] 81% NavBiasScoringRect imgui.lua:16569 - [x] 83% ImGui::NavUpdateCreateMoveRequest imgui.lua:16670 - [x] 87% ImGui::NavUpdateCreateTabbingRequest imgui.lua:16780 - [x] 85% ImGui::NavMoveRequestApplyResult imgui.lua:17391 - [x] 91% ImGui::NavUpdateCancelRequest imgui.lua:17017 - [x] 83% ImGui::NavUpdateContextMenuRequest imgui.lua:16505 - [x] 90% ImGui::NavUpdatePageUpPageDown imgui.lua:16601 - [x] 81% ImGui::NavEndFrame imgui.lua:19307 - [x] 83% ImGui::NavUpdateCreateWrappingRequest imgui.lua:19251 + [x] 90% ImGetDirQuadrantFromDelta imgui.lua:17071 + [x] 100% NavScoreItemDistInterval imgui.lua:17078 + [x] 77% ImGui::NavScoreItem imgui.lua:17092 + [x] 93% ImGui::NavApplyItemToResult imgui.lua:17198 + [x] 100% ImGui::NavUpdateCurrentWindowIsScrollPushableX imgui.lua:17525 + [x] 94% ImGui::NavProcessItem imgui.lua:17290 + [x] 90% ImGui::NavProcessItemForTabbingRequest imgui.lua:17230 + [x] 100% ImGui::NavMoveRequestButNoResultYet imgui.lua:17066 + [x] 89% ImGui::NavMoveRequestSubmit imgui.lua:16544 + [x] 92% ImGui::NavMoveRequestResolveWithLastItem imgui.lua:17213 + [x] 92% ImGui::NavMoveRequestResolveWithPastTreeNode imgui.lua:17534 + [x] 93% ImGui::NavMoveRequestCancel imgui.lua:17545 + [x] 95% ImGui::NavMoveRequestForward imgui.lua:17557 + [x] 78% ImGui::NavMoveRequestTryWrapping imgui.lua:17572 + [x] 93% ImGui::NavSaveLastChildNavWindowIntoParent imgui.lua:16062 + [x] 57% ImGui::NavRestoreLastChildNavWindow imgui.lua:16075 + [x] 86% ImGui::NavRestoreLayer imgui.lua:16083 + [x] 100% ImGui::NavUpdateAnyRequestFlag imgui.lua:16107 + [x] 88% ImGui::NavInitWindow imgui.lua:16118 + [x] 97% ImGui::NavCalcPreferredRefPosSource imgui.lua:16150 + [x] 94% ImGui::NavCalcPreferredRefPos imgui.lua:16169 + [x] 91% ImGui::GetNavTweakPressedAmount imgui.lua:16211 + [x] 85% ImGui::NavUpdate imgui.lua:16813 + [x] 88% ImGui::NavInitRequestApplyResult imgui.lua:17364 + [x] 81% NavBiasScoringRect imgui.lua:16573 + [x] 83% ImGui::NavUpdateCreateMoveRequest imgui.lua:16674 + [x] 87% ImGui::NavUpdateCreateTabbingRequest imgui.lua:16784 + [x] 85% ImGui::NavMoveRequestApplyResult imgui.lua:17395 + [x] 91% ImGui::NavUpdateCancelRequest imgui.lua:17021 + [x] 83% ImGui::NavUpdateContextMenuRequest imgui.lua:16509 + [x] 90% ImGui::NavUpdatePageUpPageDown imgui.lua:16605 + [x] 81% ImGui::NavEndFrame imgui.lua:19311 + [x] 83% ImGui::NavUpdateCreateWrappingRequest imgui.lua:19255 [x] 85% ImGui::IsWindowNavFocusable imgui.lua:7217 [x] 67% FindWindowNavFocusable imgui.lua:7225 - [x] 86% NavUpdateWindowingTarget imgui.lua:16235 - [x] 92% ImGui::NavUpdateWindowingApplyFocus imgui.lua:16258 - [x] 88% ImGui::NavUpdateWindowing imgui.lua:16296 - [x] 67% GetFallbackWindowNameForWindowingList imgui.lua:19208 - [x] 84% ImGui::NavUpdateWindowingOverlay imgui.lua:19222 + [x] 86% NavUpdateWindowingTarget imgui.lua:16239 + [x] 92% ImGui::NavUpdateWindowingApplyFocus imgui.lua:16262 + [x] 88% ImGui::NavUpdateWindowing imgui.lua:16300 + [x] 67% GetFallbackWindowNameForWindowingList imgui.lua:19212 + [x] 84% ImGui::NavUpdateWindowingOverlay imgui.lua:19226 -- DRAG AND DROP [x] 55% ImGui::IsDragDropActive imgui.lua:525 [x] 73% ImGui::ClearDragDrop imgui.lua:529 - [x] 82% ImGui::BeginTooltipHidden imgui.lua:19104 + [x] 82% ImGui::BeginTooltipHidden imgui.lua:19108 [x] 88% ImGui::BeginDragDropSource imgui.lua:547 [x] 86% ImGui::EndDragDropSource imgui.lua:648 [x] 62% ImGui::SetDragDropPayload imgui.lua:665 @@ -697,38 +697,38 @@ imgui.cpp [x] 78% WindowSettingsHandler_WriteAll imgui.lua:6001 -- LOCALIZATION - [x] 84% ImGui::LocalizeRegisterEntries imgui.lua:17594 + [x] 84% ImGui::LocalizeRegisterEntries imgui.lua:17598 -- VIEWPORTS, PLATFORM WINDOWS - [x] 83% ImGuiViewport::GetDebugName imgui.lua:17628 - [x] 76% ImGuiPlatformIO::ClearPlatformHandlers imgui.lua:17633 - [x] 69% ImGuiPlatformIO::ClearRendererHandlers imgui.lua:17647 - [x] 78% ImGui::GetMainViewport imgui.lua:17656 - [x] 88% ImGui::FindViewportByID imgui.lua:17665 + [x] 83% ImGuiViewport::GetDebugName imgui.lua:17632 + [x] 76% ImGuiPlatformIO::ClearPlatformHandlers imgui.lua:17637 + [x] 69% ImGuiPlatformIO::ClearRendererHandlers imgui.lua:17651 + [x] 78% ImGui::GetMainViewport imgui.lua:17660 + [x] 88% ImGui::FindViewportByID imgui.lua:17669 [ ] ImGui::FindViewportByPlatformHandle - [x] 98% ImGui::SetCurrentViewport imgui.lua:17677 - [x] 99% ImGui::SetWindowViewport imgui.lua:17705 - [x] 86% ImGui::GetWindowAlwaysWantOwnViewport imgui.lua:17718 - [x] 86% IsViewportAbove imgui.lua:17740 - [x] 89% ImGui::UpdateTryMergeWindowIntoHostViewport imgui.lua:17765 - [x] 83% ImGui::UpdateTryMergeWindowIntoHostViewports imgui.lua:17825 - [x] 88% ImGui::TranslateWindowsInViewport imgui.lua:17837 - [x] 94% ImGui::ScaleWindowsInViewport imgui.lua:14911 - [x] 85% ImGui::FindHoveredViewportFromPlatformWindowStack imgui.lua:17863 - [x] 87% ImGui::UpdateViewportsNewFrame imgui.lua:17880 - [x] 78% ImGui::UpdateViewportsEndFrame imgui.lua:18167 - [x] 81% ImGui::AddUpdateViewport imgui.lua:18201 - [x] 80% ImGui::DestroyViewport imgui.lua:18281 - [x] 93% ImGui::WindowSelectViewport imgui.lua:18307 - [x] 73% ImGui::WindowSyncOwnedViewport imgui.lua:18433 - [x] 88% ImGui::UpdatePlatformWindows imgui.lua:18537 - [x] 85% ImGui::RenderPlatformWindowsDefault imgui.lua:18655 - [x] 87% ImGui::FindPlatformMonitorForPos imgui.lua:18687 - [x] 78% ImGui::FindPlatformMonitorForRect imgui.lua:18703 - [x] 93% ImGui::UpdateViewportPlatformMonitor imgui.lua:18744 - [x] 89% ImGui::GetViewportPlatformMonitor imgui.lua:18751 - [x] 97% ImGui::DestroyPlatformWindow imgui.lua:18763 - [x] 77% ImGui::DestroyPlatformWindows imgui.lua:18789 + [x] 98% ImGui::SetCurrentViewport imgui.lua:17681 + [x] 99% ImGui::SetWindowViewport imgui.lua:17709 + [x] 86% ImGui::GetWindowAlwaysWantOwnViewport imgui.lua:17722 + [x] 86% IsViewportAbove imgui.lua:17744 + [x] 89% ImGui::UpdateTryMergeWindowIntoHostViewport imgui.lua:17769 + [x] 83% ImGui::UpdateTryMergeWindowIntoHostViewports imgui.lua:17829 + [x] 88% ImGui::TranslateWindowsInViewport imgui.lua:17841 + [x] 94% ImGui::ScaleWindowsInViewport imgui.lua:14915 + [x] 85% ImGui::FindHoveredViewportFromPlatformWindowStack imgui.lua:17867 + [x] 87% ImGui::UpdateViewportsNewFrame imgui.lua:17884 + [x] 78% ImGui::UpdateViewportsEndFrame imgui.lua:18171 + [x] 81% ImGui::AddUpdateViewport imgui.lua:18205 + [x] 80% ImGui::DestroyViewport imgui.lua:18285 + [x] 93% ImGui::WindowSelectViewport imgui.lua:18311 + [x] 73% ImGui::WindowSyncOwnedViewport imgui.lua:18437 + [x] 88% ImGui::UpdatePlatformWindows imgui.lua:18541 + [x] 85% ImGui::RenderPlatformWindowsDefault imgui.lua:18659 + [x] 87% ImGui::FindPlatformMonitorForPos imgui.lua:18691 + [x] 78% ImGui::FindPlatformMonitorForRect imgui.lua:18707 + [x] 93% ImGui::UpdateViewportPlatformMonitor imgui.lua:18748 + [x] 89% ImGui::GetViewportPlatformMonitor imgui.lua:18755 + [x] 97% ImGui::DestroyPlatformWindow imgui.lua:18767 + [x] 77% ImGui::DestroyPlatformWindows imgui.lua:18793 -- DOCKING [x] 61% ImGuiDockRequest::ImGuiDockRequest imgui.lua:1019 @@ -845,47 +845,47 @@ imgui.cpp [ ] InitializeDefaultPlatformHandlers -- METRICS/DEBUGGER WINDOW - [x] 97% MetricsHelpMarker imgui.lua:20063 = MetricsHelpMarker - [x] 93% ImGui::DebugRenderViewportThumbnail imgui.lua:18812 - [x] 92% RenderViewportsThumbnails imgui.lua:18851 + [x] 97% MetricsHelpMarker imgui.lua:20067 = MetricsHelpMarker + [x] 93% ImGui::DebugRenderViewportThumbnail imgui.lua:18816 + [x] 92% RenderViewportsThumbnails imgui.lua:18855 [ ] ViewportComparerByLastFocusedStampCount [ ] ImGui::DebugRenderKeyboardPreview - [x] 52% ImGui::DebugTextEncoding imgui.lua:20965 + [x] 52% ImGui::DebugTextEncoding imgui.lua:20969 [ ] DebugFlashStyleColorStop [ ] ImGui::DebugFlashStyleColor [ ] ImGui::UpdateDebugToolFlashStyleColor [ ] ImGui::DebugTextureIDToU64 [ ] FormatTextureRefForDebugDisplay - [x] 52% ImGui::ShowFontAtlas imgui.lua:20264 - [x] 84% ImGui::DebugNodeTexture imgui.lua:20231 - [x] 42% ImGui::ShowMetricsWindow imgui.lua:21030 + [x] 52% ImGui::ShowFontAtlas imgui.lua:20268 + [x] 84% ImGui::DebugNodeTexture imgui.lua:20235 + [x] 44% ImGui::ShowMetricsWindow imgui.lua:21034 [ ] ImGui::DebugBreakClearData [ ] ImGui::DebugBreakButtonTooltip [ ] ImGui::DebugBreakButton - [x] 87% ImGui::DebugNodeColumns imgui.lua:20609 - [x] 48% DebugNodeDockNodeFlags imgui.lua:20797 - [x] 79% ImGui::DebugNodeDockNode imgui.lua:20814 - [x] 28% ImGui::DebugNodeDrawList imgui.lua:20631 + [x] 87% ImGui::DebugNodeColumns imgui.lua:20613 + [x] 48% DebugNodeDockNodeFlags imgui.lua:20801 + [x] 79% ImGui::DebugNodeDockNode imgui.lua:20818 + [x] 28% ImGui::DebugNodeDrawList imgui.lua:20635 [ ] ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox [ ] CalcFontGlyphSrcOverlapMask - [x] 60% ImGui::DebugNodeFont imgui.lua:20154 - [x] 59% ImGui::DebugNodeFontGlyphsForSrcMask imgui.lua:20099 - [x] 89% ImGui::DebugNodeFontGlyph imgui.lua:20085 - [x] 37% ImGui::DebugNodeStorage imgui.lua:20619 - [x] 68% ImGui::DebugNodeTabBar imgui.lua:20692 - [x] 62% ImGui::DebugNodeViewport imgui.lua:20947 + [x] 60% ImGui::DebugNodeFont imgui.lua:20158 + [x] 59% ImGui::DebugNodeFontGlyphsForSrcMask imgui.lua:20103 + [x] 89% ImGui::DebugNodeFontGlyph imgui.lua:20089 + [x] 37% ImGui::DebugNodeStorage imgui.lua:20623 + [x] 68% ImGui::DebugNodeTabBar imgui.lua:20696 + [x] 62% ImGui::DebugNodeViewport imgui.lua:20951 [ ] ImGui::DebugNodePlatformMonitor - [x] 70% ImGui::DebugNodeWindow imgui.lua:20858 - [x] 87% ImGui::DebugNodeWindowSettings imgui.lua:20916 - [x] 73% ImGui::DebugNodeWindowsList imgui.lua:20922 - [x] 67% ImGui::DebugNodeWindowsListByBeginStackParent imgui.lua:20933 + [x] 70% ImGui::DebugNodeWindow imgui.lua:20862 + [x] 87% ImGui::DebugNodeWindowSettings imgui.lua:20920 + [x] 73% ImGui::DebugNodeWindowsList imgui.lua:20926 + [x] 67% ImGui::DebugNodeWindowsListByBeginStackParent imgui.lua:20937 -- DEBUG LOG WINDOW - [x] 5% ImGui::DebugLog imgui.lua:19904 + [x] 5% ImGui::DebugLog imgui.lua:19908 [ ] ImGui::DebugLogV - [x] 93% SameLineOrWrap imgui.lua:20336 - [x] 61% ShowDebugLogFlag imgui.lua:20343 - [x] 65% ImGui::ShowDebugLogWindow imgui.lua:20356 + [x] 93% SameLineOrWrap imgui.lua:20340 + [x] 61% ShowDebugLogFlag imgui.lua:20347 + [x] 65% ImGui::ShowDebugLogWindow imgui.lua:20360 [ ] ImGui::DebugTextUnformattedWithLocateItem -- OTHER DEBUG TOOLS (ITEM PICKER, ID STACK TOOL) @@ -897,13 +897,13 @@ imgui.cpp [ ] ImGui::DebugLocateItemResolveWithLastItem [ ] ImGui::DebugStartItemPicker [ ] ImGui::UpdateDebugToolItemPicker - [x] 75% DebugItemPathQuery_UpdateAndGetHookId imgui.lua:20427 - [x] 49% ImGui::UpdateDebugToolItemPathQuery imgui.lua:20456 - [x] 47% ImGui::DebugHookIdInfo imgui.lua:20468 + [x] 75% DebugItemPathQuery_UpdateAndGetHookId imgui.lua:20431 + [x] 49% ImGui::UpdateDebugToolItemPathQuery imgui.lua:20460 + [x] 47% ImGui::DebugHookIdInfo imgui.lua:20472 [ ] DebugItemPathQuery_FormatLevelInfo [ ] DebugItemPathQuery_GetResultAsPath - [x] 51% ImGui::ShowIDStackToolWindow imgui.lua:20527 - [x] 78% ImGui::ShowFontSelector imgui.lua:20069 + [x] 51% ImGui::ShowIDStackToolWindow imgui.lua:20531 + [x] 78% ImGui::ShowFontSelector imgui.lua:20073 ==================================================================================================== imgui_widgets.cpp @@ -946,7 +946,7 @@ imgui_widgets.cpp [x] 28% ImGui::Image imgui_widgets.lua:1252 [x] 92% ImGui::ImageButtonEx imgui_widgets.lua:1264 [x] 98% ImGui::ImageButton imgui_widgets.lua:1308 - [x] 76% ImGui::Checkbox imgui_widgets.lua:1323 + [x] 77% ImGui::Checkbox imgui_widgets.lua:1323 [x] 79% ImGui::CheckboxFlagsT imgui_widgets.lua:1436 = ImGui . CheckboxFlags [x] 7% ImGui::CheckboxFlags imgui_widgets.lua:1415 [x] 5% ImGui::RadioButton imgui_widgets.lua:1510 @@ -966,7 +966,7 @@ imgui_widgets.cpp [x] 100% ImGui::SeparatorText imgui_widgets.lua:1916 [x] 82% ImGui::SplitterBehavior imgui.lua:4592 [ ] ShrinkWidthItemComparer - [x] 58% ImGui::ShrinkWidths imgui_widgets.lua:9371 + [x] 58% ImGui::ShrinkWidths imgui_widgets.lua:10696 -- Widgets: ComboBox [x] 100% CalcMaxPopupHeightFromItemCount imgui_widgets.lua:1945 @@ -1076,7 +1076,7 @@ imgui_widgets.cpp [x] 79% STB_TEXTEDIT_DELETECHARS imgui_widgets.lua:4738 [x] 87% STB_TEXTEDIT_INSERTCHARS imgui_widgets.lua:4752 [x] 81% stb_textedit_replace imgui_widgets.lua:4795 - [x] 12% ImGuiInputTextState::ImGuiInputTextState imgui_internal.lua:1644 + [x] 12% ImGuiInputTextState::ImGuiInputTextState imgui_internal.lua:1663 [-] ImGuiInputTextState::~ImGuiInputTextState c++-only [x] 93% ImGuiInputTextState::OnKeyPressed imgui_widgets.lua:4823 [x] 63% ImGuiInputTextState::OnCharPressed imgui_widgets.lua:4838 @@ -1145,123 +1145,123 @@ imgui_widgets.cpp [x] 83% ImGui::Selectable imgui_widgets.lua:8112 -- Widgets: Typing-Select support - [ ] ImGui::GetTypingSelectRequest - [ ] ImStrimatchlen - [ ] ImGui::TypingSelectFindMatch - [ ] ImGui::TypingSelectFindNextSingleCharMatch - [ ] ImGui::TypingSelectFindBestLeadingMatch - [ ] ImGui::DebugNodeTypingSelectState + [x] 74% ImGui::GetTypingSelectRequest imgui_widgets.lua:8343 + [x] 48% ImStrimatchlen imgui_widgets.lua:8445 + [x] 95% ImGui::TypingSelectFindMatch imgui_widgets.lua:8468 + [x] 88% ImGui::TypingSelectFindNextSingleCharMatch imgui_widgets.lua:8485 + [x] 85% ImGui::TypingSelectFindBestLeadingMatch imgui_widgets.lua:8516 + [x] 86% ImGui::DebugNodeTypingSelectState imgui_widgets.lua:8534 -- Widgets: Box-Select support - [ ] BoxSelectPreStartDrag - [ ] BoxSelectActivateDrag - [ ] BoxSelectDeactivateDrag - [ ] BoxSelectScrollWithMouseDrag - [ ] ImGui::BeginBoxSelect - [ ] ImGui::EndBoxSelect + [x] 67% BoxSelectPreStartDrag imgui_widgets.lua:8555 + [x] 89% BoxSelectActivateDrag imgui_widgets.lua:8569 + [x] 99% BoxSelectDeactivateDrag imgui_widgets.lua:8582 + [x] 81% BoxSelectScrollWithMouseDrag imgui_widgets.lua:8592 + [x] 49% ImGui::BeginBoxSelect imgui_widgets.lua:8629 + [x] 90% ImGui::EndBoxSelect imgui_widgets.lua:8725 -- Widgets: Multi-Select support - [ ] DebugLogMultiSelectRequests - [ ] CalcScopeRect - [ ] ImGui::BeginMultiSelect - [ ] ImGui::EndMultiSelect - [ ] ImGui::SetNextItemSelectionUserData - [ ] ImGui::MultiSelectItemHeader - [ ] ImGui::MultiSelectItemFooter - [ ] ImGui::MultiSelectAddSetAll - [ ] ImGui::MultiSelectAddSetRange - [ ] ImGui::DebugNodeMultiSelectState + [x] 74% DebugLogMultiSelectRequests imgui_widgets.lua:8769 + [x] 61% CalcScopeRect imgui_widgets.lua:8777 + [x] 80% ImGui::BeginMultiSelect imgui_widgets.lua:8808 + [x] 83% ImGui::EndMultiSelect imgui_widgets.lua:8955 + [x] 73% ImGui::SetNextItemSelectionUserData imgui_widgets.lua:9045 + [x] 84% ImGui::MultiSelectItemHeader imgui_widgets.lua:9076 + [x] 86% ImGui::MultiSelectItemFooter imgui_widgets.lua:9146 + [x] 59% ImGui::MultiSelectAddSetAll imgui_widgets.lua:9360 + [x] 31% ImGui::MultiSelectAddSetRange imgui_widgets.lua:9372 + [x] 84% ImGui::DebugNodeMultiSelectState imgui_widgets.lua:9390 -- Widgets: Multi-Select helpers - [ ] ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage - [ ] ImGuiSelectionBasicStorage::Clear - [ ] ImGuiSelectionBasicStorage::Swap - [ ] ImGuiSelectionBasicStorage::Contains - [ ] PairComparerByValueInt - [ ] ImGuiSelectionBasicStorage::GetNextSelectedItem - [ ] ImGuiSelectionBasicStorage::SetItemSelected - [ ] ImGuiSelectionBasicStorage_BatchSetItemSelected - [ ] ImGuiSelectionBasicStorage_BatchFinish - [ ] ImGuiSelectionBasicStorage::ApplyRequests - [ ] ImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage - [ ] ImGuiSelectionExternalStorage::ApplyRequests + [x] 46% ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage imgui_widgets.lua:9435 + [x] 61% ImGuiSelectionBasicStorage::Clear imgui_widgets.lua:9447 + [x] 53% ImGuiSelectionBasicStorage::Swap imgui_widgets.lua:9454 + [x] 57% ImGuiSelectionBasicStorage::Contains imgui_widgets.lua:9462 + [x] 12% PairComparerByValueInt imgui_widgets.lua:9467 + [x] 26% ImGuiSelectionBasicStorage::GetNextSelectedItem imgui_widgets.lua:9476 + [x] 50% ImGuiSelectionBasicStorage::SetItemSelected imgui_widgets.lua:9507 + [x] 42% ImGuiSelectionBasicStorage_BatchSetItemSelected imgui_widgets.lua:9520 + [~] 0% ImGuiSelectionBasicStorage_BatchFinish imgui_widgets.lua:9535 stub + [x] 78% ImGuiSelectionBasicStorage::ApplyRequests imgui_widgets.lua:9554 + [x] 48% ImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage imgui_widgets.lua:9619 + [x] 84% ImGuiSelectionExternalStorage::ApplyRequests imgui_widgets.lua:9630 -- Widgets: ListBox - [x] 92% ImGui::BeginListBox imgui_widgets.lua:8338 - [x] 78% ImGui::EndListBox imgui_widgets.lua:8379 - [x] 63% ImGui::ListBox imgui_widgets.lua:8394 + [x] 92% ImGui::BeginListBox imgui_widgets.lua:9663 + [x] 78% ImGui::EndListBox imgui_widgets.lua:9704 + [x] 63% ImGui::ListBox imgui_widgets.lua:9719 -- Widgets: PlotLines, PlotHistogram - [x] 89% ImGui::PlotEx imgui_widgets.lua:8466 - [x] 86% ImGuiPlotArrayGetterData::ImGuiPlotArrayGetterData imgui_widgets.lua:8596 - [x] 48% Plot_ArrayGetter imgui_widgets.lua:8607 - [x] 41% ImGui::PlotLines imgui_widgets.lua:8621 - [x] 41% ImGui::PlotHistogram imgui_widgets.lua:8647 + [x] 89% ImGui::PlotEx imgui_widgets.lua:9791 + [x] 86% ImGuiPlotArrayGetterData::ImGuiPlotArrayGetterData imgui_widgets.lua:9921 + [x] 48% Plot_ArrayGetter imgui_widgets.lua:9932 + [x] 41% ImGui::PlotLines imgui_widgets.lua:9946 + [x] 41% ImGui::PlotHistogram imgui_widgets.lua:9972 -- Widgets: Value helpers [x] 21% ImGui::Value imgui_widgets.lua:384 -- MenuItem, BeginMenu, EndMenu, etc. - [x] 49% ImGuiMenuColumns::Update imgui_widgets.lua:8679 - [x] 77% ImGuiMenuColumns::CalcNextTotalWidth imgui_widgets.lua:8691 - [x] 64% ImGuiMenuColumns::DeclColumns imgui_widgets.lua:8714 - [x] 91% ImGui::BeginMenuBar imgui_widgets.lua:8727 - [x] 92% ImGui::EndMenuBar imgui_widgets.lua:8761 - [x] 85% ImGui::BeginViewportSideBar imgui_widgets.lua:8824 - [x] 90% ImGui::BeginMainMenuBar imgui_widgets.lua:8860 - [x] 90% ImGui::EndMainMenuBar imgui_widgets.lua:8886 - [x] 91% IsRootOfOpenMenuSet imgui_widgets.lua:8902 - [x] 89% ImGui::BeginMenuEx imgui_widgets.lua:8930 - [x] 67% ImGui::BeginMenu imgui_widgets.lua:9161 - [x] 92% ImGui::EndMenu imgui_widgets.lua:9167 - [x] 85% ImGui::MenuItemEx imgui_widgets.lua:9191 - [x] 67% ImGui::MenuItem imgui_widgets.lua:9298 + [x] 49% ImGuiMenuColumns::Update imgui_widgets.lua:10004 + [x] 77% ImGuiMenuColumns::CalcNextTotalWidth imgui_widgets.lua:10016 + [x] 64% ImGuiMenuColumns::DeclColumns imgui_widgets.lua:10039 + [x] 91% ImGui::BeginMenuBar imgui_widgets.lua:10052 + [x] 92% ImGui::EndMenuBar imgui_widgets.lua:10086 + [x] 85% ImGui::BeginViewportSideBar imgui_widgets.lua:10149 + [x] 90% ImGui::BeginMainMenuBar imgui_widgets.lua:10185 + [x] 90% ImGui::EndMainMenuBar imgui_widgets.lua:10211 + [x] 91% IsRootOfOpenMenuSet imgui_widgets.lua:10227 + [x] 89% ImGui::BeginMenuEx imgui_widgets.lua:10255 + [x] 67% ImGui::BeginMenu imgui_widgets.lua:10486 + [x] 92% ImGui::EndMenu imgui_widgets.lua:10492 + [x] 85% ImGui::MenuItemEx imgui_widgets.lua:10516 + [x] 67% ImGui::MenuItem imgui_widgets.lua:10623 -- Widgets: BeginTabBar, EndTabBar, etc. - [x] 19% ImGuiTabBarSection::ImGuiTabBarSection imgui_widgets.lua:9366 - [x] 16% ImGuiTabBar::ImGuiTabBar imgui_internal.lua:1991 - [x] 65% TabItemGetSectionIdx imgui_widgets.lua:9333 + [x] 19% ImGuiTabBarSection::ImGuiTabBarSection imgui_widgets.lua:10691 + [x] 16% ImGuiTabBar::ImGuiTabBar imgui_internal.lua:2168 + [x] 65% TabItemGetSectionIdx imgui_widgets.lua:10658 [ ] TabItemComparerBySection [ ] TabItemComparerByBeginOrder [ ] GetTabBarFromTabBarRef [ ] GetTabBarRefFromTabBar - [x] 60% ImGui::TabBarFindByID imgui_widgets.lua:9454 - [x] 77% ImGui::TabBarRemove imgui_widgets.lua:9460 - [x] 92% ImGui::BeginTabBar imgui_widgets.lua:9465 - [x] 82% ImGui::BeginTabBarEx imgui_widgets.lua:9483 - [x] 90% ImGui::EndTabBar imgui_widgets.lua:9547 - [x] 68% TabBarCalcScrollableWidth imgui_widgets.lua:9359 - [x] 81% ImGui::TabBarLayout imgui_widgets.lua:9582 - [x] 75% ImGui::TabBarCalcTabID imgui_widgets.lua:9853 - [x] 62% ImGui::TabBarCalcMaxTabWidth imgui_widgets.lua:9863 - [x] 71% ImGui::TabBarFindTabByID imgui_widgets.lua:9867 - [x] 90% ImGui::TabBarFindTabByOrder imgui_widgets.lua:9879 - [x] 85% ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow imgui_widgets.lua:9891 - [x] 93% ImGui::TabBarGetCurrentTab imgui_widgets.lua:9902 - [x] 55% ImGui::TabBarGetTabName imgui_widgets.lua:9907 - [x] 89% ImGui::TabBarGetTabPos imgui_widgets.lua:9913 - [x] 92% ImGui::TabBarAddTab imgui_widgets.lua:9922 - [x] 91% ImGui::TabBarRemoveTab imgui_widgets.lua:9939 - [x] 92% ImGui::TabBarCloseTab imgui_widgets.lua:9948 - [x] 97% ImGui::TabBarScrollClamp imgui_widgets.lua:9966 - [x] 89% ImGui::TabBarScrollToTab imgui_widgets.lua:9972 - [x] 67% ImGui::TabBarQueueFocus imgui_widgets.lua:10010 - [x] 100% ImGui::TabBarQueueReorder imgui_widgets.lua:10019 - [x] 85% ImGui::TabBarQueueReorderFromMousePos imgui_widgets.lua:10026 - [x] 64% ImGui::TabBarProcessReorder imgui_widgets.lua:10060 - [x] 85% ImGui::TabBarScrollingButtons imgui_widgets.lua:10083 - [x] 80% ImGui::TabBarTabListPopupButton imgui_widgets.lua:10143 + [x] 60% ImGui::TabBarFindByID imgui_widgets.lua:10779 + [x] 77% ImGui::TabBarRemove imgui_widgets.lua:10785 + [x] 92% ImGui::BeginTabBar imgui_widgets.lua:10790 + [x] 82% ImGui::BeginTabBarEx imgui_widgets.lua:10808 + [x] 90% ImGui::EndTabBar imgui_widgets.lua:10872 + [x] 68% TabBarCalcScrollableWidth imgui_widgets.lua:10684 + [x] 81% ImGui::TabBarLayout imgui_widgets.lua:10907 + [x] 75% ImGui::TabBarCalcTabID imgui_widgets.lua:11178 + [x] 62% ImGui::TabBarCalcMaxTabWidth imgui_widgets.lua:11188 + [x] 71% ImGui::TabBarFindTabByID imgui_widgets.lua:11192 + [x] 90% ImGui::TabBarFindTabByOrder imgui_widgets.lua:11204 + [x] 85% ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow imgui_widgets.lua:11216 + [x] 93% ImGui::TabBarGetCurrentTab imgui_widgets.lua:11227 + [x] 55% ImGui::TabBarGetTabName imgui_widgets.lua:11232 + [x] 89% ImGui::TabBarGetTabPos imgui_widgets.lua:11238 + [x] 92% ImGui::TabBarAddTab imgui_widgets.lua:11247 + [x] 91% ImGui::TabBarRemoveTab imgui_widgets.lua:11264 + [x] 92% ImGui::TabBarCloseTab imgui_widgets.lua:11273 + [x] 97% ImGui::TabBarScrollClamp imgui_widgets.lua:11291 + [x] 89% ImGui::TabBarScrollToTab imgui_widgets.lua:11297 + [x] 67% ImGui::TabBarQueueFocus imgui_widgets.lua:11335 + [x] 100% ImGui::TabBarQueueReorder imgui_widgets.lua:11344 + [x] 85% ImGui::TabBarQueueReorderFromMousePos imgui_widgets.lua:11351 + [x] 64% ImGui::TabBarProcessReorder imgui_widgets.lua:11385 + [x] 85% ImGui::TabBarScrollingButtons imgui_widgets.lua:11408 + [x] 80% ImGui::TabBarTabListPopupButton imgui_widgets.lua:11468 -- Widgets: BeginTabItem, EndTabItem, etc. - [x] 78% ImGui::BeginTabItem imgui_widgets.lua:10191 - [x] 85% ImGui::EndTabItem imgui_widgets.lua:10210 - [x] 81% ImGui::TabItemButton imgui_widgets.lua:10224 - [x] 84% ImGui::TabItemSpacing imgui_widgets.lua:10234 - [x] 79% ImGui::TabItemEx imgui_widgets.lua:10245 - [x] 88% ImGui::SetTabItemClosed imgui_widgets.lua:10531 - [x] 79% ImGui::TabItemCalcSize imgui_widgets.lua:10553 - [x] 91% ImGui::TabItemBackground imgui_widgets.lua:10569 - [x] 86% ImGui::TabItemLabelAndCloseButton imgui_widgets.lua:10590 + [x] 78% ImGui::BeginTabItem imgui_widgets.lua:11516 + [x] 85% ImGui::EndTabItem imgui_widgets.lua:11535 + [x] 81% ImGui::TabItemButton imgui_widgets.lua:11549 + [x] 84% ImGui::TabItemSpacing imgui_widgets.lua:11559 + [x] 79% ImGui::TabItemEx imgui_widgets.lua:11570 + [x] 88% ImGui::SetTabItemClosed imgui_widgets.lua:11856 + [x] 79% ImGui::TabItemCalcSize imgui_widgets.lua:11878 + [x] 91% ImGui::TabItemBackground imgui_widgets.lua:11894 + [x] 86% ImGui::TabItemLabelAndCloseButton imgui_widgets.lua:11915 ==================================================================================================== imgui_draw.cpp @@ -1273,7 +1273,7 @@ imgui_draw.cpp [x] 82% ImGui::StyleColorsLight imgui_draw.lua:271 -- ImDrawList - [x] 30% ImDrawListSharedData::ImDrawListSharedData imgui_internal.lua:1073 + [x] 30% ImDrawListSharedData::ImDrawListSharedData imgui_internal.lua:1092 [-] ImDrawListSharedData::~ImDrawListSharedData c++-only [x] 57% ImDrawListSharedData::SetCircleTessellationMaxError imgui_draw.lua:3632 [x] 7% ImDrawList::ImDrawList imgui_h.lua:653 @@ -1594,7 +1594,7 @@ imgui_tables.cpp [x] 82% TableGetMaxDisplayOrderAllowed imgui_tables.lua:899 [x] 82% ImGui::TableQueueSetColumnDisplayOrder imgui_tables.lua:925 [x] 62% TableSetupColumnFlags imgui_tables.lua:937 - [x] 81% ImGui::TableUpdateLayout imgui_tables.lua:996 + [x] 82% ImGui::TableUpdateLayout imgui_tables.lua:996 [x] 84% ImGui::TableApplyExternalUnclipRect imgui_tables.lua:1555 [x] 76% ImGui::TableUpdateBorders imgui_tables.lua:1571 [x] 80% ImGui::EndTable imgui_tables.lua:1633 @@ -1697,8 +1697,8 @@ imgui_tables.cpp -- Tables: Debugging [ ] DebugNodeTableGetSizingPolicyDesc - [x] 16% ImGui::DebugNodeTable imgui.lua:20750 - [x] 69% ImGui::DebugNodeTableSettings imgui.lua:20732 + [x] 16% ImGui::DebugNodeTable imgui.lua:20754 + [x] 69% ImGui::DebugNodeTableSettings imgui.lua:20736 -- Columns, BeginColumns, EndColumns, etc. [x] 24% ImGui::SetWindowClipRectBeforeSetChannel imgui_tables.lua:2070 @@ -1923,8 +1923,8 @@ imgui.h -- Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) [ ] ImGuiOnceUponAFrame::ImGuiOnceUponAFrame [-] ImGuiOnceUponAFrame::operatorbool c++-only - [x] 80% ImGuiTextFilter::Clear imgui.lua:19360 - [x] 100% ImGuiTextFilter::IsActive imgui.lua:19361 + [x] 80% ImGuiTextFilter::Clear imgui.lua:19364 + [x] 100% ImGuiTextFilter::IsActive imgui.lua:19365 [-] ImGuiTextFilter::Draw obsolete [x] 0% ImGuiTextBuffer::ImGuiTextBuffer imgui_h.lua:2682 [ ] ImGuiTextBuffer::begin @@ -1937,7 +1937,7 @@ imgui.h [x] 31% ImGuiTextBuffer::c_str imgui_h.lua:2687 [ ] ImGuiStoragePair::ImGuiStoragePair [ ] ImGuiStorage::Clear - [x] 100% ImGuiListClipper::IncludeItemByIndex imgui.lua:19591 + [x] 100% ImGuiListClipper::IncludeItemByIndex imgui.lua:19595 [-] operator+ c++-only [-] operator- c++-only [-] operator* c++-only @@ -1955,7 +1955,7 @@ imgui.h [ ] IM_MSVC_RUNTIME_CHECKS_RESTORE::HSV -- Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiSelectionRequestType, ImGuiSelectionRequest, ImGuiMultiSelectIO, ImGuiSelectionBasicStorage) - [ ] ImGuiSelectionBasicStorage::GetStorageIdFromIndex + [x] 100% ImGuiSelectionBasicStorage::GetStorageIdFromIndex imgui_widgets.lua:9516 -- Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawList, ImDrawData) [x] 14% ImDrawCmd::ImDrawCmd imgui_h.lua:506 @@ -1965,12 +1965,12 @@ imgui.h [ ] ImDrawList::GetClipRectMin [ ] ImDrawList::GetClipRectMax [x] 1% ImDrawList::AddPolyline imgui_draw.lua:4336 - [x] 29% ImDrawList::PathClear imgui_internal.lua:997 - [x] 16% ImDrawList::PathLineTo imgui_internal.lua:1001 - [x] 48% ImDrawList::PathLineToMergeDuplicate imgui_internal.lua:1011 - [x] 88% ImDrawList::PathFillConvex imgui_internal.lua:1019 + [x] 29% ImDrawList::PathClear imgui_internal.lua:1016 + [x] 16% ImDrawList::PathLineTo imgui_internal.lua:1020 + [x] 48% ImDrawList::PathLineToMergeDuplicate imgui_internal.lua:1030 + [x] 88% ImDrawList::PathFillConvex imgui_internal.lua:1038 [x] 94% ImDrawList::PathFillConcave imgui_draw.lua:5935 - [x] 67% ImDrawList::PathStroke imgui_internal.lua:1027 + [x] 67% ImDrawList::PathStroke imgui_internal.lua:1046 [x] 89% ImDrawList::ChannelsSplit imgui_draw.lua:4089 [x] 80% ImDrawList::ChannelsMerge imgui_draw.lua:4090 [x] 89% ImDrawList::ChannelsSetCurrent imgui_draw.lua:4092 @@ -2039,9 +2039,9 @@ imgui_internal.h [x] 92% ImIsPowerOfTwo imgui_internal.lua:270 [x] 35% ImUpperPowerOfTwo imgui_internal.lua:274 [x] 82% ImCountSetBits imgui_internal.lua:289 - [ ] ImToUpper - [x] 75% ImCharIsBlankA imgui_internal.lua:663 - [x] 83% ImCharIsBlankW imgui_internal.lua:666 + [x] 62% ImToUpper imgui_internal.lua:397 + [x] 75% ImCharIsBlankA imgui_internal.lua:679 + [x] 83% ImCharIsBlankW imgui_internal.lua:682 [ ] ImCharIsXdigitA [x] 0% ImFileOpen imgui.lua:4702 [x] 0% ImFileClose imgui.lua:4719 @@ -2051,7 +2051,7 @@ imgui_internal.h [x] n/a ImPow imgui_internal.lua:150 = math . pow [x] n/a ImLog imgui_internal.lua:151 = math . log [x] n/a ImAbs imgui_internal.lua:152 = math . abs - [ ] ImSign + [x] 69% ImSign imgui_internal.lua:391 [x] 40% ImRsqrt imgui_internal.lua:260 [ ] ImRsqrtPrecise [ ] ImSwap @@ -2062,7 +2062,7 @@ imgui_internal.h [x] 25% ImClamp imgui_internal.lua:218 [x] 45% ImLerp imgui_internal.lua:229 [x] 77% ImSaturate imgui_internal.lua:387 - [x] 67% ImLengthSqr imgui_internal.lua:424 + [x] 67% ImLengthSqr imgui_internal.lua:434 [ ] ImInvLength [x] 24% ImTrunc imgui_internal.lua:242 [x] 69% ImFloor imgui_internal.lua:266 @@ -2071,50 +2071,50 @@ imgui_internal.h [ ] ImRoundSigned64 [x] 42% ImCeilFast imgui_internal.lua:252 [ ] ImModPositive - [x] 100% ImDot imgui_internal.lua:437 - [x] 98% ImRotate imgui_internal.lua:431 - [x] 100% ImLinearSweep imgui_widgets.lua:9327 - [ ] ImLinearRemapClamp - [x] 97% ImMul imgui_internal.lua:444 + [x] 100% ImDot imgui_internal.lua:447 + [x] 98% ImRotate imgui_internal.lua:441 + [x] 100% ImLinearSweep imgui_widgets.lua:10652 + [x] 100% ImLinearRemapClamp imgui_internal.lua:393 + [x] 97% ImMul imgui_internal.lua:454 [ ] ImIsFloatAboveGuaranteedIntegerPrecision [x] 71% ImExponentialMovingAverage imgui_internal.lua:302 [ ] ImIsTruncated [ ] ImIsTruncated4 [ ] ImTriangleArea [ ] ImTriangleIsClockwise - [x] 0% IM_MSVC_RUNTIME_CHECKS_OFF::ImVec1 imgui_internal.lua:768 + [x] 0% IM_MSVC_RUNTIME_CHECKS_OFF::ImVec1 imgui_internal.lua:784 [ ] ImVec2i::ImVec2i [ ] ImVec2ih::ImVec2ih - [x] 0% ImRect::ImRect imgui_internal.lua:798 - [x] 90% ImRect::GetCenter imgui_internal.lua:828 - [x] 89% ImRect::GetSize imgui_internal.lua:833 - [x] 94% ImRect::GetWidth imgui_internal.lua:829 - [x] 94% ImRect::GetHeight imgui_internal.lua:830 - [x] 91% ImRect::GetArea imgui_internal.lua:919 - [x] 33% ImRect::GetTL imgui_internal.lua:836 - [x] 89% ImRect::GetTR imgui_internal.lua:838 - [x] 89% ImRect::GetBL imgui_internal.lua:840 - [x] 33% ImRect::GetBR imgui_internal.lua:842 - [x] 43% ImRect::Contains imgui_internal.lua:805 - [x] 96% ImRect::ContainsWithPad imgui_internal.lua:817 - [x] 14% ImRect::Overlaps imgui_internal.lua:823 - [x] 59% ImRect::Add imgui_internal.lua:867 - [ ] ImRect::AddX - [ ] ImRect::AddY - [x] 38% ImRect::Expand imgui_internal.lua:885 - [x] 64% ImRect::Translate imgui_internal.lua:902 - [x] 55% ImRect::TranslateX imgui_internal.lua:908 - [x] 55% ImRect::TranslateY imgui_internal.lua:913 - [x] 22% ImRect::ClipWith imgui_internal.lua:847 - [x] 20% ImRect::ClipWithFull imgui_internal.lua:857 - [x] 91% ImRect::IsInverted imgui_internal.lua:917 - [x] 94% ImRect::ToVec4 imgui_internal.lua:896 - [x] 36% ImRect::AsVec4 imgui_internal.lua:922 + [x] 0% ImRect::ImRect imgui_internal.lua:814 + [x] 90% ImRect::GetCenter imgui_internal.lua:844 + [x] 89% ImRect::GetSize imgui_internal.lua:849 + [x] 94% ImRect::GetWidth imgui_internal.lua:845 + [x] 94% ImRect::GetHeight imgui_internal.lua:846 + [x] 91% ImRect::GetArea imgui_internal.lua:938 + [x] 33% ImRect::GetTL imgui_internal.lua:852 + [x] 89% ImRect::GetTR imgui_internal.lua:854 + [x] 89% ImRect::GetBL imgui_internal.lua:856 + [x] 33% ImRect::GetBR imgui_internal.lua:858 + [x] 43% ImRect::Contains imgui_internal.lua:821 + [x] 96% ImRect::ContainsWithPad imgui_internal.lua:833 + [x] 14% ImRect::Overlaps imgui_internal.lua:839 + [x] 59% ImRect::Add imgui_internal.lua:883 + [x] 96% ImRect::AddX imgui_internal.lua:899 + [x] 96% ImRect::AddY imgui_internal.lua:900 + [x] 38% ImRect::Expand imgui_internal.lua:904 + [x] 64% ImRect::Translate imgui_internal.lua:921 + [x] 55% ImRect::TranslateX imgui_internal.lua:927 + [x] 55% ImRect::TranslateY imgui_internal.lua:932 + [x] 22% ImRect::ClipWith imgui_internal.lua:863 + [x] 20% ImRect::ClipWithFull imgui_internal.lua:873 + [x] 91% ImRect::IsInverted imgui_internal.lua:936 + [x] 94% ImRect::ToVec4 imgui_internal.lua:915 + [x] 36% ImRect::AsVec4 imgui_internal.lua:941 [ ] ImBitArrayGetStorageSizeInBytes [ ] ImBitArrayClearAllBits [ ] ImBitArrayTestBit [ ] ImBitArrayClearBit - [x] 34% ImBitArraySetBit imgui_internal.lua:957 + [x] 34% ImBitArraySetBit imgui_internal.lua:976 [ ] ImBitArraySetBitRange [ ] ImBitVector::Create [ ] ImBitVector::Clear @@ -2166,10 +2166,10 @@ imgui_internal.h [ ] template::offset_from_ptr [ ] template::ptr_from_offset [ ] template::swap - [x] 83% ImGuiTextIndex::clear imgui_internal.lua:1758 - [x] 100% ImGuiTextIndex::size imgui_internal.lua:1760 - [x] 71% ImGuiTextIndex::get_line_begin imgui_internal.lua:1762 - [x] 76% ImGuiTextIndex::get_line_end imgui_internal.lua:1766 + [x] 83% ImGuiTextIndex::clear imgui_internal.lua:1777 + [x] 100% ImGuiTextIndex::size imgui_internal.lua:1779 + [x] 71% ImGuiTextIndex::get_line_begin imgui_internal.lua:1781 + [x] 76% ImGuiTextIndex::get_line_end imgui_internal.lua:1785 [ ] ImGuiTextFilterItem::ImGuiTextFilterItem [ ] ImGuiPackedDate::ImGuiPackedDate [ ] ImGuiPackedDate::IsValid @@ -2177,37 +2177,37 @@ imgui_internal.h [ ] ImGuiPackedDate::SubtractMonths -- ImDrawList support - [x] 27% ImDrawDataBuilder::ImDrawDataBuilder imgui_internal.lua:2778 + [x] 27% ImDrawDataBuilder::ImDrawDataBuilder imgui_internal.lua:2973 -- Style support [ ] ImGuiStyleVarInfo::GetVarPtr - [x] 39% ImGuiStyleMod::ImGuiStyleMod imgui_internal.lua:1210 + [x] 39% ImGuiStyleMod::ImGuiStyleMod imgui_internal.lua:1229 -- Widgets support: flags, enums, data structures - [x] 21% ImGuiComboPreviewData::ImGuiComboPreviewData imgui_internal.lua:1604 - [x] 12% ImGuiMenuColumns::ImGuiMenuColumns imgui_internal.lua:2445 - [x] 20% ImGuiInputTextDeactivatedState::ImGuiInputTextDeactivatedState imgui_internal.lua:1690 + [x] 21% ImGuiComboPreviewData::ImGuiComboPreviewData imgui_internal.lua:1623 + [x] 12% ImGuiMenuColumns::ImGuiMenuColumns imgui_internal.lua:2640 + [x] 20% ImGuiInputTextDeactivatedState::ImGuiInputTextDeactivatedState imgui_internal.lua:1709 [ ] ImGuiInputTextDeactivatedState::ClearFreeMemory [ ] ImGuiInputTextState::ClearText [ ] ImGuiInputTextState::ClearFreeMemory [ ] ImGuiInputTextState::GetText - [x] 3% ImGuiNextWindowData::ImGuiNextWindowData imgui_internal.lua:1375 + [x] 3% ImGuiNextWindowData::ImGuiNextWindowData imgui_internal.lua:1394 [ ] ImGuiNextWindowData::ClearFlags - [x] 20% ImGuiNextItemData::ImGuiNextItemData imgui_internal.lua:1327 + [x] 20% ImGuiNextItemData::ImGuiNextItemData imgui_internal.lua:1346 [ ] ImGuiNextItemData::ClearFlags - [x] 11% ImGuiLastItemData::ImGuiLastItemData imgui_internal.lua:1247 - [x] 8% ImGuiErrorRecoveryState::ImGuiErrorRecoveryState imgui.lua:19780 + [x] 11% ImGuiLastItemData::ImGuiLastItemData imgui_internal.lua:1266 + [x] 8% ImGuiErrorRecoveryState::ImGuiErrorRecoveryState imgui.lua:19784 [ ] ImGuiPtrOrIndex::ImGuiPtrOrIndex -- Popup support - [x] 36% ImGuiPopupData::ImGuiPopupData imgui_internal.lua:3332 + [x] 36% ImGuiPopupData::ImGuiPopupData imgui_internal.lua:3527 -- Inputs support - [x] 9% ImGuiInputEvent::ImGuiInputEvent imgui_internal.lua:3126 - [x] 73% ImGuiKeyRoutingData::ImGuiKeyRoutingData imgui_internal.lua:1711 - [x] 10% ImGuiKeyRoutingTable::ImGuiKeyRoutingTable imgui_internal.lua:1738 + [x] 9% ImGuiInputEvent::ImGuiInputEvent imgui_internal.lua:3321 + [x] 73% ImGuiKeyRoutingData::ImGuiKeyRoutingData imgui_internal.lua:1730 + [x] 10% ImGuiKeyRoutingTable::ImGuiKeyRoutingTable imgui_internal.lua:1757 [ ] ImGuiKeyRoutingTable::Clear - [x] 64% ImGuiKeyOwnerData::ImGuiKeyOwnerData imgui_internal.lua:624 + [x] 64% ImGuiKeyOwnerData::ImGuiKeyOwnerData imgui_internal.lua:634 -- Clipper support [ ] ImGuiListClipperRange::FromIndices @@ -2216,25 +2216,25 @@ imgui_internal.h [ ] ImGuiListClipperData::Reset -- Navigation support - [x] 13% ImGuiNavItemData::ImGuiNavItemData imgui_internal.lua:1844 + [x] 13% ImGuiNavItemData::ImGuiNavItemData imgui_internal.lua:1863 [ ] ImGuiNavItemData::Clear -- Typing-select support - [ ] ImGuiTypingSelectState::ImGuiTypingSelectState - [ ] ImGuiTypingSelectState::Clear + [x] 7% ImGuiTypingSelectState::ImGuiTypingSelectState imgui_internal.lua:1907 + [x] 71% ImGuiTypingSelectState::Clear imgui_internal.lua:1910 -- Columns support [x] 19% ImGuiOldColumnData::ImGuiOldColumnData imgui_tables.lua:340 [x] 5% ImGuiOldColumns::ImGuiOldColumns imgui_tables.lua:345 -- Box-select support - [ ] ImGuiBoxSelectState::ImGuiBoxSelectState + [x] 6% ImGuiBoxSelectState::ImGuiBoxSelectState imgui_internal.lua:1934 -- Multi-select support - [ ] ImGuiMultiSelectTempData::ImGuiMultiSelectTempData - [ ] ImGuiMultiSelectTempData::Clear - [ ] ImGuiMultiSelectTempData::ClearIO - [ ] ImGuiMultiSelectState::ImGuiMultiSelectState + [x] 9% ImGuiMultiSelectTempData::ImGuiMultiSelectTempData imgui_internal.lua:1980 + [x] 10% ImGuiMultiSelectTempData::Clear imgui_internal.lua:1986 + [x] 67% ImGuiMultiSelectTempData::ClearIO imgui_internal.lua:2004 + [x] 72% ImGuiMultiSelectState::ImGuiMultiSelectState imgui_internal.lua:2017 -- Docking support [x] 100% ImGuiDockNode::IsRootNode imgui.lua:1095 @@ -2252,23 +2252,23 @@ imgui_internal.h [x] 20% ImGuiDockContext::ImGuiDockContext imgui.lua:1040 -- Viewport support - [x] 36% ImGuiViewportP::ImGuiViewportP imgui_internal.lua:2865 + [x] 36% ImGuiViewportP::ImGuiViewportP imgui_internal.lua:3060 [-] ImGuiViewportP::~ImGuiViewportP c++-only - [x] 78% ImGuiViewportP::ClearRequestFlags imgui_internal.lua:2820 - [x] 94% ImGuiViewportP::CalcWorkRectPos imgui_internal.lua:2827 - [x] 98% ImGuiViewportP::CalcWorkRectSize imgui_internal.lua:2831 - [x] 87% ImGuiViewportP::UpdateWorkRect imgui_internal.lua:2836 - [x] 92% ImGuiViewportP::GetMainRect imgui_internal.lua:2843 - [x] 92% ImGuiViewportP::GetWorkRect imgui_internal.lua:2850 - [x] 96% ImGuiViewportP::GetBuildWorkRect imgui_internal.lua:2857 + [x] 78% ImGuiViewportP::ClearRequestFlags imgui_internal.lua:3015 + [x] 94% ImGuiViewportP::CalcWorkRectPos imgui_internal.lua:3022 + [x] 98% ImGuiViewportP::CalcWorkRectSize imgui_internal.lua:3026 + [x] 87% ImGuiViewportP::UpdateWorkRect imgui_internal.lua:3031 + [x] 92% ImGuiViewportP::GetMainRect imgui_internal.lua:3038 + [x] 92% ImGuiViewportP::GetWorkRect imgui_internal.lua:3045 + [x] 96% ImGuiViewportP::GetBuildWorkRect imgui_internal.lua:3052 -- Settings support - [x] 18% ImGuiWindowSettings::ImGuiWindowSettings imgui_internal.lua:2412 + [x] 18% ImGuiWindowSettings::ImGuiWindowSettings imgui_internal.lua:2607 [ ] ImGuiWindowSettings::GetName - [x] 13% ImGuiSettingsHandler::ImGuiSettingsHandler imgui_h.lua:2719 + [x] 13% ImGuiSettingsHandler::ImGuiSettingsHandler imgui_h.lua:2835 -- Metrics, Debug Tools - [x] 7% ImGuiDebugAllocInfo::ImGuiDebugAllocInfo imgui_internal.lua:1792 + [x] 7% ImGuiDebugAllocInfo::ImGuiDebugAllocInfo imgui_internal.lua:1811 [ ] ImGuiStackLevelInfo::ImGuiStackLevelInfo [ ] ImGuiDebugItemPathQuery::ImGuiDebugItemPathQuery [ ] ImGuiIDStackTool::ImGuiIDStackTool @@ -2277,12 +2277,12 @@ imgui_internal.h [ ] ImGuiContextHook::ImGuiContextHook -- ImGuiWindowTempData, ImGuiWindow - [x] 92% ImGuiWindow::Rect imgui_internal.lua:2586 - [x] 84% ImGuiWindow::TitleBarRect imgui_internal.lua:2592 - [x] 91% ImGuiWindow::MenuBarRect imgui_internal.lua:2598 + [x] 92% ImGuiWindow::Rect imgui_internal.lua:2781 + [x] 84% ImGuiWindow::TitleBarRect imgui_internal.lua:2787 + [x] 91% ImGuiWindow::MenuBarRect imgui_internal.lua:2793 -- Tab bar, Tab item support - [x] 50% ImGuiTabItem::ImGuiTabItem imgui_internal.lua:1972 + [x] 50% ImGuiTabItem::ImGuiTabItem imgui_internal.lua:2149 -- Table support + internal API [x] 28% ImGuiTableColumn::ImGuiTableColumn imgui_tables.lua:240 @@ -2298,36 +2298,36 @@ imgui_internal.h [x] 86% TableGetInstanceID imgui_tables.lua:445 -- ImGui internal API - [x] 100% GetScale imgui.lua:13575 - [x] 100% GetCurrentWindowRead imgui.lua:13581 - [x] 100% GetCurrentWindow imgui.lua:13588 + [x] 100% GetScale imgui.lua:13577 + [x] 100% GetCurrentWindowRead imgui.lua:13583 + [x] 100% GetCurrentWindow imgui.lua:13590 [ ] SetWindowParentWindowForFocusRoute - [x] 99% WindowRectAbsToRel imgui_internal.lua:641 - [x] 99% WindowRectRelToAbs imgui_internal.lua:646 - [x] 98% WindowPosAbsToRel imgui_internal.lua:659 - [ ] WindowPosRelToAbs + [x] 99% WindowRectAbsToRel imgui_internal.lua:651 + [x] 99% WindowRectRelToAbs imgui_internal.lua:656 + [x] 98% WindowPosAbsToRel imgui_internal.lua:669 + [x] 98% WindowPosRelToAbs imgui_internal.lua:675 [ ] GetPixelDensity - [x] 55% GetRoundedFontSize imgui_internal.lua:635 - [x] 15% GetForegroundDrawList imgui.lua:13663 - [x] 80% LocalizeGetMsg imgui_internal.lua:3436 + [x] 55% GetRoundedFontSize imgui_internal.lua:645 + [x] 15% GetForegroundDrawList imgui.lua:13665 + [x] 80% LocalizeGetMsg imgui_internal.lua:3631 [ ] ScrollToBringRectIntoView [ ] GetItemStatusFlags [ ] GetActiveID [ ] GetFocusID [x] 2% ItemSize imgui.lua:6492 - [x] 100% IsNamedKey imgui_internal.lua:549 - [x] 75% IsNamedKeyOrMod imgui_internal.lua:580 + [x] 100% IsNamedKey imgui_internal.lua:559 + [x] 75% IsNamedKeyOrMod imgui_internal.lua:590 [ ] IsLegacyKey - [x] 60% IsKeyboardKey imgui_internal.lua:554 - [x] 60% IsGamepadKey imgui_internal.lua:559 - [x] 60% IsMouseKey imgui_internal.lua:564 - [x] 60% IsAliasKey imgui_internal.lua:569 - [x] 100% IsLRModKey imgui_internal.lua:574 - [x] 76% ConvertSingleModFlagToKey imgui_internal.lua:586 + [x] 60% IsKeyboardKey imgui_internal.lua:564 + [x] 60% IsGamepadKey imgui_internal.lua:569 + [x] 60% IsMouseKey imgui_internal.lua:574 + [x] 60% IsAliasKey imgui_internal.lua:579 + [x] 100% IsLRModKey imgui_internal.lua:584 + [x] 76% ConvertSingleModFlagToKey imgui_internal.lua:596 [x] 17% GetKeyData imgui.lua:8440 - [x] 97% MouseButtonToKey imgui_internal.lua:546 - [x] 81% IsActiveIdUsingNavDir imgui_internal.lua:650 - [x] 88% GetKeyOwnerData imgui_internal.lua:612 + [x] 97% MouseButtonToKey imgui_internal.lua:556 + [x] 81% IsActiveIdUsingNavDir imgui_internal.lua:660 + [x] 88% GetKeyOwnerData imgui_internal.lua:622 [x] 100% DockNodeGetRootNode imgui.lua:4488 [x] 100% DockNodeIsInHierarchyOf imgui.lua:4493 [x] 86% DockNodeGetDepth imgui.lua:4501 @@ -2335,36 +2335,33 @@ imgui_internal.h [ ] GetWindowDockNode [x] 97% DockBuilderGetCentralNode imgui.lua:3661 [ ] GetCurrentFocusScope - [ ] GetBoxSelectState - [ ] GetMultiSelectState + [x] 91% GetBoxSelectState imgui_internal.lua:2023 + [x] 95% GetMultiSelectState imgui_internal.lua:2026 [ ] GetCurrentTabBar - [x] 35% TabBarGetTabOrder imgui_widgets.lua:9885 + [x] 35% TabBarGetTabOrder imgui_widgets.lua:11210 [-] RenderNavHighlight obsolete - [x] 100% TempInputIsActive imgui_internal.lua:3443 - [x] 88% GetInputTextState imgui_internal.lua:3451 + [x] 100% TempInputIsActive imgui_internal.lua:3638 + [x] 88% GetInputTextState imgui_internal.lua:3646 [ ] IsItemActiveAsInputText - [x] 80% SetNextItemColorMarker imgui_internal.lua:1036 + [x] 80% SetNextItemColorMarker imgui_internal.lua:1055 -- ImFontLoader - [x] 6% ImFontLoader::ImFontLoader imgui_internal.lua:2927 + [x] 6% ImFontLoader::ImFontLoader imgui_internal.lua:3122 -- ImFontAtlas internal API [-] operator== c++-only [-] operator!= c++-only - [x] 75% ImFontAtlasRectId_GetIndex imgui_internal.lua:2979 - [x] 62% ImFontAtlasRectId_GetGeneration imgui_internal.lua:2983 - [x] 77% ImFontAtlasRectId_Make imgui_internal.lua:2988 - [x] 19% ImFontAtlasBuilder::ImFontAtlasBuilder imgui_internal.lua:1134 + [x] 75% ImFontAtlasRectId_GetIndex imgui_internal.lua:3174 + [x] 62% ImFontAtlasRectId_GetGeneration imgui_internal.lua:3178 + [x] 77% ImFontAtlasRectId_Make imgui_internal.lua:3183 + [x] 19% ImFontAtlasBuilder::ImFontAtlasBuilder imgui_internal.lua:1153 ==================================================================================================== API declarations not ported (by upstream line) ==================================================================================================== - Public API (imgui.h, namespace ImGui): 365 / 376 + Public API (imgui.h, namespace ImGui): 368 / 376 -- Dear ImGui end-user API functions - [ ] ImGui::BeginMultiSelect - [ ] ImGui::EndMultiSelect - [ ] ImGui::SetNextItemSelectionUserData [ ] ImGui::SetTooltipV [ ] ImGui::SetItemTooltipV [ ] ImGui::DebugFlashStyleColor @@ -2374,7 +2371,7 @@ API declarations not ported (by upstream line) [ ] ImGui::GetAllocatorFunctions [ ] ImGui::FindViewportByPlatformHandle - Internal API (imgui_internal.h, namespace ImGui): 405 / 443 + Internal API (imgui_internal.h, namespace ImGui): 417 / 443 -- ImGui internal API [ ] ImGui::RegisterUserTexture [ ] ImGui::UnregisterUserTexture @@ -2384,16 +2381,6 @@ API declarations not ported (by upstream line) [ ] ImGui::RemoveContextHook [ ] ImGui::CallContextHooks [ ] ImGui::TestShortcutRouting - [ ] ImGui::GetTypingSelectRequest - [ ] ImGui::TypingSelectFindMatch - [ ] ImGui::TypingSelectFindNextSingleCharMatch - [ ] ImGui::TypingSelectFindBestLeadingMatch - [ ] ImGui::BeginBoxSelect - [ ] ImGui::EndBoxSelect - [ ] ImGui::MultiSelectItemHeader - [ ] ImGui::MultiSelectItemFooter - [ ] ImGui::MultiSelectAddSetAll - [ ] ImGui::MultiSelectAddSetRange [ ] ImGui::GetWindowResizeCornerID [ ] ImGui::GetWindowResizeBorderID [ ] ImGui::DemoMarker @@ -2410,8 +2397,6 @@ API declarations not ported (by upstream line) [ ] ImGui::DebugTextureIDToU64 [ ] ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox [ ] ImGui::DebugNodeInputTextState - [ ] ImGui::DebugNodeTypingSelectState - [ ] ImGui::DebugNodeMultiSelectState [ ] ImGui::DebugNodePlatformMonitor [ ] ImGui::DebugRenderKeyboardPreview @@ -2465,7 +2450,7 @@ Enums (values ported / total; missing listed) ImGuiMouseButton_ 4 / 4 ImGuiMouseCursor_ 13 / 13 ImGuiMouseSource 4 / 4 - ImGuiMultiSelectFlags_ 0 / 21 missing: ImGuiMultiSelectFlags_None, ImGuiMultiSelectFlags_SingleSelect, ImGuiMultiSelectFlags_NoSelectAll, ImGuiMultiSelectFlags_NoRangeSelect, ImGuiMultiSelectFlags_NoAutoSelect, ImGuiMultiSelectFlags_NoAutoClear, ImGuiMultiSelectFlags_NoAutoClearOnReselect, ImGuiMultiSelectFlags_BoxSelect1d, ImGuiMultiSelectFlags_BoxSelect2d, ImGuiMultiSelectFlags_BoxSelectNoScroll, ImGuiMultiSelectFlags_ClearOnEscape, ImGuiMultiSelectFlags_ClearOnClickVoid, ImGuiMultiSelectFlags_ScopeWindow, ImGuiMultiSelectFlags_ScopeRect, ImGuiMultiSelectFlags_SelectOnAuto, ImGuiMultiSelectFlags_SelectOnClickAlways, ImGuiMultiSelectFlags_SelectOnClickRelease, ImGuiMultiSelectFlags_NavWrapX, ImGuiMultiSelectFlags_NoSelectOnRightClick, ImGuiMultiSelectFlags_SelectOnMask_, ImGuiMultiSelectFlags_CheckboxMode_ + ImGuiMultiSelectFlags_ 21 / 21 ImGuiNavLayer 3 / 3 ImGuiNavMoveFlags_ 18 / 18 ImGuiNavRenderCursorFlags_ 3 / 3 @@ -2478,7 +2463,7 @@ Enums (values ported / total; missing listed) ImGuiScrollFlags_ 10 / 10 ImGuiSelectableFlagsPrivate_ 7 / 7 ImGuiSelectableFlags_ 8 / 8 - ImGuiSelectionRequestType 0 / 3 missing: ImGuiSelectionRequestType_None, ImGuiSelectionRequestType_SetAll, ImGuiSelectionRequestType_SetRange + ImGuiSelectionRequestType 3 / 3 ImGuiSeparatorFlags_ 4 / 4 ImGuiSliderFlagsPrivate_ 2 / 2 ImGuiSliderFlags_ 11 / 11 @@ -2496,7 +2481,7 @@ Enums (values ported / total; missing listed) ImGuiTooltipFlags_ 2 / 2 ImGuiTreeNodeFlagsPrivate_ 5 / 5 ImGuiTreeNodeFlags_ 22 / 22 - ImGuiTypingSelectFlags_ 0 / 3 missing: ImGuiTypingSelectFlags_None, ImGuiTypingSelectFlags_AllowBackspace, ImGuiTypingSelectFlags_AllowSingleCharMode + ImGuiTypingSelectFlags_ 3 / 3 ImGuiViewportFlags_ 15 / 15 ImGuiWindowBgClickFlags_ 2 / 2 ImGuiWindowDockStyleCol 10 / 10 @@ -2517,7 +2502,7 @@ Struct fields (name found in the port / total; missing listed). Field names are ExampleTreeNode 9 / 9 IM_MSVC_RUNTIME_CHECKS_OFF 7 / 7 IM_MSVC_RUNTIME_CHECKS_RESTORE 79 / 81 missing: CurveTessellationMaxError, DataEnd - ImBitVector 0 / 1 missing: Storage + ImBitVector 1 / 1 ImDrawChannel 2 / 2 ImDrawCmd 9 / 9 ImDrawCmdHeader 3 / 3 @@ -2539,10 +2524,10 @@ Struct fields (name found in the port / total; missing listed). Field names are ImFontGlyphRangesBuilder 0 / 1 missing: UsedChars ImFontLoader 2 / 2 ImFontStackData 3 / 3 - ImGuiBoxSelectState 3 / 16 missing: IsActive, IsStarting, IsStartedFromVoid, IsStartedSetNavIdOnce, RequestClear, StartPosRel, EndPosRel, ScrollAccum, UnclipMode, UnclipRect, UnclipRects, BoxSelectRectPrev, BoxSelectRectCurr + ImGuiBoxSelectState 16 / 16 ImGuiColorMod 2 / 2 ImGuiComboPreviewData 6 / 9 missing: BackupWorkRectMaxX, BackupContentRectMaxX, WithinPreview - ImGuiContext 306 / 338 missing: TestEngineHookItems, CurrentPixelDensity, TestEngine, InputEventsTrail, MixedValueLabel, DebugBreakInShortcutRouting, DebugLocateId, NavJustMovedToIsInit, DragDropPayloadBufHeap, DragDropPayloadBufLocal, BoxSelectState, CurrentMultiSelect, MultiSelectTempDataStacked, MultiSelectTempData, MultiSelectStorage, ClipboardHandlerData, TypingSelectState, SessionDate, DemoMarkerCallback, DebugLogIndex, DebugLogSkippedErrors, DebugLocateFrames, DebugBreakInLocateId, DebugBreakKeyChord, DebugItemPickerActive, DebugItemPickerMouseButton, DebugItemPickerBreakId, DebugFlashStyleColorTime, DebugFlashStyleColorBackup, DebugDrawIdConflictsAliveCount, DebugDrawIdConflictsHighlightSet, TempKeychordName + ImGuiContext 312 / 338 missing: TestEngineHookItems, CurrentPixelDensity, TestEngine, InputEventsTrail, MixedValueLabel, DebugBreakInShortcutRouting, DebugLocateId, NavJustMovedToIsInit, DragDropPayloadBufHeap, DragDropPayloadBufLocal, ClipboardHandlerData, SessionDate, DemoMarkerCallback, DebugLogIndex, DebugLogSkippedErrors, DebugLocateFrames, DebugBreakInLocateId, DebugBreakKeyChord, DebugItemPickerActive, DebugItemPickerMouseButton, DebugItemPickerBreakId, DebugFlashStyleColorTime, DebugFlashStyleColorBackup, DebugDrawIdConflictsAliveCount, DebugDrawIdConflictsHighlightSet, TempKeychordName ImGuiContextHook 2 / 5 missing: HookId, Owner, Callback ImGuiDataTypeInfo 4 / 4 ImGuiDataTypeStorage 1 / 1 @@ -2585,9 +2570,9 @@ Struct fields (name found in the port / total; missing listed). Field names are ImGuiLocEntry 2 / 2 ImGuiMenuColumns 8 / 8 ImGuiMetricsConfig 15 / 17 missing: SettingsDiscardMonths, SettingsHighlightOldEntries - ImGuiMultiSelectIO 2 / 6 missing: RangeSrcItem, NavIdItem, NavIdSelected, RangeSrcReset - ImGuiMultiSelectState 3 / 8 missing: LastSelectionSize, RangeSelected, NavIdSelected, RangeSrcItem, NavIdItem - ImGuiMultiSelectTempData 6 / 16 missing: Storage, ScopeRectMin, BoxSelectId, LoopRequestSetAll, IsEndIO, IsKeyboardSetRange, NavIdPassedBy, RangeSrcPassedBy, RangeDstPassedBy, IsSoleOrUnknownSelectionSize + ImGuiMultiSelectIO 6 / 6 + ImGuiMultiSelectState 8 / 8 + ImGuiMultiSelectTempData 16 / 16 ImGuiNavItemData 9 / 9 ImGuiNextItemData 12 / 12 ImGuiNextWindowData 25 / 25 @@ -2604,9 +2589,9 @@ Struct fields (name found in the port / total; missing listed). Field names are ImGuiPtrOrIndex 1 / 2 missing: Ptr ImGuiResizeBorderDef 4 / 4 ImGuiResizeGripDef 4 / 4 - ImGuiSelectionBasicStorage 2 / 5 missing: PreserveOrder, _SelectionOrder, _Storage + ImGuiSelectionBasicStorage 5 / 5 ImGuiSelectionExternalStorage 1 / 1 - ImGuiSelectionRequest 2 / 5 missing: RangeDirection, RangeFirstItem, RangeLastItem + ImGuiSelectionRequest 5 / 5 ImGuiSettingsCleanupArgs 0 / 7 missing: TypeHashFilter, DiscardOlderThanMonths, DiscardWhenMissingDate, DiscardAll, SetCurrentSessionDateToAll, SetCurrentSessionDateWhenMissingDate, _DiscardOlderThanDate ImGuiSettingsHandler 3 / 3 ImGuiShrinkWidthItem 3 / 3 @@ -2636,8 +2621,8 @@ Struct fields (name found in the port / total; missing listed). Field names are ImGuiTextFilterItem 1 / 4 missing: Begin, Len, MatchIncludesMask ImGuiTextIndex 2 / 2 ImGuiTreeNodeStackData 7 / 7 - ImGuiTypingSelectRequest 1 / 6 missing: SearchBufferLen, SearchBuffer, SelectRequest, SingleCharMode, SingleCharSize - ImGuiTypingSelectState 0 / 6 missing: Request, SearchBuffer, FocusScope, LastRequestFrame, LastRequestTime, SingleCharModeLock + ImGuiTypingSelectRequest 6 / 6 + ImGuiTypingSelectState 6 / 6 ImGuiViewport 19 / 20 missing: PlatformIconData ImGuiViewportP 22 / 23 missing: BgFgDrawListsLastFrameActive ImGuiWindow 124 / 125 missing: NameBufLen diff --git a/imgui.lua b/imgui.lua index 801b8dd..1595c50 100644 --- a/imgui.lua +++ b/imgui.lua @@ -10828,6 +10828,8 @@ function ImGui.GcCompactTransientMiscBuffers() g.ItemFlagsStack:clear() g.GroupStack:clear() g.InputTextLineIndex:clear() + g.MultiSelectTempDataStacked = 0 + g.MultiSelectTempData:clear() ImGui.TableGcCompactSettings() for _, atlas in g.FontAtlases:iter() do atlas:CompactCache() @@ -14324,6 +14326,8 @@ function ImGui.Shutdown() -- TODO: -- Clear everything else + g.MultiSelectStorage:Clear() + g.MultiSelectTempData:clear() g.MenusIdSubmittedThisFrame:clear() end @@ -19700,7 +19704,7 @@ local function ImGuiListClipper_StepInternal(clipper) -- Add box selection range local bs = g.BoxSelectState - if bs and bs.IsActive and bs.Window == window then + if bs.IsActive and bs.Window == window then -- FIXME: Selectable() use of half-ItemSpacing isn't consistent in matter of layout, as ItemAdd(bb) stray above ItemSize()'s CursorPos. -- RangeSelect's BoxSelect relies on comparing overlap of previous and current rectangle and is sensitive to that. -- As a workaround we currently half ItemSpacing worth on each side. @@ -19846,7 +19850,7 @@ function ImGui.ErrorRecoveryTryToRecoverWindowState(state_in) IM_ASSERT_USER_ERROR(false, "Missing EndTabBar()") ImGui.EndTabBar() end - while g.CurrentMultiSelect ~= nil and g.CurrentMultiSelect.Storage.Window == window and ImGui.EndMultiSelect do + while g.CurrentMultiSelect ~= nil and g.CurrentMultiSelect.Storage.Window == window do --V1044 IM_ASSERT_USER_ERROR(false, "Missing EndMultiSelect()") ImGui.EndMultiSelect() end @@ -21183,6 +21187,25 @@ function ImGui.ShowMetricsWindow(p_open) ImGui.TreePop() end + -- Details for TypingSelect + if ImGui.TreeNode("TypingSelect", "TypingSelect (%d)", (#g.TypingSelectState.SearchBuffer ~= 0) and 1 or 0) then + ImGui.DebugNodeTypingSelectState(g.TypingSelectState) + ImGui.TreePop() + end + + -- Details for MultiSelect + if ImGui.TreeNode("MultiSelect", "MultiSelect (%d)", g.MultiSelectStorage.AliveCount) then + local bs = g.BoxSelectState + ImGui.BulletText("BoxSelect ID=0x%08X, Starting = %d, Active %d", bs.ID, B(bs.IsStarting), B(bs.IsActive)) + for n = 0, g.MultiSelectStorage:GetMapSize() - 1 do + local state = g.MultiSelectStorage:GetByIndex(n) + if state then + ImGui.DebugNodeMultiSelectState(state) + end + end + ImGui.TreePop() + end + -- Details for Docking if ImGui.TreeNode("Docking") then local dc = g.DockContext @@ -21310,6 +21333,7 @@ function ImGui.ShowMetricsWindow(p_open) ImGui.Indent() ImGui.Text("NavWindow: '%s'", N(g.NavWindow)) ImGui.Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer) + ImGui.Text("NavLastValidSelectionUserData = %s", tostring(g.NavLastValidSelectionUserData)) -- [Port] any Lua value: tostring() instead of %lld (0x%llX) ImGui.Text("NavActive: %d, NavVisible: %d", B(io.NavActive), B(io.NavVisible)) ImGui.Text("NavActivateId/DownId/PressedId: %08X/%08X/%08X", g.NavActivateId, g.NavActivateDownId or 0, g.NavActivatePressedId or 0) ImGui.Text("NavActivateFlags: %04X", g.NavActivateFlags or 0) diff --git a/imgui_h.lua b/imgui_h.lua index d23e611..c4086aa 100644 --- a/imgui_h.lua +++ b/imgui_h.lua @@ -2713,6 +2713,122 @@ function ImColorMT:U32() return ImGui.ColorConvertFloat4ToU32(self.Value) end function ImColorMT:SetHSV(h, s, v, a) self.Value.x, self.Value.y, self.Value.z = ImGui.ColorConvertHSVtoRGB(h, s, v); self.Value.w = a or 1.0 end function ImColor.HSV(h, s, v, a) local r, g, b = ImGui.ColorConvertHSVtoRGB(h, s, v); return ImColor(r, g, b, a or 1.0) end +------------------------------------------------------------------------------- +-- [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiSelectionRequestType, ImGuiSelectionRequest, ImGuiMultiSelectIO, ImGuiSelectionBasicStorage) +------------------------------------------------------------------------------- + +-- Multi-selection system +-- Documentation at: https://github.com/ocornut/imgui/wiki/Multi-Select +-- - Refer to 'Demo->Widgets->Selection State & Multi-Select' for demos using this. +-- - This system implements standard multi-selection idioms (Ctrl+Mouse/Keyboard, Shift+Mouse/Keyboard, etc) +-- with support for clipper (skipping non-visible items), box-select and many other details. +-- - Selectable(), Checkbox() are supported but custom widgets may use it as well. +-- - TreeNode() is technically supported but... using this correctly is more complicated: you need some sort of linear/random access to your tree, +-- which is suited to advanced trees setups also implementing filters and clipper. We will work toward simplifying and demoing it. +-- - In the spirit of Dear ImGui design, your code owns actual selection data. +-- This is designed to allow all kinds of selection storage you may use in your application e.g. set/map/hash. +-- About ImGuiSelectionBasicStorage: +-- - This is an optional helper to store a selection state and apply selection requests. +-- - It is used by our demos and provided as a convenience to quickly implement multi-selection. +-- Usage: +-- - Identify submitted items with SetNextItemSelectionUserData(), most likely using an index into your current data-set. +-- - Store and maintain actual selection data using persistent object identifiers. +-- - Usage flow: +-- BEGIN - (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO result. +-- - (2) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 6. +-- - (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. Calculate its index and pass to clipper:IncludeItemByIndex(). If storing indices in ImGuiSelectionUserData, a simple clipper:IncludeItemByIndex(ms_io.RangeSrcItem) call will work. +-- LOOP - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls. +-- END - (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO result. +-- - (6) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 2. +-- If you submit all items (no clipper), Step 2 and 3 are optional and will be handled by each item themselves. It is fine to always honor those steps. +-- About ImGuiSelectionUserData: +-- - This can store an application-defined identifier (e.g. index or pointer) submitted via SetNextItemSelectionUserData(). +-- - In return we store them into RangeSrcItem/RangeFirstItem/RangeLastItem and other fields in ImGuiMultiSelectIO. +-- - Most applications will store an object INDEX, hence the chosen name and type. Storing an index is natural, because +-- SetRange requests will give you two end-points and you will need to iterate/interpolate between them to update your selection. +-- - However it is perfectly possible to store a POINTER or another IDENTIFIER inside ImGuiSelectionUserData. +-- Our system never assume that you identify items by indices, it never attempts to interpolate between two values. +-- - If you enable ImGuiMultiSelectFlags.NoRangeSelect then it is guaranteed that you will never have to interpolate +-- between two ImGuiSelectionUserData, which may be a convenient way to use part of the feature with less code work. +-- - As most users will want to store an index, for convenience and to reduce confusion we use ImS64 instead of void*, +-- being syntactically easier to downcast. Feel free to reinterpret_cast and store a pointer inside. +-- [Port] ImGuiSelectionUserData is any Lua value compared with '==' (an integer index, or e.g. a table reference instead of a pointer). +-- -1 (ImGuiSelectionUserData_Invalid) is reserved. ImGuiSelectionBasicStorage/ImGuiSelectionExternalStorage require integer indices. + +-- Flags for BeginMultiSelect() +--- @enum ImGuiMultiSelectFlags +ImGuiMultiSelectFlags = { + None = 0, + SingleSelect = bitLShift(1, 0), -- Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho! + NoSelectAll = bitLShift(1, 1), -- Disable Ctrl+A shortcut to select all. + NoRangeSelect = bitLShift(1, 2), -- Disable Shift+selection mouse/keyboard support (useful for unordered 2D selection). With BoxSelect is also ensure contiguous SetRange requests are not combined into one. This allows not handling interpolation in SetRange requests. + NoAutoSelect = bitLShift(1, 3), -- Disable selecting items when navigating (useful for e.g. supporting range-select in a list of checkboxes). + NoAutoClear = bitLShift(1, 4), -- Disable clearing selection when navigating or selecting another one (generally used with ImGuiMultiSelectFlags.NoAutoSelect. useful for e.g. supporting range-select in a list of checkboxes). + NoAutoClearOnReselect = bitLShift(1, 5), -- Disable clearing selection when clicking/selecting an already selected item. + BoxSelect1d = bitLShift(1, 6), -- Enable box-selection with same width and same x pos items (e.g. full row Selectable()). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space. + BoxSelect2d = bitLShift(1, 7), -- Enable box-selection with varying width or varying x pos items support (e.g. different width labels, or 2D layout/grid). This is slower: alters clipping logic so that e.g. horizontal movements will update selection of normally clipped items. + BoxSelectNoScroll = bitLShift(1, 8), -- Disable scrolling when box-selecting and moving mouse near edges of scope. + ClearOnEscape = bitLShift(1, 9), -- Clear selection when pressing Escape while scope is focused. + ClearOnClickVoid = bitLShift(1, 10), -- Clear selection when clicking on empty location within scope. + ScopeWindow = bitLShift(1, 11), -- Scope for _BoxSelect and _ClearOnClickVoid is whole window (Default). Use if BeginMultiSelect() covers a whole window or used a single time in same window. + ScopeRect = bitLShift(1, 12), -- Scope for _BoxSelect and _ClearOnClickVoid is rectangle encompassing BeginMultiSelect()/EndMultiSelect(). Use if BeginMultiSelect() is called multiple times in same window. + SelectOnAuto = bitLShift(1, 13), -- Apply selection on mouse down when clicking on unselected item, on mouse up when clicking on selected item. (Default) + SelectOnClickAlways = bitLShift(1, 14), -- Apply selection on mouse down when clicking on any items. Prevents Drag and Drop from being used on multiple-selection, but allows e.g. BoxSelect to always reselect even when clicking inside an existing selection. (Excel style behavior) + SelectOnClickRelease = bitLShift(1, 15), -- Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection. + --RangeSelect2d = bitLShift(1, 15), -- Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does. + NavWrapX = bitLShift(1, 16), -- [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one. + NoSelectOnRightClick = bitLShift(1, 17), -- Disable default right-click processing, which selects item on mouse down, and is designed for context-menus. + CheckboxMode_ = bitLShift(1, 20), -- [Internal] +} +ImGuiMultiSelectFlags.SelectOnMask_ = bit32.bor(ImGuiMultiSelectFlags.SelectOnAuto, ImGuiMultiSelectFlags.SelectOnClickAlways, ImGuiMultiSelectFlags.SelectOnClickRelease) +ImGuiMultiSelectFlags.SelectOnClick = ImGuiMultiSelectFlags.SelectOnAuto -- RENAMED in 1.92.6 + +-- Main IO structure returned by BeginMultiSelect()/EndMultiSelect(). +-- This mainly contains a list of selection requests. +-- - Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen. +-- - Some fields are only useful if your list is dynamic and allows deletion (getting post-deletion focus/state right is shown in the demo) +-- - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code. +--- @class ImGuiMultiSelectIO +--- @field Requests ImVector # ms:w, app:r / ms:w app:r // Requests to apply to your selection data. (1-based .Data) +--- @field RangeSrcItem ImGuiSelectionUserData # ms:w app:r / // (If using clipper) Begin: Source item (often the first selected item) must never be clipped: use clipper:IncludeItemByIndex() to ensure it is submitted. +--- @field NavIdItem ImGuiSelectionUserData # ms:w, app:r / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items). +--- @field NavIdSelected bool # ms:w, app:r / app:r // (If using deletion) Last known selection state for NavId (if part of submitted items). +--- @field RangeSrcReset bool # app:w / ms:r // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection). +--- @field ItemsCount int # ms:w, app:r / app:r // 'int items_count' parameter to BeginMultiSelect() is copied here for convenience, allowing simpler calls to your ApplyRequests handler. Not used internally. + +-- Selection request type +--- @enum ImGuiSelectionRequestType +ImGuiSelectionRequestType = { + None = 0, + SetAll = 1, -- Request app to clear selection (if Selected==false) or select all items (if Selected==true). We cannot set RangeFirstItem/RangeLastItem as its contents is entirely up to user (not necessarily an index) + SetRange = 2, -- Request app to select/unselect [RangeFirstItem..RangeLastItem] items (inclusive) based on value of Selected. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false. +} + +-- Selection request item +--- @class ImGuiSelectionRequest +--- @field Type ImGuiSelectionRequestType # ms:w, app:r / ms:w, app:r // Request type. You'll most often receive 1 Clear + 1 SetRange with a single-item range. +--- @field Selected bool # ms:w, app:r / ms:w, app:r // Parameter for SetAll/SetRange requests (true = select, false = unselect) +--- @field RangeDirection int # / ms:w app:r // Parameter for SetRange request: +1 when RangeFirstItem comes before RangeLastItem, -1 otherwise. Useful if you want to preserve selection order on a backward Shift+Click. +--- @field RangeFirstItem ImGuiSelectionUserData # / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom). +--- @field RangeLastItem ImGuiSelectionUserData # / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top). Inclusive! +function ImGuiSelectionRequest() + return { Type = ImGuiSelectionRequestType.None, Selected = false, RangeDirection = 0, RangeFirstItem = -1, RangeLastItem = -1 } +end +-- [Port] ImVector copy function: push_back() copies fields into the preallocated element (C++ copies by value), +-- so callers can push a reused scratch request without allocating. +function ImGuiSelectionRequest_Copy(t, k, v) + local dst = t[k] + dst.Type = v.Type; dst.Selected = v.Selected; dst.RangeDirection = v.RangeDirection; dst.RangeFirstItem = v.RangeFirstItem; dst.RangeLastItem = v.RangeLastItem +end + +--- @return ImGuiMultiSelectIO +function ImGuiMultiSelectIO() + return { Requests = ImVector(ImGuiSelectionRequest, ImGuiSelectionRequest_Copy), RangeSrcItem = -1, NavIdItem = -1, NavIdSelected = false, RangeSrcReset = false, ItemsCount = 0 } +end + +-- ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage: see [SECTION] Widgets: Multi-Select helpers in imgui_widgets.lua +-- (constructors and methods are defined there, like upstream's out-of-line definitions in imgui_widgets.cpp). + --- @class ImGuiSettingsHandler --- Fns take (ctx, handler, ...) like upstream. ReadOpenFn(ctx, handler, name) returns entry; ReadLineFn(ctx, handler, entry, line); --- WriteAllFn(ctx, handler, buf: ImGuiTextBuffer) diff --git a/imgui_internal.lua b/imgui_internal.lua index 1c51f02..ba3505e 100644 --- a/imgui_internal.lua +++ b/imgui_internal.lua @@ -386,6 +386,16 @@ end --@hoist function ImSaturate(f) return ((f < 0.0 and 0.0) or (f > 1.0 and 1.0) or f) end +--- @param x float +--- @return float # Sign operator - returns -1, 0 or 1 based on sign of argument +function ImSign(x) if x < 0.0 then return -1.0 elseif x > 0.0 then return 1.0 end return 0.0 end + +function ImLinearRemapClamp(s0, s1, d0, d1, x) return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0 end + +--- @param c int # byte +--- @return int +function ImToUpper(c) if c >= 97 and c <= 122 then return bit32.band(c, bit32.bnot(32)) end return c end + IM_F32_TO_INT8_UNBOUND = function(val) return math.floor(val * 255.0 + (val >= 0 and 0.5 or -0.5)) end -- Unsaturated, for display purpose IM_F32_TO_INT8_SAT = function(val) return math.floor(ImSaturate(val) * 255.0 + 0.5) end -- Saturated, always output 0..255 @@ -658,6 +668,12 @@ end --- @nodiscard function ImGui.WindowPosAbsToRel(window, p) local off = window.DC.CursorStartPos return ImVec2(p.x - off.x, p.y - off.y) end +--- @param window ImGuiWindow +--- @param p ImVec2 +--- @return ImVec2 +--- @nodiscard +function ImGui.WindowPosRelToAbs(window, p) local off = window.DC.CursorStartPos return ImVec2(p.x + off.x, p.y + off.y) end + --- @param c char --- @return bool # True if this character is a ' ' or '\t' function ImCharIsBlankA(c) return c == 32 or c == 9 end @@ -880,6 +896,9 @@ function IM_RECT:Add(p) end end +function IM_RECT:AddX(x) if self.Min.x > x then self.Min.x = x end if self.Max.x < x then self.Max.x = x end end +function IM_RECT:AddY(y) if self.Min.y > y then self.Min.y = y end if self.Max.y < y then self.Max.y = y end end + --- @param amount float|ImVec2 @native function IM_RECT:Expand(amount) @@ -1848,6 +1867,164 @@ local function ImGuiNavItemData() return this end +------------------------------------------------------------------------------- +-- [SECTION] Typing-select support +------------------------------------------------------------------------------- + +-- Flags for GetTypingSelectRequest() +--- @enum ImGuiTypingSelectFlags +ImGuiTypingSelectFlags = { + None = 0, + AllowBackspace = bitLShift(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) + AllowSingleCharMode = bitLShift(1, 1), -- Allow "single char" search mode which is activated when pressing the same character multiple times. +} + +-- Returned by GetTypingSelectRequest(), designed to eventually be public. +--- @class ImGuiTypingSelectRequest +--- @field Flags ImGuiTypingSelectFlags # Flags passed to GetTypingSelectRequest() +--- @field SearchBufferLen int +--- @field SearchBuffer string # Search buffer contents (use full string. unless SingleCharMode is set, in which case use SingleCharSize). +--- @field SelectRequest bool # Set when buffer was modified this frame, requesting a selection. +--- @field SingleCharMode bool # Notify when buffer contains same character repeated, to implement special mode. In this situation it preferred to not display any on-screen search indication. +--- @field SingleCharSize int # Length in bytes of first letter codepoint (1 for ascii, 2-4 for UTF-8). If (SearchBufferLen==RepeatCharSize) only 1 letter has been input. +local function ImGuiTypingSelectRequest() + return { Flags = 0, SearchBufferLen = 0, SearchBuffer = "", SelectRequest = false, SingleCharMode = false, SingleCharSize = 0 } +end + +-- Storage for GetTypingSelectRequest() +-- [Port] SearchBuffer is an immutable Lua string (C++: char[64], 63 bytes max + zero terminator). SearchBuffer[0] != 0 -> #SearchBuffer ~= 0. +--- @class ImGuiTypingSelectState +--- @field Request ImGuiTypingSelectRequest # User-facing data +--- @field SearchBuffer string # Search buffer: no need to make dynamic as this search is very transient. +--- @field FocusScope ImGuiID +--- @field LastRequestFrame int +--- @field LastRequestTime float +--- @field SingleCharModeLock bool # After a certain single char repeat count we lock into SingleCharMode. Two benefits: 1) buffer never fill, 2) we can provide an immediate SingleChar mode without timer elapsing. +MT.ImGuiTypingSelectState = {} +MT.ImGuiTypingSelectState.__index = MT.ImGuiTypingSelectState + +--- @return ImGuiTypingSelectState +function ImGuiTypingSelectState() + return setmetatable({ Request = ImGuiTypingSelectRequest(), SearchBuffer = "", FocusScope = 0, LastRequestFrame = 0, LastRequestTime = 0.0, SingleCharModeLock = false }, MT.ImGuiTypingSelectState) +end +function MT.ImGuiTypingSelectState:Clear() self.SearchBuffer = ""; self.SingleCharModeLock = false end -- We preserve remaining data for easier debugging + +------------------------------------------------------------------------------- +-- [SECTION] Box-select support +------------------------------------------------------------------------------- + +--- @class ImGuiBoxSelectState +--- @field ID ImGuiID +--- @field IsActive bool +--- @field IsStarting bool +--- @field IsStartedFromVoid bool # Starting click was not from an item. +--- @field IsStartedSetNavIdOnce bool +--- @field RequestClear bool +--- @field KeyMods ImGuiKeyChord # Latched key-mods for box-select logic. +--- @field StartPosRel ImVec2 # Start position in window-contents relative space (to support scrolling) +--- @field EndPosRel ImVec2 # End position in window-contents relative space +--- @field ScrollAccum ImVec2 # Scrolling accumulator (to behave at high-frame spaces) +--- @field Window ImGuiWindow? +--- @field UnclipMode bool # (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select. +--- @field UnclipRect ImRect # Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets. +--- @field UnclipRects ImRect[] # Per-axis versions. [Port] 1-based: [1] = upstream [0] (X), [2] = upstream [1] (Y) +--- @field BoxSelectRectPrev ImRect # Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos) +--- @field BoxSelectRectCurr ImRect +--- @return ImGuiBoxSelectState +function ImGuiBoxSelectState() + return { + -- Active box-selection data (persistent, 1 active at a time) + ID = 0, IsActive = false, IsStarting = false, IsStartedFromVoid = false, IsStartedSetNavIdOnce = false, RequestClear = false, + KeyMods = 0, + StartPosRel = ImVec2(), EndPosRel = ImVec2(), ScrollAccum = ImVec2(), + Window = nil, + + -- Temporary/Transient data + UnclipMode = false, + UnclipRect = ImRect(), + UnclipRects = { ImRect(), ImRect() }, + BoxSelectRectPrev = ImRect(), + BoxSelectRectCurr = ImRect(), + } +end + +------------------------------------------------------------------------------- +-- [SECTION] Multi-select support +------------------------------------------------------------------------------- + +-- We always assume that -1 is an invalid value (which works for indices and pointers) +-- (ImGuiSelectionUserData_Invalid is defined above with the ImGuiSelectionUserData alias) + +-- Temporary storage for multi-select +--- @class ImGuiMultiSelectTempData +--- @field IO ImGuiMultiSelectIO # MUST BE FIRST FIELD. Requests are set and returned by BeginMultiSelect()/EndMultiSelect() + written to by user during the loop. +--- @field Storage ImGuiMultiSelectState? +--- @field FocusScopeId ImGuiID # Copied from g.CurrentFocusScopeId (unless another selection scope was pushed manually) +--- @field Flags ImGuiMultiSelectFlags +--- @field ScopeRectMin ImVec2 +--- @field BackupCursorMaxPos ImVec2 +--- @field BoxSelectId ImGuiID +--- @field KeyMods ImGuiKeyChord +--- @field LoopRequestSetAll int # -1: no operation, 0: clear all, 1: select all. +--- @field IsEndIO bool # Set when switching IO from BeginMultiSelect() to EndMultiSelect() state. +--- @field IsFocused bool # Set if currently focusing the selection scope (any item of the selection). May be used if you have custom shortcut associated to selection. +--- @field IsKeyboardSetRange bool # Set by BeginMultiSelect() when using Shift+Navigation. Because scrolling may be affected we can't afford a frame of lag with Shift+Navigation. +--- @field NavIdPassedBy bool +--- @field RangeSrcPassedBy bool # Set by the item that matches RangeSrcItem. +--- @field RangeDstPassedBy bool # Set by the item that matches NavJustMovedToId when IsSetRange is set. +--- @field IsSoleOrUnknownSelectionSize bool +MT.ImGuiMultiSelectTempData = {} +MT.ImGuiMultiSelectTempData.__index = MT.ImGuiMultiSelectTempData + +--- @return ImGuiMultiSelectTempData +function ImGuiMultiSelectTempData() + local this = setmetatable({ IO = ImGuiMultiSelectIO(), ScopeRectMin = ImVec2(), BackupCursorMaxPos = ImVec2() }, MT.ImGuiMultiSelectTempData) + this:Clear() + return this +end +-- Zero-clear except IO as we preserve IO.Requests[] buffer allocation. +function MT.ImGuiMultiSelectTempData:Clear() + self:ClearIO() + self.Storage = nil + self.FocusScopeId = 0 + self.Flags = 0 + ImVec2_CopyV(self.ScopeRectMin, 0.0, 0.0) + ImVec2_CopyV(self.BackupCursorMaxPos, 0.0, 0.0) + self.BoxSelectId = 0 + self.KeyMods = 0 + self.LoopRequestSetAll = 0 + self.IsEndIO = false + self.IsFocused = false + self.IsKeyboardSetRange = false + self.NavIdPassedBy = false + self.RangeSrcPassedBy = false + self.RangeDstPassedBy = false + self.IsSoleOrUnknownSelectionSize = false +end +function MT.ImGuiMultiSelectTempData:ClearIO() local io = self.IO; io.Requests:resize(0); io.RangeSrcItem = ImGuiSelectionUserData_Invalid; io.NavIdItem = ImGuiSelectionUserData_Invalid; io.NavIdSelected = false; io.RangeSrcReset = false end + +-- Persistent storage for multi-select (as long as selection is alive) +--- @class ImGuiMultiSelectState +--- @field Window ImGuiWindow? +--- @field ID ImGuiID +--- @field LastFrameActive int # Last used frame-count, for GC. +--- @field LastSelectionSize int # Set by BeginMultiSelect() based on optional info provided by user. May be -1 if unknown. +--- @field RangeSelected int # -1 (don't have) or true/false +--- @field NavIdSelected int # -1 (don't have) or true/false +--- @field RangeSrcItem ImGuiSelectionUserData # +--- @field NavIdItem ImGuiSelectionUserData # SetNextItemSelectionUserData() value for NavId (if part of submitted items) +--- @return ImGuiMultiSelectState +function ImGuiMultiSelectState() + return { Window = nil, ID = 0, LastFrameActive = 0, LastSelectionSize = 0, RangeSelected = -1, NavIdSelected = -1, RangeSrcItem = ImGuiSelectionUserData_Invalid, NavIdItem = ImGuiSelectionUserData_Invalid } +end + +--- @param id ImGuiID +--- @return ImGuiBoxSelectState? +function ImGui.GetBoxSelectState(id) local g = GImGui; if id ~= 0 and g.BoxSelectState.ID == id and g.BoxSelectState.IsActive then return g.BoxSelectState end return nil end +--- @param id ImGuiID +--- @return ImGuiMultiSelectState? +function ImGui.GetMultiSelectState(id) local g = GImGui; return g.MultiSelectStorage:GetByKey(id) end + ------------------------------------------------------------------------------- -- [SECTION] ImGuiContext (main Dear ImGui context) ------------------------------------------------------------------------------- @@ -2129,6 +2306,7 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct NavHighlightItemUnderNav = false, -- Disable mouse hovering highlight. Highlight navigation focused item instead of mouse hovered item. NavIdIsAlive = false, -- Nav widget has been seen this frame ~~ NavRectRel is valid NavInputSource = ImGuiInputSource.Keyboard, -- Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource.Keyboard or ImGuiInputSource.Gamepad + NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid, -- Last valid data passed to SetNextItemSelectionUser(), or -1. For current window. Not reset when focusing an item that doesn't have selection data. NavWindowingTarget = nil, NavWindowingTargetAnim = nil, NavWindowingListWindow = nil, -- Target window when doing Ctrl+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! NavWindowingTimer = 0.0, NavWindowingHighlightAlpha = 0.0, NavWindowingToggleLayer = false, -- Set while Alt or GamepadMenu is held, may be cleared by other operations, and processed when releasing the key. @@ -2168,6 +2346,14 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct NavActivateId = 0, NavActivateDownId = 0, NavActivatePressedId = 0, -- ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey.Space) || IsKeyDown(ImGuiKey.Enter) || IsKeyPressed(ImGuiKey.NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItemByID() NavActivateFlags = ImGuiActivateFlags.None, + -- Navigation: record of last move request + NavJustMovedFromFocusScopeId = 0, -- Just navigated from this focus scope id (result of a successfully MoveRequest). + NavJustMovedToId = 0, -- Just navigated to this id (result of a successfully MoveRequest). + NavJustMovedToFocusScopeId = 0, -- Just navigated to this focus scope id (result of a successfully MoveRequest). + NavJustMovedToKeyMods = 0, + NavJustMovedToIsTabbing = false, -- Copy of ImGuiNavMoveFlags.IsTabbing. Maybe we should store whole flags. + NavJustMovedToHasSelectionData = false, -- Copy of move result's ItemFlags & ImGuiItemFlags.HasSelectionUserData). Maybe we should just store ImGuiNavItemData. + FrameCount = 0, FrameCountEnded = -1, @@ -2228,6 +2414,8 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct MouseCursor = ImGuiMouseCursor.Arrow, MouseStationaryTimer = 0.0, -- Time the mouse has been stationary (with some loose heuristic) + TypingSelectState = ImGuiTypingSelectState(), -- State for GetTypingSelectRequest() + InputTextState = ImGuiInputTextState(), InputTextLineIndex = ImGuiTextIndex(), -- Temporary storage InputTextDeactivatedState = ImGuiInputTextDeactivatedState(), @@ -2249,6 +2437,13 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct CurrentTabBarStack = ImVector(), -- holds ImGuiTabBar references directly (no ImGuiPtrOrIndex needed in Lua) ShrinkWidthBuffer = ImVector(), -- ImGuiShrinkWidthItem + -- Multi-Select state + BoxSelectState = ImGuiBoxSelectState(), + CurrentMultiSelect = nil, + MultiSelectTempDataStacked = 0, -- Temporary multi-select data size (because we leave previous instances undestructed, we generally don't use MultiSelectTempData.Size) + MultiSelectTempData = ImVector(), -- ImVector, 1-based + MultiSelectStorage = ImPool(ImGuiMultiSelectState), -- ImPool + WindowResizeBorderExpectedRect = ImRect(), -- Expected border rect, switch to relative edit if moving WindowResizeRelativeMode = false, diff --git a/imgui_tables.lua b/imgui_tables.lua index 75889f2..9d82042 100644 --- a/imgui_tables.lua +++ b/imgui_tables.lua @@ -1539,7 +1539,7 @@ function ImGui.TableUpdateLayout(table_) local inner_window = table_.InnerWindow local bs = g.BoxSelectState - if bs and bs.Window == inner_window and bs.UnclipMode then + if bs.Window == inner_window and bs.UnclipMode then ImGui.TableApplyExternalUnclipRect(table_, bs.UnclipRect) end diff --git a/imgui_widgets.lua b/imgui_widgets.lua index 78b951d..aac5d75 100644 --- a/imgui_widgets.lua +++ b/imgui_widgets.lua @@ -1355,14 +1355,14 @@ function ImGui.Checkbox(label, v) -- Range-Selection/Multi-selection support (header) local checked = v if is_multi_select then - -- TODO: MultiSelectItemHeader() + checked = ImGui.MultiSelectItemHeader(id, checked, nil) end local pressed, hovered, held = ImGui.ButtonBehavior(total_bb, id) -- Range-Selection/Multi-selection support (footer) if is_multi_select then - -- TODO: MultiSelectItemFooter() + checked, pressed = ImGui.MultiSelectItemFooter(id, checked, pressed, ImGuiMultiSelectFlags.CheckboxMode_) elseif pressed then checked = not checked end @@ -8219,7 +8219,7 @@ function ImGui.Selectable(label, selected, flags, size_arg) local was_selected = selected if is_multi_select then -- Handle multi-select + alter button flags for it - -- TODO: selected, button_flags = ImGui.MultiSelectItemHeader(id, selected, button_flags) + selected, button_flags = ImGui.MultiSelectItemHeader(id, selected, button_flags) end local pressed, hovered, held = ImGui.ButtonBehavior(bb, id, button_flags) @@ -8227,7 +8227,7 @@ function ImGui.Selectable(label, selected, flags, size_arg) -- Multi-selection support (footer) if is_multi_select then - -- TODO: selected, pressed = ImGui.MultiSelectItemFooter(id, selected, pressed) + selected, pressed = ImGui.MultiSelectItemFooter(id, selected, pressed) else -- Auto-select when moved into -- - This will be more fully fleshed in the range-select branch @@ -8320,6 +8320,1331 @@ function ImGui.Selectable(label, selected, flags, size_arg) return pressed, selected end +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Typing-Select support +------------------------------------------------------------------------------- + +do -- [Port] Typing-Select, Box-Select, Multi-Select sections: locals kept private + +local MT = ImGui.GetMetatables() + +local TypingSelectCharBuf = { 0, 0, 0, 0, 0 } -- [Luau] scratch for ImTextCharToUtf8() (C++: char w_buf[5]) + +-- [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(...) then +-- local req = ImGui.GetTypingSelectRequest() +-- if req then focus_idx = ImGui.TypingSelectFindMatch(req, #my_items, function(user_data, n) return my_items[n + 1].Name end, my_items, -1) end +-- However the code is written in a way where calling it from multiple locations is safe (e.g. to obtain buffer). +-- [Port] Item indices passed to get_item_name_func(user_data, idx) and returned by TypingSelectFind*() are 0-based like upstream (-1 = none). +--- @param flags? ImGuiTypingSelectFlags +--- @return ImGuiTypingSelectRequest? +function ImGui.GetTypingSelectRequest(flags) + if flags == nil then flags = ImGuiTypingSelectFlags.None end + local g = GImGui + local data = g.TypingSelectState + local out_request = data.Request + + -- Clear buffer + local TYPING_SELECT_RESET_TIMER = 1.80 -- FIXME: Potentially move to IO config. + local TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK = 4 -- Lock single char matching when repeating same char 4 times + if #data.SearchBuffer ~= 0 then + local clear_buffer = false + clear_buffer = clear_buffer or (g.NavFocusScopeId ~= data.FocusScope) + clear_buffer = clear_buffer or (data.LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time) + clear_buffer = clear_buffer or g.NavAnyRequest + clear_buffer = clear_buffer or (g.ActiveId ~= 0 and g.NavActivateId == 0) -- Allow temporary SPACE activation to not interfere + clear_buffer = clear_buffer or ImGui.IsKeyPressed(ImGuiKey.Escape) or ImGui.IsKeyPressed(ImGuiKey.Enter) + clear_buffer = clear_buffer or (ImGui.IsKeyPressed(ImGuiKey.Backspace) and bit32.band(flags, ImGuiTypingSelectFlags.AllowBackspace) == 0) + --if clear_buffer then IMGUI_DEBUG_LOG("GetTypingSelectRequest(): Clear SearchBuffer.\n") end + if clear_buffer then + data:Clear() + end + end + + -- Append to buffer + local buffer_max_len = 64 - 1 -- [Port] IM_COUNTOF(data->SearchBuffer) - 1 + local buffer_len = #data.SearchBuffer + local select_request = false + local input_queue = g.IO.InputQueueCharacters + for n = 1, input_queue.Size do + repeat + local w = input_queue.Data[n] + local w_buf = TypingSelectCharBuf + local w_len = ImStd.ImTextCharToUtf8(w_buf, w) -- [Port] also gives ImTextCountUtf8BytesFromStr(&w, &w + 1) + if w < 32 or (buffer_len == 0 and ImCharIsBlankW(w)) or (buffer_len + w_len > buffer_max_len) then -- Ignore leading blanks + break -- continue + end + local w_str = string.char(table.unpack(w_buf, 1, w_len)) + if data.SingleCharModeLock and w_len == out_request.SingleCharSize and string.sub(data.SearchBuffer, 1, w_len) == w_str then + select_request = true -- Same character: don't need to append to buffer. + break -- continue + end + if data.SingleCharModeLock then + data:Clear() -- Different character: clear + buffer_len = 0 + end + data.SearchBuffer = string.sub(data.SearchBuffer, 1, buffer_len) .. w_str -- Append + buffer_len = buffer_len + w_len + select_request = true + until true + end + input_queue:resize(0) + + -- Handle backspace + if bit32.band(flags, ImGuiTypingSelectFlags.AllowBackspace) ~= 0 and ImGui.IsKeyPressedEx(ImGuiKey.Backspace, ImGuiInputFlags.Repeat) then + local p = ImStd.ImTextFindPreviousUtf8Codepoint(data.SearchBuffer, 1, buffer_len + 1) -- 1-based byte position + data.SearchBuffer = string.sub(data.SearchBuffer, 1, p - 1) + buffer_len = p - 1 + end + + -- Return request if any + if buffer_len == 0 then + return nil + end + if select_request then + data.FocusScope = g.NavFocusScopeId + data.LastRequestFrame = g.FrameCount + data.LastRequestTime = g.Time + end + out_request.Flags = flags + out_request.SearchBufferLen = buffer_len + out_request.SearchBuffer = data.SearchBuffer + out_request.SelectRequest = (data.LastRequestFrame == g.FrameCount) + out_request.SingleCharMode = false + out_request.SingleCharSize = 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. + -- - SingleCharMode is always set for first input character, because it usually leads to a "next". + if bit32.band(flags, ImGuiTypingSelectFlags.AllowSingleCharMode) ~= 0 then + local buf = out_request.SearchBuffer + local buf_begin = 1 + local buf_end = buf_begin + out_request.SearchBufferLen + local c0_len = ImStd.ImTextCountUtf8BytesFromChar(buf, buf_begin, buf_end) + local c0 = string.sub(buf, buf_begin, buf_begin + c0_len - 1) + local p = buf_begin + c0_len + while p < buf_end do + if string.sub(buf, p, p + c0_len - 1) ~= c0 then -- memcmp(buf_begin, p, c0_len) != 0 + break + end + p = p + c0_len + end + local single_char_count = (p == buf_end) and math.floor(out_request.SearchBufferLen / c0_len) or 0 + out_request.SingleCharMode = (single_char_count > 0 or data.SingleCharModeLock) + out_request.SingleCharSize = c0_len + data.SingleCharModeLock = data.SingleCharModeLock or (single_char_count >= TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK) -- From now on we stop search matching to lock to single char mode. + end + + return out_request +end + +-- [Port] s1 is the byte range [s1_begin, s1_end) of string s1, s2 is a whole string (out of range bytes read as 0 like a C terminator). +local function ImStrimatchlen(s1, s1_begin, s1_end, s2) + local match_len = 0 + local s2_p = 1 + while s1_begin < s1_end and ImToUpper(string.byte(s1, s1_begin)) == ImToUpper(string.byte(s2, s2_p) or 0) do + s1_begin = s1_begin + 1 + s2_p = s2_p + 1 + match_len = match_len + 1 + end + return match_len +end + +-- Default handler for finding a result for typing-select. You may implement your own. +-- You might want to display a tooltip to visualize the current request SearchBuffer +-- When SingleCharMode is set: +-- - it is better to NOT display a tooltip of other on-screen display indicator. +-- - the index of the currently focused item is required. +-- if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO.NavIdItem, otherwise from g.NavLastValidSelectionUserData. +--- @param req ImGuiTypingSelectRequest? +--- @param items_count int +--- @param get_item_name_func fun(user_data: any, idx: int): string # idx is 0-based +--- @param user_data any +--- @param nav_item_idx int # 0-based, -1 if unknown +--- @return int # 0-based, -1 if none +function ImGui.TypingSelectFindMatch(req, items_count, get_item_name_func, user_data, nav_item_idx) + if req == nil or req.SelectRequest == false then -- Support NULL parameter so both calls can be done from same spot. + return -1 + end + local idx = -1 + if req.SingleCharMode and bit32.band(req.Flags, ImGuiTypingSelectFlags.AllowSingleCharMode) ~= 0 then + idx = ImGui.TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx) + else + idx = ImGui.TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data) + end + if idx ~= -1 then + ImGui.SetNavCursorVisibleAfterMove() + end + return idx +end + +-- Special handling when a single character is repeated: perform search on a single letter and goes to next. +function ImGui.TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx) + -- FIXME: Assume selection user data is index. Would be extremely practical. + --if nav_item_idx == -1 then + -- nav_item_idx = g.NavLastValidSelectionUserData + --end + + local first_match_idx = -1 + local return_next_match = false + for idx = 0, items_count - 1 do + repeat + local item_name = get_item_name_func(user_data, idx) + if ImStrimatchlen(req.SearchBuffer, 1, 1 + req.SingleCharSize, item_name) < req.SingleCharSize then + break -- continue + end + if return_next_match then -- Return next matching item after current item. + return idx + end + if first_match_idx == -1 and nav_item_idx == -1 then -- Return first match immediately if we don't have a nav_item_idx value. + return idx + end + if first_match_idx == -1 then -- Record first match for wrapping. + first_match_idx = idx + end + if nav_item_idx == idx then -- Record that we encountering nav_item so we can return next match. + return_next_match = true + end + until true + end + return first_match_idx -- First result +end + +function ImGui.TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data) + local longest_match_idx = -1 + local longest_match_len = 0 + for idx = 0, items_count - 1 do + local item_name = get_item_name_func(user_data, idx) + local match_len = ImStrimatchlen(req.SearchBuffer, 1, 1 + req.SearchBufferLen, item_name) + if match_len > longest_match_len then -- [Port] inverted 'if (match_len <= longest_match_len) continue;' + longest_match_idx = idx + longest_match_len = match_len + if match_len == req.SearchBufferLen then + break + end + end + end + return longest_match_idx +end + +--- @param data ImGuiTypingSelectState +function ImGui.DebugNodeTypingSelectState(data) + ImGui.Text("SearchBuffer = \"%s\"", data.SearchBuffer) + ImGui.Text("SingleCharMode = %d, Size = %d, Lock = %d", data.Request.SingleCharMode and 1 or 0, data.Request.SingleCharSize, data.SingleCharModeLock and 1 or 0) + ImGui.Text("LastRequest = time: %.2f, frame: %d", data.LastRequestTime, data.LastRequestFrame) +end + +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Box-Select support +-- This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet. +------------------------------------------------------------------------------- +-- Extra logic in MultiSelectItemFooter() and ImGuiListClipper:Step() +------------------------------------------------------------------------------- +-- - BoxSelectPreStartDrag() [Internal] +-- - BoxSelectActivateDrag() [Internal] +-- - BoxSelectDeactivateDrag() [Internal] +-- - BoxSelectScrollWithMouseDrag() [Internal] +-- - BeginBoxSelect() [Internal] +-- - EndBoxSelect() [Internal] +------------------------------------------------------------------------------- + +-- Call on the initial click. +local function BoxSelectPreStartDrag(id, clicked_item) + local g = GImGui + local bs = g.BoxSelectState + bs.ID = id + bs.IsStarting = true -- Consider starting box-select. + bs.IsStartedFromVoid = (clicked_item == ImGuiSelectionUserData_Invalid) + bs.IsStartedSetNavIdOnce = bs.IsStartedFromVoid + bs.KeyMods = g.IO.KeyMods + local off = g.CurrentWindow.DC.CursorStartPos -- [Luau] inline WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos) + ImVec2_CopyV(bs.StartPosRel, g.IO.MousePos.x - off.x, g.IO.MousePos.y - off.y) + ImVec2_Copy(bs.EndPosRel, bs.StartPosRel) + ImVec2_CopyV(bs.ScrollAccum, 0.0, 0.0) +end + +local function BoxSelectActivateDrag(bs, window) + local g = GImGui + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Activate\n", bs.ID) + bs.IsActive = true + bs.Window = window + bs.IsStarting = false + ImGui.SetActiveID(bs.ID, window) + ImGui.SetActiveIdUsingAllKeyboardKeys() + if bs.IsStartedFromVoid and bit32.band(bs.KeyMods, bit32.bor(ImGuiMod_Ctrl, ImGuiMod_Shift)) == 0 then + bs.RequestClear = true + end +end + +local function BoxSelectDeactivateDrag(bs) + local g = GImGui + bs.IsActive = false; bs.IsStarting = false + if g.ActiveId == bs.ID then + IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Deactivate\n", bs.ID) + ImGui.ClearActiveID() + end + bs.ID = 0 +end + +local function BoxSelectScrollWithMouseDrag(bs, window, inner_r) + local g = GImGui + IM_ASSERT(bs.Window == window) + for n = 1, 2 do -- each axis -- [Port] 1 = X, 2 = Y + repeat + local k = (n == 1) and "x" or "y" + local mouse_pos = g.IO.MousePos[k] + local dist = (mouse_pos > inner_r.Max[k]) and mouse_pos - inner_r.Max[k] or (mouse_pos < inner_r.Min[k]) and mouse_pos - inner_r.Min[k] or 0.0 + local scroll_curr = window.Scroll[k] + if dist == 0.0 or (dist < 0.0 and scroll_curr < 0.0) or (dist > 0.0 and scroll_curr >= window.ScrollMax[k]) then + break -- continue + end + + local speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0, 1.0, 4.0, ImAbs(dist)) -- x1 to x4 depending on distance + local scroll_step = g.FontSize * 35.0 * speed_multiplier * ImSign(dist) * g.IO.DeltaTime + bs.ScrollAccum[k] = bs.ScrollAccum[k] + scroll_step + + -- Accumulate into a stored value so we can handle high-framerate + local scroll_step_i = ImFloor(bs.ScrollAccum[k]) + if scroll_step_i == 0.0 then + break -- continue + end + if n == 1 then + ImGui.SetScrollX(window, scroll_curr + scroll_step_i) + else + ImGui.SetScrollY(window, scroll_curr + scroll_step_i) + end + bs.ScrollAccum[k] = bs.ScrollAccum[k] - scroll_step_i + until true + end +end + +--- @param scope_rect ImRect +--- @param window ImGuiWindow +--- @param box_select_id ImGuiID +--- @param ms_flags ImGuiMultiSelectFlags +--- @return bool +function ImGui.BeginBoxSelect(scope_rect, window, box_select_id, ms_flags) + local g = GImGui + local bs = g.BoxSelectState + ImGui.KeepAliveID(box_select_id) + if bs.ID ~= box_select_id then + return false + end + + -- IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry. + bs.UnclipMode = false + bs.RequestClear = false + if bs.IsStarting and ImGui.IsMouseDragPastThreshold(0) then + BoxSelectActivateDrag(bs, window) + elseif (bs.IsStarting or bs.IsActive) and g.IO.MouseDown[0] == false then + BoxSelectDeactivateDrag(bs) + end + if not bs.IsActive then + return false + end + + -- Current frame absolute prev/current rectangles are used to toggle selection. + -- They are derived from positions relative to scrolling space, so "previous" rectangle is reprojected for current frame coordinates. + -- [Luau] scalar versions of WindowPosRelToAbs()/ImClamp()/ImMin()/ImMax() (no temporary ImVec2) + local off = window.DC.CursorStartPos + local start_pos_abs_x, start_pos_abs_y = bs.StartPosRel.x + off.x, bs.StartPosRel.y + off.y + local prev_end_pos_abs_x, prev_end_pos_abs_y = bs.EndPosRel.x + off.x, bs.EndPosRel.y + off.y -- Clamped already + local curr_end_pos_abs_x, curr_end_pos_abs_y = g.IO.MousePos.x, g.IO.MousePos.y + if bit32.band(ms_flags, ImGuiMultiSelectFlags.ScopeWindow) ~= 0 then -- Box-select scrolling only happens with ScopeWindow + curr_end_pos_abs_x = ImClamp(curr_end_pos_abs_x, scope_rect.Min.x, scope_rect.Max.x) + curr_end_pos_abs_y = ImClamp(curr_end_pos_abs_y, scope_rect.Min.y, scope_rect.Max.y) + end + ImVec2_CopyV(bs.BoxSelectRectPrev.Min, ImMin(start_pos_abs_x, prev_end_pos_abs_x), ImMin(start_pos_abs_y, prev_end_pos_abs_y)) + ImVec2_CopyV(bs.BoxSelectRectPrev.Max, ImMax(start_pos_abs_x, prev_end_pos_abs_x), ImMax(start_pos_abs_y, prev_end_pos_abs_y)) + ImVec2_CopyV(bs.BoxSelectRectCurr.Min, ImMin(start_pos_abs_x, curr_end_pos_abs_x), ImMin(start_pos_abs_y, curr_end_pos_abs_y)) + ImVec2_CopyV(bs.BoxSelectRectCurr.Max, ImMax(start_pos_abs_x, curr_end_pos_abs_x), ImMax(start_pos_abs_y, curr_end_pos_abs_y)) + --IMGUI_DEBUG_LOG("StartPosRel (%.2f,%.2f) EndPosRel (%.2f,%.2f) -> (%.2f,%.2f)\n", bs.StartPosRel.x, bs.StartPosRel.y, bs.EndPosRel.x, bs.EndPosRel.y, WindowPosAbsToRel(window, g.IO.MousePos).x, WindowPosAbsToRel(window, g.IO.MousePos).y) + + -- Box-select 2D mode detects change of the rectangle. + -- Storing unclip rects which will be tested by widgets supporting box-select. Always update rectangles when active (even if we don't use them). + -- To facilitate understanding this: enable IMGUI_DEBUG_BOXSELECT and visualize all geometry. + if bit32.band(ms_flags, bit32.bor(ImGuiMultiSelectFlags.BoxSelect1d, ImGuiMultiSelectFlags.BoxSelect2d)) ~= 0 then + -- For both sides, compute the area differing between Prev and Curr rectangles. + local unclip_x, unclip_y = bs.UnclipRects[1], bs.UnclipRects[2] -- [Port] UnclipRects[0], UnclipRects[1] + ImVec2_CopyV(unclip_x.Min, FLT_MAX, FLT_MAX); ImVec2_CopyV(unclip_x.Max, -FLT_MAX, -FLT_MAX) + ImVec2_CopyV(unclip_y.Min, FLT_MAX, FLT_MAX); ImVec2_CopyV(unclip_y.Max, -FLT_MAX, -FLT_MAX) + local curr, prev = bs.BoxSelectRectCurr, bs.BoxSelectRectPrev + for side = 0, 1 do + local a = (side == 0) and curr.Min or curr.Max + local b = (side == 0) and prev.Min or prev.Max + local d_min_x, d_min_y = ImMin(a.x, b.x), ImMin(a.y, b.y) + local d_max_x, d_max_y = ImMax(a.x, b.x), ImMax(a.y, b.y) + if d_min_x ~= d_max_x then + unclip_x:AddX(d_min_x) + unclip_x:AddX(d_max_x) + end + if d_min_y ~= d_max_y then + unclip_y:AddY(d_min_y) + unclip_y:AddY(d_max_y) + end + end + + -- box_select_intersection = BoxSelectRectPrev + BoxSelectRectCurr + local box_select_intersection_min_x, box_select_intersection_min_y = ImMin(prev.Min.x, curr.Min.x), ImMin(prev.Min.y, curr.Min.y) + local box_select_intersection_max_x, box_select_intersection_max_y = ImMax(prev.Max.x, curr.Max.x), ImMax(prev.Max.y, curr.Max.y) + if bit32.band(ms_flags, ImGuiMultiSelectFlags.BoxSelect2d) ~= 0 then + if prev.Min.x ~= curr.Min.x or prev.Max.x ~= curr.Max.x then + unclip_x:AddY(box_select_intersection_min_y) + unclip_x:AddY(box_select_intersection_max_y) + end + end + if bit32.band(ms_flags, bit32.bor(ImGuiMultiSelectFlags.BoxSelect1d, ImGuiMultiSelectFlags.BoxSelect2d)) ~= 0 then + if prev.Min.y ~= curr.Min.y or prev.Max.y ~= curr.Max.y then + unclip_y:AddX(box_select_intersection_min_x) + unclip_y:AddX(box_select_intersection_max_x) + end + end + + -- Merge both rectangles into one. + -- FIXME-OPT: When UnclipRect.Area() is much larger than the sum of UnclipRects[0]/[1] Areas, widgets should + -- ideally first use UnclipRect as a first coarse cull layer + the individual ones as a second validation. + ImRect_Copy(bs.UnclipRect, unclip_x) + bs.UnclipRect:Add(unclip_y) + if not bs.UnclipRect:IsInverted() and (not window.ClipRect:Contains(bs.UnclipRect.Min) or not window.ClipRect:Contains(bs.UnclipRect.Max)) then -- !! Don't use Contains(ImRect) + bs.UnclipMode = true + end + if bs.UnclipMode and g.CurrentTable ~= nil then + ImGui.TableApplyExternalUnclipRect(g.CurrentTable, bs.UnclipRect) -- No need submitting both + end + end + + -- [Port] IMGUI_DEBUG_BOXSELECT visualization not ported (compile-time debug option, disabled upstream). + return true +end + +--- @param scope_rect ImRect +--- @param ms_flags ImGuiMultiSelectFlags +function ImGui.EndBoxSelect(scope_rect, ms_flags) + local g = GImGui + local window = g.CurrentWindow + local bs = g.BoxSelectState + IM_ASSERT(bs.IsActive) + bs.UnclipMode = false + + -- Render selection rectangle + bs.EndPosRel = ImGui.WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)) -- Clamp stored position according to current scrolling view + local box_select_r = ImRect(bs.BoxSelectRectCurr.Min, bs.BoxSelectRectCurr.Max) + box_select_r:ClipWith(scope_rect) + local draw_window = ImGui.FindFrontMostVisibleChildWindow(window) + draw_window.DrawList:AddRectFilled(box_select_r.Min, box_select_r.Max, ImGui.GetColorU32(ImGuiCol.SeparatorHovered, 0.30)) -- FIXME-MULTISELECT: Styling + draw_window.DrawList:AddRect(box_select_r.Min, box_select_r.Max, ImGui.GetColorU32(ImGuiCol.NavCursor)) -- FIXME-MULTISELECT FIXME-DPI: Styling + + -- Scroll + local enable_scroll = bit32.band(ms_flags, ImGuiMultiSelectFlags.ScopeWindow) ~= 0 and bit32.band(ms_flags, ImGuiMultiSelectFlags.BoxSelectNoScroll) == 0 + if enable_scroll then + local scroll_r = ImRect(scope_rect.Min, scope_rect.Max) + scroll_r:Expand(-g.FontSize) + --GetForegroundDrawList():AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255)) + if not scroll_r:Contains(g.IO.MousePos) then + BoxSelectScrollWithMouseDrag(bs, window, scroll_r) + end + end +end + +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Multi-Select support +------------------------------------------------------------------------------- +-- - DebugLogMultiSelectRequests() [Internal] +-- - CalcScopeRect() [Internal] +-- - BeginMultiSelect() +-- - EndMultiSelect() +-- - SetNextItemSelectionUserData() +-- - MultiSelectItemHeader() [Internal] +-- - MultiSelectItemFooter() [Internal] +-- - DebugNodeMultiSelectState() [Internal] +------------------------------------------------------------------------------- + +-- [Luau] reused scratch request: ImVector:push_back() copies it into the preallocated element (ImGuiSelectionRequest_Copy) +local MultiSelectScratchRequest = { Type = 0, Selected = false, RangeDirection = 0, RangeFirstItem = -1, RangeLastItem = -1 } +local MultiSelectScratchItemRect = ImRect() -- [Luau] scratch for MultiSelectItemFooter()'s item_rect + +local function DebugLogMultiSelectRequests(func, io) + local g = GImGui + for _, req in io.Requests:iter() do + if req.Type == ImGuiSelectionRequestType.SetAll then IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", func, req.Selected and 1 or 0, req.Selected and "SelectAll" or "Clear") end + if req.Type == ImGuiSelectionRequestType.SetRange then IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %s..%s = %d (dir %d)\n", func, tostring(req.RangeFirstItem), tostring(req.RangeLastItem), req.Selected and 1 or 0, req.RangeDirection) end + end +end + +local function CalcScopeRect(ms, window) + if bit32.band(ms.Flags, ImGuiMultiSelectFlags.ScopeRect) ~= 0 then + -- Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only + -- This probably doesn't work inside a table as there are ample ambiguities related to exact time of calling BeginMultiSelect()/EndMultiSelect(). + return ImRect(ms.ScopeRectMin.x, ms.ScopeRectMin.y, ImMax(window.DC.CursorMaxPos.x, ms.ScopeRectMin.x), ImMax(window.DC.CursorMaxPos.y, ms.ScopeRectMin.y)) + else + --// When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970) + local scope_rect = ImRect(window.InnerClipRect.Min, window.InnerClipRect.Max) + --if g.CurrentTable ~= nil then + -- scope_rect = g.CurrentTable.HostClipRect + --end + + -- Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect? + scope_rect.Min.x = ImMin(scope_rect.Min.x + window.DecoInnerSizeX1, scope_rect.Max.x) + scope_rect.Min.y = ImMin(scope_rect.Min.y + window.DecoInnerSizeY1, scope_rect.Max.y) + return scope_rect + end +end + +-- Return ImGuiMultiSelectIO structure. +-- Lifetime: don't hold on ImGuiMultiSelectIO references over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +-- Passing 'selection_size' and 'items_count' parameters is currently optional. +-- - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags.ClearOnEscape won't claim Escape key when selection_size 0, +-- allowing a first press to clear selection THEN the second press to leave child window and return to parent. +-- - 'items_count' is stored in ImGuiMultiSelectIO which makes it a convenient way to pass the information to your ApplyRequest() handler (but you may pass it differently). +-- - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1. +-- - If you can easily tell if your selection is empty or not, you may pass 0/1, or you may enable ImGuiMultiSelectFlags.ClearOnEscape flag dynamically. +--- @param flags ImGuiMultiSelectFlags +--- @param selection_size? int +--- @param items_count? int +--- @return ImGuiMultiSelectIO +function ImGui.BeginMultiSelect(flags, selection_size, items_count) + if selection_size == nil then selection_size = -1 end + if items_count == nil then items_count = -1 end + local g = GImGui + local window = g.CurrentWindow + + g.MultiSelectTempDataStacked = g.MultiSelectTempDataStacked + 1 + while g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size do -- [Port] resize(n, ImGuiMultiSelectTempData()) would share one instance + g.MultiSelectTempData:push_back(ImGuiMultiSelectTempData()) + end + local ms = g.MultiSelectTempData.Data[g.MultiSelectTempDataStacked] + g.CurrentMultiSelect = ms + if bit32.band(flags, bit32.bor(ImGuiMultiSelectFlags.ScopeWindow, ImGuiMultiSelectFlags.ScopeRect)) == 0 then + flags = bit32.bor(flags, ImGuiMultiSelectFlags.ScopeWindow) + end + if bit32.band(flags, ImGuiMultiSelectFlags.SingleSelect) ~= 0 then + flags = bit32.band(flags, bit32.bnot(bit32.bor(ImGuiMultiSelectFlags.BoxSelect2d, ImGuiMultiSelectFlags.BoxSelect1d))) + end + if bit32.band(flags, ImGuiMultiSelectFlags.BoxSelect2d) ~= 0 then + flags = bit32.band(flags, bit32.bnot(ImGuiMultiSelectFlags.BoxSelect1d)) + end + + -- FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250) + -- They should perhaps be stacked properly? + local table_ = g.CurrentTable + if table_ then + if not table_.IsLayoutLocked then + ImGui.TableUpdateLayout(table_) + elseif table_.CurrentColumn ~= -1 then + ImGui.TableEndCell(table_) -- This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it. + end + end + + -- FIXME: BeginFocusScope() + local id = window.IDStack.Data[window.IDStack.Size] -- window.IDStack:back() + ms:Clear() + ms.FocusScopeId = id + ms.Flags = flags + ImVec2_Copy(ms.BackupCursorMaxPos, window.DC.CursorMaxPos) + ImVec2_Copy(ms.ScopeRectMin, window.DC.CursorPos) + if bit32.band(flags, ImGuiMultiSelectFlags.ScopeRect) ~= 0 then + ImVec2_Copy(window.DC.CursorMaxPos, ms.ScopeRectMin) -- CalcScopeRect() for ImGuiMultiSelectFlags.ScopeRect will measure in EndMultiSelect(). + end + ImGui.PushFocusScope(ms.FocusScopeId) + ms.IsFocused = ImGui.IsInNavFocusRoute(g.CurrentFocusScopeId) + if bit32.band(flags, ImGuiMultiSelectFlags.ScopeWindow) ~= 0 then -- Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items. + window.DC.NavLayersActiveMask = bit32.bor(window.DC.NavLayersActiveMask, bit32.lshift(1, ImGuiNavLayer.Main)) + end + + -- Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame. + if g.NavJustMovedToId ~= 0 then + ms.KeyMods = g.NavJustMovedToIsTabbing and 0 or g.NavJustMovedToKeyMods + else + ms.KeyMods = g.IO.KeyMods + end + if bit32.band(flags, ImGuiMultiSelectFlags.NoRangeSelect) ~= 0 then + ms.KeyMods = bit32.band(ms.KeyMods, bit32.bnot(ImGuiMod_Shift)) + end + + -- Bind storage + local storage = g.MultiSelectStorage:GetOrAddByKey(id) + storage.ID = id + storage.LastFrameActive = g.FrameCount + storage.LastSelectionSize = selection_size + storage.Window = window + ms.Storage = storage + + -- Output to user + ms.IO.Requests:resize(0) + ms.IO.RangeSrcItem = storage.RangeSrcItem + ms.IO.NavIdItem = storage.NavIdItem + ms.IO.NavIdSelected = (storage.NavIdSelected == 1) + ms.IO.ItemsCount = items_count + + -- Clear when using Navigation to move within the scope + -- (we compare FocusScopeId so it possible to use multiple selections inside a same window) + local request_clear = false + local request_select_all = false + if g.NavJustMovedToId ~= 0 and g.NavJustMovedToFocusScopeId == ms.FocusScopeId and g.NavJustMovedToHasSelectionData then + if bit32.band(ms.KeyMods, ImGuiMod_Shift) ~= 0 then + ms.IsKeyboardSetRange = true + end + if ms.IsKeyboardSetRange then + IM_ASSERT(storage.RangeSrcItem ~= ImGuiSelectionUserData_Invalid) -- Not ready -> could clear? + end + if bit32.band(ms.KeyMods, bit32.bor(ImGuiMod_Ctrl, ImGuiMod_Shift)) == 0 and bit32.band(flags, bit32.bor(ImGuiMultiSelectFlags.NoAutoClear, ImGuiMultiSelectFlags.NoAutoSelect)) == 0 then + request_clear = true + end + elseif g.NavJustMovedFromFocusScopeId == ms.FocusScopeId then + -- Also clear on leaving scope (may be optional?) + if bit32.band(ms.KeyMods, bit32.bor(ImGuiMod_Ctrl, ImGuiMod_Shift)) == 0 and bit32.band(flags, bit32.bor(ImGuiMultiSelectFlags.NoAutoClear, ImGuiMultiSelectFlags.NoAutoSelect)) == 0 then + request_clear = true + end + end + + -- Box-select handling: update active state. + local bs = g.BoxSelectState + if bit32.band(flags, bit32.bor(ImGuiMultiSelectFlags.BoxSelect1d, ImGuiMultiSelectFlags.BoxSelect2d)) ~= 0 then + ms.BoxSelectId = ImGui.GetID("##BoxSelect") + if ImGui.BeginBoxSelect(CalcScopeRect(ms, window), window, ms.BoxSelectId, flags) then + request_clear = request_clear or bs.RequestClear + end + end + + if ms.IsFocused then + -- Shortcut: Clear selection (Escape) + -- - Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window. + -- - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock. + if bit32.band(flags, ImGuiMultiSelectFlags.ClearOnEscape) ~= 0 then + if selection_size ~= 0 or bs.IsActive then + if ImGui.Shortcut(ImGuiKey.Escape, ImGuiInputFlags.None, bs.IsActive and bs.ID or 0) then + request_clear = true + if bs.IsActive then + BoxSelectDeactivateDrag(bs) + end + end + end + end + + -- Shortcut: Select all (Ctrl+A) + if bit32.band(flags, ImGuiMultiSelectFlags.SingleSelect) == 0 and bit32.band(flags, ImGuiMultiSelectFlags.NoSelectAll) == 0 then + if ImGui.Shortcut(bit32.bor(ImGuiMod_Ctrl, ImGuiKey.A)) then + request_select_all = true + end + end + end + + if request_clear or request_select_all then + ImGui.MultiSelectAddSetAll(ms, request_select_all) + if not request_select_all then + storage.LastSelectionSize = 0 + end + end + ms.LoopRequestSetAll = request_select_all and 1 or (request_clear and 0 or -1) + ms.IsSoleOrUnknownSelectionSize = (storage.LastSelectionSize == 1) or (storage.LastSelectionSize == -1) + --ms.PrevSubmittedItem = ImGuiSelectionUserData_Invalid + + if bit32.band(g.DebugLogFlags, ImGuiDebugLogFlags.EventSelection) ~= 0 then + DebugLogMultiSelectRequests("BeginMultiSelect", ms.IO) + end + + return ms.IO +end + +-- Return updated ImGuiMultiSelectIO structure. +-- Lifetime: don't hold on ImGuiMultiSelectIO references over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect(). +--- @return ImGuiMultiSelectIO +function ImGui.EndMultiSelect() + local g = GImGui + local ms = g.CurrentMultiSelect + local storage = ms.Storage + local window = g.CurrentWindow + IM_ASSERT_USER_ERROR(ms.FocusScopeId == g.CurrentFocusScopeId, "EndMultiSelect() FocusScope mismatch!") + IM_ASSERT(g.CurrentMultiSelect ~= nil and storage.Window == g.CurrentWindow) + IM_ASSERT(g.MultiSelectTempDataStacked > 0 and g.MultiSelectTempData.Data[g.MultiSelectTempDataStacked] == g.CurrentMultiSelect) + + local scope_rect = CalcScopeRect(ms, window) + if ms.IsFocused then + -- We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here. + if ms.IO.RangeSrcReset or (ms.RangeSrcPassedBy == false and ms.IO.RangeSrcItem ~= ImGuiSelectionUserData_Invalid) then -- Can't read storage.RangeSrcItem here -> we want the state at beginning of the scope (see tests for easy failure) + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n") -- Will set be to NavId. + storage.RangeSrcItem = ImGuiSelectionUserData_Invalid + end + if ms.NavIdPassedBy == false and storage.NavIdItem ~= ImGuiSelectionUserData_Invalid then + IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset NavIdItem.\n") + storage.NavIdItem = ImGuiSelectionUserData_Invalid + storage.NavIdSelected = -1 + end + + if bit32.band(ms.Flags, bit32.bor(ImGuiMultiSelectFlags.BoxSelect1d, ImGuiMultiSelectFlags.BoxSelect2d)) ~= 0 and ImGui.GetBoxSelectState(ms.BoxSelectId) then + ImGui.EndBoxSelect(scope_rect, ms.Flags) + end + end + + if ms.IsEndIO == false then + ms.IO.Requests:resize(0) + end + + -- Clear selection when clicking void? + -- We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection! + -- The InnerRect test is necessary for non-child/decorated windows. + local scope_hovered = window.InnerRect:Contains(g.IO.MousePos) and ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows) + if scope_hovered and bit32.band(ms.Flags, ImGuiMultiSelectFlags.ScopeRect) ~= 0 then + scope_hovered = scope_hovered and scope_rect:Contains(g.IO.MousePos) + end + if scope_hovered and g.HoveredId == 0 and g.ActiveId == 0 then + if bit32.band(ms.Flags, bit32.bor(ImGuiMultiSelectFlags.BoxSelect1d, ImGuiMultiSelectFlags.BoxSelect2d)) ~= 0 then + if not g.BoxSelectState.IsActive and not g.BoxSelectState.IsStarting and g.IO.MouseClickedCount[0] == 1 then + BoxSelectPreStartDrag(ms.BoxSelectId, ImGuiSelectionUserData_Invalid) + ImGui.FocusWindow(window, ImGuiFocusRequestFlags.UnlessBelowModal) + ImGui.SetHoveredID(ms.BoxSelectId) + if bit32.band(ms.Flags, ImGuiMultiSelectFlags.ScopeRect) ~= 0 then + ImGui.SetNavID(0, ImGuiNavLayer.Main, ms.FocusScopeId, ImRect(g.IO.MousePos.x, g.IO.MousePos.y, g.IO.MousePos.x, g.IO.MousePos.y)) -- Automatically switch FocusScope for initial click from void to box-select. + end + end + end + + if bit32.band(ms.Flags, ImGuiMultiSelectFlags.ClearOnClickVoid) ~= 0 then + if ImGui.IsMouseReleased(0) and ImGui.IsMouseDragPastThreshold(0) == false and g.IO.KeyMods == ImGuiMod_None then + ImGui.MultiSelectAddSetAll(ms, false) + end + end + end + + -- Courtesy nav wrapping helper flag + if bit32.band(ms.Flags, ImGuiMultiSelectFlags.NavWrapX) ~= 0 then + IM_ASSERT(bit32.band(ms.Flags, ImGuiMultiSelectFlags.ScopeWindow) ~= 0) -- Only supported at window scope + ImGui.NavMoveRequestTryWrapping(ImGui.GetCurrentWindow(), ImGuiNavMoveFlags.WrapX) + end + + -- Unwind + local table_ = g.CurrentTable + if table_ then + if table_.IsInsideRow then + ImGui.TableEndRow(table_) + end + end + ImVec2_CopyV(window.DC.CursorMaxPos, ImMax(ms.BackupCursorMaxPos.x, window.DC.CursorMaxPos.x), ImMax(ms.BackupCursorMaxPos.y, window.DC.CursorMaxPos.y)) + ImGui.PopFocusScope() + + if bit32.band(g.DebugLogFlags, ImGuiDebugLogFlags.EventSelection) ~= 0 then + DebugLogMultiSelectRequests("EndMultiSelect", ms.IO) + end + + ms.FocusScopeId = 0 + ms.Flags = ImGuiMultiSelectFlags.None + g.MultiSelectTempDataStacked = g.MultiSelectTempDataStacked - 1 + if g.MultiSelectTempDataStacked > 0 then + g.CurrentMultiSelect = g.MultiSelectTempData.Data[g.MultiSelectTempDataStacked] + else + g.CurrentMultiSelect = nil + end + + return ms.IO +end + +--- @param selection_user_data ImGuiSelectionUserData +function ImGui.SetNextItemSelectionUserData(selection_user_data) + -- Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code! + -- This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api. + local g = GImGui + local next_item_data = g.NextItemData + next_item_data.SelectionUserData = selection_user_data + next_item_data.FocusScopeId = g.CurrentFocusScopeId + + local ms = g.CurrentMultiSelect + if ms then + -- Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping) + next_item_data.ItemFlagsSet = bit32.bor(next_item_data.ItemFlagsSet, ImGuiItemFlags.HasSelectionUserData, ImGuiItemFlags.IsMultiSelect) + if ms.IO.RangeSrcItem == selection_user_data then + ms.RangeSrcPassedBy = true + end + --ms.PrevSubmittedItem = ms.CurrSubmittedItem -- Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select. + --ms.CurrSubmittedItem = selection_user_data + else + next_item_data.ItemFlagsSet = bit32.bor(next_item_data.ItemFlagsSet, ImGuiItemFlags.HasSelectionUserData) + end +end + +-- In charge of: +-- - Applying SetAll for submitted items. +-- - Applying SetRange for submitted items and record end points. +-- - Altering button behavior flags to facilitate use with drag and drop. +-- [Port] out-params as return values: `selected, button_flags = MultiSelectItemHeader(id, selected, button_flags)` (button_flags may be nil: C++ p_button_flags == NULL) +--- @param id ImGuiID +--- @param selected bool +--- @param button_flags ImGuiButtonFlags? +--- @return bool, ImGuiButtonFlags? +function ImGui.MultiSelectItemHeader(id, selected, button_flags) + local g = GImGui + local ms = g.CurrentMultiSelect + + if ms.IsFocused then + local storage = ms.Storage + local item_data = g.NextItemData.SelectionUserData + IM_ASSERT(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId, "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope") + + -- Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect(). + -- This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper. + -- If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect() + if ms.LoopRequestSetAll ~= -1 then + selected = (ms.LoopRequestSetAll == 1) + end + + -- When using Shift+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection) + -- For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function) + if ms.IsKeyboardSetRange then + IM_ASSERT(id ~= 0 and bit32.band(ms.KeyMods, ImGuiMod_Shift) ~= 0) + local is_range_dst = (ms.RangeDstPassedBy == false) and g.NavJustMovedToId == id -- Assume that g.NavJustMovedToId is not clipped. + if is_range_dst then + ms.RangeDstPassedBy = true + end + if is_range_dst and storage.RangeSrcItem == ImGuiSelectionUserData_Invalid then -- If we don't have RangeSrc, assign RangeSrc = RangeDst + storage.RangeSrcItem = item_data + storage.RangeSelected = selected and 1 or 0 + end + local is_range_src = storage.RangeSrcItem == item_data + if is_range_src or is_range_dst or ms.RangeSrcPassedBy ~= ms.RangeDstPassedBy then + -- Apply range-select value to visible items + IM_ASSERT(storage.RangeSrcItem ~= ImGuiSelectionUserData_Invalid and storage.RangeSelected ~= -1) + selected = (storage.RangeSelected ~= 0) + elseif bit32.band(ms.KeyMods, ImGuiMod_Ctrl) == 0 and bit32.band(ms.Flags, ImGuiMultiSelectFlags.NoAutoClear) == 0 then + -- Clear other items + selected = false + end + end + end + + -- Alter button behavior flags + -- To handle drag and drop of multiple items we need to avoid clearing selection on click. + -- Enabling this test makes actions using Ctrl+Shift delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items. + if button_flags ~= nil then + button_flags = bit32.bor(button_flags, ImGuiButtonFlags.NoHoveredOnFocus) + button_flags = bit32.band(button_flags, bit32.bnot(bit32.bor(ImGuiButtonFlags.PressedOnClick, ImGuiButtonFlags.PressedOnClickRelease))) + if bit32.band(ms.Flags, ImGuiMultiSelectFlags.SelectOnClickAlways) ~= 0 then + button_flags = bit32.bor(button_flags, ImGuiButtonFlags.PressedOnClick) + elseif bit32.band(ms.Flags, ImGuiMultiSelectFlags.SelectOnClickRelease) ~= 0 then + button_flags = bit32.bor(button_flags, ImGuiButtonFlags.PressedOnClickRelease) + else -- ImGuiMultiSelectFlags.SelectOnAuto + button_flags = bit32.bor(button_flags, (not selected or (g.ActiveId == id and g.ActiveIdHasBeenPressedBefore)) and ImGuiButtonFlags.PressedOnClick or ImGuiButtonFlags.PressedOnClickRelease) + end + end + return selected, button_flags +end + +-- In charge of: +-- - Auto-select on navigation. +-- - Box-select toggle handling. +-- - Right-click handling. +-- - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse. +-- - Record current selection state for RangeSrc +-- This is all rather complex, best to run and refer to "widgets_multiselect_xxx" tests in imgui_test_suite. +-- [Port] out-params as return values: `selected, pressed = MultiSelectItemFooter(id, selected, pressed, extra_flags)` +--- @param id ImGuiID +--- @param selected bool +--- @param pressed bool +--- @param extra_flags? ImGuiMultiSelectFlags +--- @return bool, bool +function ImGui.MultiSelectItemFooter(id, selected, pressed, extra_flags) + if extra_flags == nil then extra_flags = 0 end + local g = GImGui + local window = g.CurrentWindow + + local ms = g.CurrentMultiSelect + local storage = ms.Storage + if pressed then + ms.IsFocused = true + end + + local hovered = false + if bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredRect) ~= 0 then + hovered = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup) + end + if not ms.IsFocused and not hovered then + return selected, pressed + end + + local item_data = g.NextItemData.SelectionUserData + + local flags = bit32.bor(ms.Flags, extra_flags) + local is_singleselect = bit32.band(flags, ImGuiMultiSelectFlags.SingleSelect) ~= 0 + local is_ctrl = bit32.band(ms.KeyMods, ImGuiMod_Ctrl) ~= 0 + local is_shift = bit32.band(ms.KeyMods, ImGuiMod_Shift) ~= 0 + + local apply_to_range_src = false + + if g.NavId == id and storage.RangeSrcItem == ImGuiSelectionUserData_Invalid then + apply_to_range_src = true + end + if ms.IsEndIO == false then + ms.IO.Requests:resize(0) + ms.IsEndIO = true + end + + -- Auto-select as you navigate a list + if g.NavJustMovedToId == id then + if bit32.band(flags, ImGuiMultiSelectFlags.NoAutoSelect) == 0 then + if is_ctrl and is_shift then + pressed = true + elseif not is_ctrl then + selected = true; pressed = true + end + else + -- With NoAutoSelect, using Shift+keyboard performs a write/copy + if is_shift then + pressed = true + elseif not is_ctrl then + apply_to_range_src = true -- Since if (pressed) {} main block is not running we update this + end + end + end + + if apply_to_range_src then + storage.RangeSrcItem = item_data + storage.RangeSelected = selected and 1 or 0 -- Will be updated at the end of this function anyway. + end + + -- Box-select toggle handling + if ms.BoxSelectId ~= 0 then + local bs = ImGui.GetBoxSelectState(ms.BoxSelectId) + if bs then + local item_rect = MultiSelectScratchItemRect + ImRect_Copy(item_rect, g.LastItemData.Rect) + if not window.DC.NavIsScrollPushableX then -- FIXME: Rename to be more generic. + local table_ = g.CurrentTable + if table_ and table_.CurrentColumn ~= -1 then + -- FIXME: We cannot solely use current ClipRect as it includes HostClipRect. + -- However we account for ClipRect being larger than current column (e.g. when using SpanAllColumns) + -- A more generic version would be nice, but window.WorkRect.Min/Max exclude CellPadding. (#7994, #9383) + local column = table_.Columns[table_.CurrentColumn] + -- [Port] upstream tests ItemFlags against ImGuiItemStatusFlags_HasClipRect (sic), reproduced as-is. + local has_clip_rect = bit32.band(g.LastItemData.ItemFlags, ImGuiItemStatusFlags.HasClipRect) ~= 0 + local clip_min_x = has_clip_rect and g.LastItemData.ClipRect.Min.x or window.ClipRect.Min.x + local clip_max_x = has_clip_rect and g.LastItemData.ClipRect.Max.x or window.ClipRect.Max.x + if clip_min_x ~= clip_max_x then -- When zero sized we expect that bounds have been clamped and thus are unreliable + item_rect.Min.x = ImMax(item_rect.Min.x, ImMin(column.MinX, clip_min_x)) + item_rect.Max.x = ImMin(item_rect.Max.x, ImMax(column.MaxX, clip_max_x)) + else + item_rect.Min.x = ImMax(item_rect.Min.x, column.MinX) + item_rect.Max.x = ImMin(item_rect.Max.x, column.MaxX) + end + --GetForegroundDrawList():AddRect(item_rect.Min, item_rect.Max, IM_COL32(255, 0, 255, 255)) + end + end + local rect_overlap_curr = bs.BoxSelectRectCurr:Overlaps(item_rect) + local rect_overlap_prev = bs.BoxSelectRectPrev:Overlaps(item_rect) + if (rect_overlap_curr and not rect_overlap_prev and not selected) or (rect_overlap_prev and not rect_overlap_curr) then + if storage.LastSelectionSize <= 0 and bs.IsStartedSetNavIdOnce then + pressed = true -- First item act as a pressed: code below will emit selection request and set NavId (whatever we emit here will be overridden anyway) + bs.IsStartedSetNavIdOnce = false + else + selected = not selected + ImGui.MultiSelectAddSetRange(ms, selected, 1, item_data, item_data) + end + storage.LastSelectionSize = ImMax(storage.LastSelectionSize + 1, 1) + end + end + end + + -- Right-click handling. + -- FIXME-MULTISELECT: Maybe should be moved to Selectable()? Also see #5816, #8200, #9015 + if hovered and ImGui.IsMouseClicked(1) and bit32.band(flags, bit32.bor(ImGuiMultiSelectFlags.NoAutoSelect, ImGuiMultiSelectFlags.NoSelectOnRightClick)) == 0 then + if g.ActiveId ~= 0 and g.ActiveId ~= id then + ImGui.ClearActiveID() + end + ImGui.SetFocusID(id, window) + if not pressed and not selected then + pressed = true + is_ctrl = false; is_shift = false + end + end + + -- Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected. + -- The later, "unless current item is not select", may become optional? It seems like a better default if Enter doesn't necessarily open something + -- (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional? + local enter_pressed = pressed and (g.NavActivateId == id) and bit32.band(g.NavActivateFlags, ImGuiActivateFlags.PreferInput) ~= 0 + + -- Alter selection + if pressed and (not enter_pressed or not selected) then + -- Box-select + local input_source = (g.NavJustMovedToId == id or g.NavActivateId == id) and g.NavInputSource or ImGuiInputSource.Mouse + if bit32.band(flags, bit32.bor(ImGuiMultiSelectFlags.BoxSelect1d, ImGuiMultiSelectFlags.BoxSelect2d)) ~= 0 then + if not g.BoxSelectState.IsActive and not g.BoxSelectState.IsStarting and input_source == ImGuiInputSource.Mouse and g.IO.MouseClickedCount[0] == 1 then + BoxSelectPreStartDrag(ms.BoxSelectId, item_data) + end + end + + ------------------------------------------------------------------------------------------ + -- ACTION | Begin | Pressed/Activated | End + ------------------------------------------------------------------------------------------ + -- Keys Navigated: | Clear | Src=item, Sel=1 SetRange 1 + -- Keys Navigated: Ctrl | n/a | n/a + -- Keys Navigated: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + -- Keys Navigated: Ctrl+Shift | n/a | Dst=item, Sel=Src => Clear + SetRange Src-Dst + -- Keys Activated: | n/a | Src=item, Sel=1 => Clear + SetRange 1 + -- Keys Activated: Ctrl | n/a | Src=item, Sel=!Sel => SetSange 1 + -- Keys Activated: Shift | n/a | Dst=item, Sel=1 => Clear + SetSange 1 + ------------------------------------------------------------------------------------------ + -- Mouse Pressed: | n/a | Src=item, Sel=1, => Clear + SetRange 1 + -- Mouse Pressed: Ctrl | n/a | Src=item, Sel=!Sel => SetRange 1 + -- Mouse Pressed: Shift | n/a | Dst=item, Sel=1, => Clear + SetRange 1 + -- Mouse Pressed: Ctrl+Shift | n/a | Dst=item, Sel=!Sel => SetRange Src-Dst + ------------------------------------------------------------------------------------------ + + if bit32.band(flags, ImGuiMultiSelectFlags.NoAutoClear) == 0 then + local request_clear = false + if is_singleselect then + request_clear = true + elseif (input_source == ImGuiInputSource.Mouse or g.NavActivateId == id) and not is_ctrl then + if bit32.band(flags, ImGuiMultiSelectFlags.NoAutoClearOnReselect) ~= 0 then request_clear = not selected else request_clear = true end + elseif (input_source == ImGuiInputSource.Keyboard or input_source == ImGuiInputSource.Gamepad) and is_shift and not is_ctrl then + request_clear = true -- With is_shift==false the RequestClear was done in BeginIO, not necessary to do again. + end + if request_clear then + ImGui.MultiSelectAddSetAll(ms, false) + end + end + + local range_direction + local range_selected + if is_shift and not is_singleselect then + --IM_ASSERT(storage.HasRangeSrc and storage.HasRangeValue) + if storage.RangeSrcItem == ImGuiSelectionUserData_Invalid then + storage.RangeSrcItem = item_data + end + if bit32.band(flags, ImGuiMultiSelectFlags.CheckboxMode_) == 0 then + -- Shift+Arrow always select + -- Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage.RangeSelected) + if is_ctrl and storage.RangeSelected ~= -1 then range_selected = (storage.RangeSelected ~= 0) else range_selected = true end + else + -- Shift+Arrow copy source selection state + -- Shift+Click always copy from target selection state + if ms.IsKeyboardSetRange then + if storage.RangeSelected ~= -1 then range_selected = (storage.RangeSelected ~= 0) else range_selected = true end + else + range_selected = not selected + end + end + range_direction = ms.RangeSrcPassedBy and 1 or -1 + else + -- Ctrl inverts selection, otherwise always select + if bit32.band(flags, ImGuiMultiSelectFlags.CheckboxMode_) == 0 then + if is_ctrl then selected = not selected else selected = true end + else + selected = not selected + end + storage.RangeSrcItem = item_data + range_selected = selected + range_direction = 1 + end + ImGui.MultiSelectAddSetRange(ms, range_selected, range_direction, storage.RangeSrcItem, item_data) + end + + -- Update/store the selection state of the Source item (used by Ctrl+Shift, when Source is unselected we perform a range unselect) + if storage.RangeSrcItem == item_data then + storage.RangeSelected = selected and 1 or 0 + end + + -- Update/store the selection state of focused item + if g.NavId == id then + storage.NavIdItem = item_data + storage.NavIdSelected = selected and 1 or 0 + end + if storage.NavIdItem == item_data then + ms.NavIdPassedBy = true + end + + return selected, pressed +end + +--- @param ms ImGuiMultiSelectTempData +--- @param selected bool +function ImGui.MultiSelectAddSetAll(ms, selected) + local req = MultiSelectScratchRequest -- ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid } + req.Type = ImGuiSelectionRequestType.SetAll; req.Selected = selected; req.RangeDirection = 0; req.RangeFirstItem = ImGuiSelectionUserData_Invalid; req.RangeLastItem = ImGuiSelectionUserData_Invalid + ms.IO.Requests:resize(0) -- Can always clear previous requests + ms.IO.Requests:push_back(req) -- Add new request +end + +--- @param ms ImGuiMultiSelectTempData +--- @param selected bool +--- @param range_dir int +--- @param first_item ImGuiSelectionUserData +--- @param last_item ImGuiSelectionUserData +function ImGui.MultiSelectAddSetRange(ms, selected, range_dir, first_item, last_item) + -- Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges) + -- FIXME-OPT: Disabled on 2026/04/09 as this would break with any form of coarse clipping that we don't know about (e.g. TableNextColumn() return value). + -- The low-hanging fruit would be to know that ImGuiSelectionUserData are sequential indices, in which case we can trivially compare PrevSubmittedItem + RangeDir == FirstItem. + -- User can always perform this merge if required. + -- (#if 0 block not ported) + + local req = MultiSelectScratchRequest -- ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item } + req.Type = ImGuiSelectionRequestType.SetRange; req.Selected = selected; req.RangeDirection = range_dir + if range_dir > 0 then + req.RangeFirstItem = first_item; req.RangeLastItem = last_item + else + req.RangeFirstItem = last_item; req.RangeLastItem = first_item + end + ms.IO.Requests:push_back(req) -- Add new request +end + +--- @param storage ImGuiMultiSelectState +function ImGui.DebugNodeMultiSelectState(storage) + local is_active = (storage.LastFrameActive >= ImGui.GetFrameCount() - 2) -- Note that fully clipped early out scrolling tables will appear as inactive here. + if not is_active then ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetStyleColorVec4(ImGuiCol.TextDisabled)) end + local open = ImGui.TreeNode(storage.ID, "MultiSelect 0x%08X in '%s'%s", storage.ID, storage.Window and storage.Window.Name or "N/A", is_active and "" or " *Inactive*") + if not is_active then ImGui.PopStyleColor() end + if not open then + return + end + -- [Port] ImGuiSelectionUserData may be any Lua value: printed with tostring() instead of %lld (0x%llX) + ImGui.Text("RangeSrcItem = %s, RangeSelected = %d", tostring(storage.RangeSrcItem), storage.RangeSelected) + ImGui.Text("NavIdItem = %s, NavIdSelected = %d", tostring(storage.NavIdItem), storage.NavIdSelected) + ImGui.Text("LastSelectionSize = %d", storage.LastSelectionSize) -- Provided by user + ImGui.TreePop() +end + +------------------------------------------------------------------------------- +-- [SECTION] Widgets: Multi-Select helpers +------------------------------------------------------------------------------- +-- - ImGuiSelectionBasicStorage +-- - ImGuiSelectionExternalStorage +------------------------------------------------------------------------------- +-- [Port] Storage representation: +-- - Upstream _Storage is an ImGuiStorage (vector of {ImGuiID key, int val_i} pairs sorted by key, val_i = selection order +-- or 0 when unselected). Here _Storage is a plain Lua table: [id] = selection order (always > 0); unselected ids are +-- removed (nil) instead of being kept with val_i == 0. Contains()/SetItemSelected() are O(1). +-- - Iteration: `local it, id = nil, 0; local has_more; while true do has_more, it, id = selection:GetNextSelectedItem(it) if not has_more then break end ... end` +-- (C++: void* it = NULL; ImGuiID id; while (selection.GetNextSelectedItem(&it, &id)) {...}). The opaque iterator is an +-- integer position into _IterKeys, a snapshot of the selected ids sorted by key (or by selection order when +-- PreserveOrder is set), rebuilt when an iteration starts (it == nil). Ids unselected during an iteration are skipped. +-- A storage supports one iteration at a time (starting a new one rebuilds the shared snapshot). +-- - Keys returned by AdapterIndexToStorageId must be mutually comparable with '<' (numbers by default) for the sort. + +--- @class ImGuiSelectionBasicStorage +--- @field Size int # Number of selected items, maintained by this helper. +--- @field PreserveOrder bool # = false // GetNextSelectedItem() will return ordered selection (currently implemented by two additional sorts of selection. Could be improved) +--- @field UserData any # = nil // User data for use by adapter function // e.g. selection.UserData = my_items +--- @field AdapterIndexToStorageId fun(self: ImGuiSelectionBasicStorage, idx: int): ImGuiID # e.g. selection.AdapterIndexToStorageId = function(self, idx) return self.UserData[idx + 1].ID end +--- @field _SelectionOrder int # [Internal] Increasing counter to store selection order +--- @field _Storage table # [Internal] Selection set. Think of this as similar to e.g. std::set. Prefer not accessing directly: iterate with GetNextSelectedItem(). +MT.ImGuiSelectionBasicStorage = {} +MT.ImGuiSelectionBasicStorage.__index = MT.ImGuiSelectionBasicStorage + +local function ImGuiSelectionBasicStorage_DefaultAdapterIndexToStorageId(_, idx) return idx end + +--- @return ImGuiSelectionBasicStorage +function ImGuiSelectionBasicStorage() + local this = setmetatable({}, MT.ImGuiSelectionBasicStorage) + this.Size = 0 + this.PreserveOrder = false + this.UserData = nil + this.AdapterIndexToStorageId = ImGuiSelectionBasicStorage_DefaultAdapterIndexToStorageId -- [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; } + this._SelectionOrder = 1 -- Always >0 + this._Storage = {} + this._IterKeys = {} -- [Port] GetNextSelectedItem() snapshot + return this +end + +function MT.ImGuiSelectionBasicStorage:Clear() + self.Size = 0 + self._SelectionOrder = 1 -- Always >0 + table.clear(self._Storage) +end + +--- @param r ImGuiSelectionBasicStorage +function MT.ImGuiSelectionBasicStorage:Swap(r) + self.Size, r.Size = r.Size, self.Size + self._SelectionOrder, r._SelectionOrder = r._SelectionOrder, self._SelectionOrder + self._Storage, r._Storage = r._Storage, self._Storage +end + +--- @param id ImGuiID +--- @return bool +function MT.ImGuiSelectionBasicStorage:Contains(id) + return self._Storage[id] ~= nil +end + +local PairComparerStorage = nil +local function PairComparerByValueInt(lhs, rhs) + return PairComparerStorage[lhs] < PairComparerStorage[rhs] +end + +-- GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user. +-- (e.g. store unselected vs compact down, compact down on demand, use raw ImVector instead of ImGuiStorage...) +-- [Port] `has_more, opaque_it, out_id = selection:GetNextSelectedItem(opaque_it)`: pass nil to start, then the returned opaque_it. +--- @param opaque_it any +--- @return bool, any, ImGuiID +function MT.ImGuiSelectionBasicStorage:GetNextSelectedItem(opaque_it) + local storage = self._Storage + local keys = self._IterKeys + local it = opaque_it + if it == nil then + table.clear(keys) + local n = 0 + for id in pairs(storage) do n = n + 1; keys[n] = id end + if self.PreserveOrder then + PairComparerStorage = storage + table.sort(keys, PairComparerByValueInt) -- ~ImGuiStorage::BuildSortByValueInt() + PairComparerStorage = nil + else + table.sort(keys) -- ImGuiStorage is sorted by key + end + it = 1 + end + local it_end = #keys + 1 + IM_ASSERT(it >= 1 and it <= it_end) + while it < it_end and storage[keys[it]] == nil do + it = it + 1 + end + local has_more = (it ~= it_end) + if has_more then + return true, it + 1, keys[it] + end + return false, it, 0 +end + +--- @param id ImGuiID +--- @param selected bool +function MT.ImGuiSelectionBasicStorage:SetItemSelected(id, selected) + local storage = self._Storage + local p_int = storage[id] + if selected and p_int == nil then storage[id] = self._SelectionOrder; self._SelectionOrder = self._SelectionOrder + 1; self.Size = self.Size + 1 + elseif not selected and p_int ~= nil then storage[id] = nil; self.Size = self.Size - 1 end +end + +--- @param idx int +--- @return ImGuiID +function MT.ImGuiSelectionBasicStorage:GetStorageIdFromIndex(idx) return self.AdapterIndexToStorageId(self, idx) end -- Convert index to item id based on provided adapter. + +-- Optimized for batch edits (with same value of 'selected') +-- [Port] With a hash table there is nothing to defer: insert/remove in place. size_before_amends is unused. +local function ImGuiSelectionBasicStorage_BatchSetItemSelected(selection, id, selected, size_before_amends, selection_order) + local storage = selection._Storage + local is_contained = storage[id] ~= nil + if selected == is_contained then + return + end + if selected then + storage[id] = selection_order + else + storage[id] = nil + end + selection.Size = selection.Size + (selected and 1 or -1) +end + +-- [Port] Upstream sorts the ImGuiStorage vector here (BuildSortByKey). The Lua table needs no sorting: GetNextSelectedItem() sorts its snapshot. +local function ImGuiSelectionBasicStorage_BatchFinish(selection, selected, size_before_amends) +end + +-- Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +-- - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen. +-- - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem. +-- - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection. +-- - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform +-- a lookup in order to have some way to iterate/interpolate between two items. +-- - A full-featured application is likely to allow search/filtering which is likely to lead to using indices +-- and constructing a view index <> object id/ptr data structure anyway. +-- WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ImGuiSelectionBasicStorage' INDIRECTION LOGIC. +-- Notice that with the simplest adapter (using indices everywhere), all functions return their parameters. +-- The most simple implementation (using indices everywhere) would look like: +-- for _, req in ms_io.Requests:iter() do +-- if req.Type == ImGuiSelectionRequestType.SetAll then Clear(); if req.Selected then for n = 0, items_count - 1 do SetItemSelected(n, true) end end end +-- if req.Type == ImGuiSelectionRequestType.SetRange then for n = req.RangeFirstItem, req.RangeLastItem do SetItemSelected(n, req.Selected) end end +-- end +--- @param ms_io ImGuiMultiSelectIO +function MT.ImGuiSelectionBasicStorage:ApplyRequests(ms_io) + -- For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional. + -- It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect(). + -- Other scheme may handle SetAll differently. + IM_ASSERT(ms_io.ItemsCount ~= -1, "Missing value for items_count in BeginMultiSelect() call!") + IM_ASSERT(self.AdapterIndexToStorageId ~= nil) + + -- This is optimized/specialized to cope with very large selections (e.g. 100k+ items) + -- - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish(). + -- - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass. + -- - A more optimal version wouldn't even use ImGuiStorage but directly a ImVector to reduce bandwidth, but this is a reasonable trade off to reuse code. + -- - There are many ways this could be better optimized. The worse case scenario being: using BoxSelect2d in a grid, box-select scrolling down while wiggling + -- left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each. + -- FIXME-OPT: For each block of consecutive SetRange request: + -- - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage. + -- - rewrite sorted storage a single time. + for _, req in ms_io.Requests:iter() do + if req.Type == ImGuiSelectionRequestType.SetAll then + self:Clear() + if req.Selected then + local size_before_amends = self.Size -- [Port] _Storage.Data.Size (0 after Clear()) + for idx = 0, ms_io.ItemsCount - 1 do + ImGuiSelectionBasicStorage_BatchSetItemSelected(self, self:GetStorageIdFromIndex(idx), req.Selected, size_before_amends, self._SelectionOrder) + self._SelectionOrder = self._SelectionOrder + 1 + end + ImGuiSelectionBasicStorage_BatchFinish(self, req.Selected, size_before_amends) + end + elseif req.Type == ImGuiSelectionRequestType.SetRange then + local selection_changes = req.RangeLastItem - req.RangeFirstItem + 1 + --local g = GImGui; IMGUI_DEBUG_LOG_SELECTION("Req %d/%d: set %d to %d\n", ms_io.Requests:index_from_ptr(req), ms_io.Requests.Size, selection_changes, req.Selected) + if selection_changes == 1 or (selection_changes < math.floor(self.Size / 100)) then + -- Multiple sorted insertion + copy likely to be faster. + -- Technically we could do a single copy with a little more work (sort sequential SetRange requests) + for idx = req.RangeFirstItem, req.RangeLastItem do + self:SetItemSelected(self:GetStorageIdFromIndex(idx), req.Selected) + end + else + -- Append insertion + single sort likely be faster. + -- Use req.RangeDirection to set order field so that Shift+Clicking from 1 to 5 is different than Shift+Clicking from 5 to 1 + local size_before_amends = self.Size + local selection_order = self._SelectionOrder + ((req.RangeDirection < 0) and selection_changes - 1 or 0) + for idx = req.RangeFirstItem, req.RangeLastItem do + ImGuiSelectionBasicStorage_BatchSetItemSelected(self, self:GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order) + selection_order = selection_order + req.RangeDirection + end + if req.Selected then + self._SelectionOrder = self._SelectionOrder + selection_changes + end + ImGuiSelectionBasicStorage_BatchFinish(self, req.Selected, size_before_amends) + end + end + end +end + +------------------------------------------------------------------------------- + +-- Optional helper to apply multi-selection requests to existing randomly accessible storage. +-- Convenient if you want to quickly wire multi-select API on e.g. an array of bool or items storing their own selection state. +--- @class ImGuiSelectionExternalStorage +--- @field UserData any # User data for use by adapter function // e.g. selection.UserData = my_items +--- @field AdapterSetItemSelected fun(self: ImGuiSelectionExternalStorage, idx: int, selected: bool) # e.g. AdapterSetItemSelected = function(self, idx, selected) self.UserData[idx + 1].Selected = selected end +MT.ImGuiSelectionExternalStorage = {} +MT.ImGuiSelectionExternalStorage.__index = MT.ImGuiSelectionExternalStorage + +--- @return ImGuiSelectionExternalStorage +function ImGuiSelectionExternalStorage() + local this = setmetatable({}, MT.ImGuiSelectionExternalStorage) + this.UserData = nil + this.AdapterSetItemSelected = nil + return this +end + +-- Apply requests coming from BeginMultiSelect() and EndMultiSelect(). +-- We also pull 'ms_io.ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage +-- This makes no assumption about underlying storage. +--- @param ms_io ImGuiMultiSelectIO +function MT.ImGuiSelectionExternalStorage:ApplyRequests(ms_io) + IM_ASSERT(self.AdapterSetItemSelected) + for _, req in ms_io.Requests:iter() do + if req.Type == ImGuiSelectionRequestType.SetAll then + for idx = 0, ms_io.ItemsCount - 1 do + self.AdapterSetItemSelected(self, idx, req.Selected) + end + end + if req.Type == ImGuiSelectionRequestType.SetRange then + for idx = req.RangeFirstItem, req.RangeLastItem do + self.AdapterSetItemSelected(self, idx, req.Selected) + end + end + end +end + +end -- do (Typing-Select, Box-Select, Multi-Select) + ------------------------------------------------------------------------------- -- [SECTION] Widgets: ListBox ------------------------------------------------------------------------------- diff --git a/tools/test/check_all.sh b/tools/test/check_all.sh index cb56003..0dea0d6 100755 --- a/tools/test/check_all.sh +++ b/tools/test/check_all.sh @@ -13,6 +13,7 @@ CHECKS=( "readme-min|README OK|MINIFY=1 $R --main tools/test/scripts/t_readme.luau --frames 30" "cached|CACHED OK|$R --main tools/test/scripts/t_cached_window.luau --frames 130" "fixes|FIXES OK|$R --main tools/test/scripts/t_fixes.luau --frames 55" +"multiselect|MULTISELECT OK|$R --main tools/test/scripts/t_multiselect.luau --frames 380" "nodemo|NODEMO OK|NO_DEMO=1 $R --main tools/test/scripts/t_nodemo.luau --frames 25" "widgets|OK|$R --main tools/test/scripts/t_widgets.luau --frames 80" "tables|OK|$R --main tools/test/scripts/t_tables.luau --frames 150" diff --git a/tools/test/scripts/t_multiselect.luau b/tools/test/scripts/t_multiselect.luau new file mode 100644 index 0000000..a3fb978 --- /dev/null +++ b/tools/test/scripts/t_multiselect.luau @@ -0,0 +1,265 @@ +-- Multi-select / box-select / typing-select: ImGuiSelectionBasicStorage + Selectable list (click, Ctrl+click, Shift+click, Ctrl+A, +-- Escape, box-select drag, keyboard nav + Shift, typing-select), ImGuiListClipper + IncludeItemByIndex(RangeSrcItem), +-- ImGuiSelectionExternalStorage on a bool array, checkboxes and tree nodes inside a multi-select scope. +local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui")) +local ImGui, ImVec2 = G.ImGui, G.ImVec2 +local MSF = G.ImGuiMultiSelectFlags +ImGui.CreateContext(); G.ImGui_ImplRoblox.Init() +local io = ImGui.GetIO() +io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.NavEnableKeyboard) +local failed = {} +local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end + +local NAMES = { "Apple", "Apricot", "Banana", "Blueberry", "Cherry", "Coconut", "Date", "Durian", "Elderberry", "Fig", + "Grape", "Guava", "Kiwi", "Lemon", "Lime", "Mango", "Melon", "Orange", "Papaya", "Peach" } +local N = #NAMES + +-- Iterate with GetNextSelectedItem() (sorted by key) -> "a,b,c" +local function sel_str(sel) + local out, it, id, has_more = {}, nil, 0, false + while true do + has_more, it, id = sel:GetNextSelectedItem(it) + if not has_more then break end + table.insert(out, tostring(id)) + end + return table.concat(out, ",") +end +local function bools_str(t, n) + local out = {} + for i = 1, n do if t[i] then table.insert(out, tostring(i - 1)) end end + return table.concat(out, ",") +end + +local sel_list = G.ImGuiSelectionBasicStorage() +local sel_clip = G.ImGuiSelectionBasicStorage() +local sel_tree = G.ImGuiSelectionBasicStorage() +local ext_bools = {} +local sel_ext = G.ImGuiSelectionExternalStorage() +sel_ext.UserData = ext_bools +sel_ext.AdapterSetItemSelected = function(self, idx, selected) self.UserData[idx + 1] = selected end +local chk_bools = {} +local sel_chk = G.ImGuiSelectionExternalStorage() +sel_chk.UserData = chk_bools +sel_chk.AdapterSetItemSelected = function(self, idx, selected) self.UserData[idx + 1] = selected end + +local rects = {} -- [name] = { min, max } (copies) +local ids = {} -- [name] = item id +local function rect() local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); return { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end +local function center(name) local r = rects[name]; return (r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2 end +local function key(k) io:AddKeyEvent(k, true); io:AddKeyEvent(k, false) end +local function mods(ctrl, shift) io:AddKeyEvent(G.ImGuiMod_Ctrl, ctrl == true); io:AddKeyEvent(G.ImGuiMod_Shift, shift == true) end + +-- Schedule: each step = { input = fn (before NewFrame), check = fn (after Render) }. Steps run on consecutive frames. +local steps = {} +local function step(input, chk) table.insert(steps, { input = input, check = chk }) end +local function idle(n) for _ = 1, n or 1 do step(nil, nil) end end +-- click: press (+mods) on frame A, release on frame B, then one idle frame for EndMultiSelect requests to be applied +local function click(name, ctrl, shift) + step(function() mods(ctrl, shift); io:AddMousePosEvent(center(name)); io:AddMouseButtonEvent(0, true) end) + step(function() io:AddMouseButtonEvent(0, false) end) + step(function() mods(false, false) end) +end + +local typed_focus_idx = -1 +local clip_scroll_request = false +local f = 0 + +idle(3) +-- 1. List: click selects one +click("L2"); step(nil, function() check(sel_str(sel_list) == "2", "click: " .. sel_str(sel_list)) end) +-- 2. Ctrl+click toggles +click("L5", true); step(nil, function() check(sel_str(sel_list) == "2,5", "ctrl+click add: " .. sel_str(sel_list)) end) +click("L2", true); step(nil, function() check(sel_str(sel_list) == "5", "ctrl+click remove: " .. sel_str(sel_list)) end) +-- 3. Shift+click range (from RangeSrc = 2, the last Ctrl+clicked item) +click("L8", false, true); step(nil, function() check(sel_str(sel_list) == "2,3,4,5,6,7,8", "shift+click range: " .. sel_str(sel_list)) end) +-- 4. Ctrl+A +step(function() io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.A, true) end) +step(function() io:AddKeyEvent(G.ImGuiKey.A, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end) +step(nil, function() check(sel_list.Size == N, "ctrl+A size " .. sel_list.Size) end) +-- 5. Escape clears (ClearOnEscape) +step(function() key(G.ImGuiKey.Escape) end) +step(nil, function() check(sel_list.Size == 0 and sel_str(sel_list) == "", "escape clear: " .. sel_str(sel_list)) end) +-- 6. Box-select drag (BoxSelect1d) from item 2 to item 5 +step(function() io:AddMousePosEvent(center("L2")); io:AddMouseButtonEvent(0, true) end) +step(function() local x, y = center("L2"); io:AddMousePosEvent(x + 2, y + 10) end) +step(function() local x, y = center("L4"); io:AddMousePosEvent(x + 4, y) end) +step(function() local x, y = center("L5"); io:AddMousePosEvent(x + 4, y) end, + function() check(ImGui.GetCurrentContext().BoxSelectState.IsActive, "box-select active while dragging") end) +step(nil) +step(function() io:AddMouseButtonEvent(0, false) end) +step(nil, function() check(sel_str(sel_list) == "2,3,4,5", "box-select: " .. sel_str(sel_list)); check(not ImGui.GetCurrentContext().BoxSelectState.IsActive, "box-select ended") end) +-- 7. Keyboard nav: click 3, Down -> 4 (auto-select), Shift+Down x2 -> 4..6 +click("L3"); step(nil, function() check(sel_str(sel_list) == "3", "click before nav: " .. sel_str(sel_list)) end) +step(function() key(G.ImGuiKey.DownArrow) end); idle(2) +step(nil, function() check(sel_str(sel_list) == "4", "nav down auto-select: " .. sel_str(sel_list)) end) +step(function() io:AddKeyEvent(G.ImGuiMod_Shift, true); key(G.ImGuiKey.DownArrow) end); idle(1) +step(function() key(G.ImGuiKey.DownArrow) end); idle(1) +step(function() io:AddKeyEvent(G.ImGuiMod_Shift, false) end); idle(1) +step(nil, function() check(sel_str(sel_list) == "4,5,6", "shift+nav range: " .. sel_str(sel_list)) end) +-- 8. Typing-select: "ch" -> Cherry (5), then single-char mode "b" "b" -> Banana (2) then Blueberry (3) +step(function() io:AddInputCharacter(string.byte("c")); io:AddInputCharacter(string.byte("h")) end); idle(2) +step(nil, function() check(ImGui.GetCurrentContext().NavId == ids.L4, "typing 'ch' -> Cherry"); check(sel_str(sel_list) == "4", "typing focus selects: " .. sel_str(sel_list)) end) +idle(120) -- TYPING_SELECT_RESET_TIMER +step(function() io:AddInputCharacter(string.byte("b")) end); idle(2) +step(nil, function() check(ImGui.GetCurrentContext().NavId == ids.L2, "typing 'b' -> Banana") end) +step(function() io:AddInputCharacter(string.byte("b")) end); idle(2) +step(nil, function() check(ImGui.GetCurrentContext().NavId == ids.L3, "typing 'bb' -> Blueberry") end) +idle(120) + +-- 9. Clipper (1000 items): click 5, scroll down, Shift+click item 40 -> 5..40 (RangeSrcItem 5 kept submitted by IncludeItemByIndex) +click("C5") +-- move NavId away with Ctrl+Down (no selection change, RangeSrc stays 5) so the clipper doesn't keep item 5 alive as the nav item +step(function() io:AddKeyEvent(G.ImGuiMod_Ctrl, true); key(G.ImGuiKey.DownArrow) end); idle(1) +step(function() key(G.ImGuiKey.DownArrow) end); idle(1) +step(function() key(G.ImGuiKey.DownArrow) end); idle(1) +step(function() io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end) +step(nil, function() check(sel_str(sel_clip) == "5", "clipper ctrl+nav keeps selection: " .. sel_str(sel_clip)) end) +step(function() clip_scroll_request = true end); idle(2) +step(nil, function() check(rects.C40 ~= nil and rects.C5 == nil, "clipper scrolled (item 5 not visible)") end) +click("C40", false, true) +step(nil, function() check(sel_clip.Size == 36, "clipper shift+click range size " .. sel_clip.Size); check(sel_clip:Contains(5) and sel_clip:Contains(40) and not sel_clip:Contains(4) and not sel_clip:Contains(41), "clipper range bounds") end) + +-- 10. External storage on bool array: click 1, Shift+click 3, Ctrl+click 2 +click("E1"); click("E3", false, true) +step(nil, function() check(bools_str(ext_bools, 10) == "1,2,3", "external storage range: " .. bools_str(ext_bools, 10)) end) +click("E2", true) +step(nil, function() check(bools_str(ext_bools, 10) == "1,3", "external storage ctrl+click: " .. bools_str(ext_bools, 10)) end) + +-- 11. Checkboxes (NoAutoSelect|NoAutoClear): click 1 toggles on, Shift+click 4 -> 1..4 on, click 2 toggles off +click("K1"); step(nil, function() check(bools_str(chk_bools, 8) == "1", "checkbox click: " .. bools_str(chk_bools, 8)) end) +click("K4", false, true); step(nil, function() check(bools_str(chk_bools, 8) == "1,2,3,4", "checkbox shift+click: " .. bools_str(chk_bools, 8)) end) +click("K2"); step(nil, function() check(bools_str(chk_bools, 8) == "1,3,4", "checkbox toggle off: " .. bools_str(chk_bools, 8)) end) + +-- 12. Tree nodes: click 0, Ctrl+click 2, Shift+click 4 (range from 2) +click("T0"); click("T2", true) +step(nil, function() check(sel_str(sel_tree) == "0,2", "tree ctrl+click: " .. sel_str(sel_tree)) end) +click("T4", false, true) +step(nil, function() check(sel_str(sel_tree) == "2,3,4", "tree shift+click: " .. sel_str(sel_tree)) end) + +-- 13. Metrics window renders the TypingSelect / MultiSelect debug nodes +local show_metrics = false +step(function() show_metrics = true end); idle(3) +step(nil, function() if #failed == 0 then print("MULTISELECT OK") end end) + +local function list_window() + ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(200, 420)) + ImGui.Begin("L") + local flags = bit32.bor(MSF.ClearOnEscape, MSF.BoxSelect1d) + local ms_io = ImGui.BeginMultiSelect(flags, sel_list.Size, N) + sel_list:ApplyRequests(ms_io) + -- typing-select + local focus_idx = -1 + if ImGui.IsWindowFocused() then + local req = ImGui.GetTypingSelectRequest(G.ImGuiTypingSelectFlags.AllowSingleCharMode) + local nav_idx = (type(ms_io.NavIdItem) == "number") and ms_io.NavIdItem or -1 + focus_idx = ImGui.TypingSelectFindMatch(req, N, function(_, idx) return NAMES[idx + 1] end, nil, nav_idx) + end + for n = 0, N - 1 do + if n == focus_idx then ImGui.SetKeyboardFocusHere() end + ImGui.SetNextItemSelectionUserData(n) + ImGui.Selectable(NAMES[n + 1], sel_list:Contains(n)) + rects["L" .. n] = rect(); ids["L" .. n] = ImGui.GetItemID() + end + ms_io = ImGui.EndMultiSelect() + sel_list:ApplyRequests(ms_io) + ImGui.End() +end + +local function clipper_window() + ImGui.SetNextWindowPos(ImVec2(220, 10)); ImGui.SetNextWindowSize(ImVec2(200, 300)) + ImGui.Begin("C") + if clip_scroll_request then ImGui.SetScrollY(ImGui.GetTextLineHeightWithSpacing() * 30); clip_scroll_request = false end + local COUNT = 1000 + local ms_io = ImGui.BeginMultiSelect(MSF.ClearOnEscape, sel_clip.Size, COUNT) + sel_clip:ApplyRequests(ms_io) + local clipper = G.ImGuiListClipper() + clipper:Begin(COUNT) + if ms_io.RangeSrcItem ~= -1 then clipper:IncludeItemByIndex(ms_io.RangeSrcItem) end -- Ensure RangeSrc item is not clipped. + for k in rects do if string.sub(k, 1, 1) == "C" then rects[k] = nil end end + while clipper:Step() do + for n = clipper.DisplayStart, clipper.DisplayEnd - 1 do + ImGui.SetNextItemSelectionUserData(n) + ImGui.Selectable(("Object %04d"):format(n), sel_clip:Contains(n)) + if ImGui.IsItemVisible() then rects["C" .. n] = rect() end + end + end + ms_io = ImGui.EndMultiSelect() + sel_clip:ApplyRequests(ms_io) + ImGui.End() +end + +local function external_window() + ImGui.SetNextWindowPos(ImVec2(430, 10)); ImGui.SetNextWindowSize(ImVec2(180, 220)) + ImGui.Begin("E") + local ms_io = ImGui.BeginMultiSelect(MSF.None, -1, 10) + sel_ext:ApplyRequests(ms_io) + for n = 0, 9 do + ImGui.SetNextItemSelectionUserData(n) + ImGui.Selectable("Ext " .. n, ext_bools[n + 1] == true) + rects["E" .. n] = rect() + end + ms_io = ImGui.EndMultiSelect() + sel_ext:ApplyRequests(ms_io) + ImGui.End() +end + +local function checkbox_window() + ImGui.SetNextWindowPos(ImVec2(430, 240)); ImGui.SetNextWindowSize(ImVec2(180, 200)) + ImGui.Begin("K") + local ms_io = ImGui.BeginMultiSelect(bit32.bor(MSF.NoAutoSelect, MSF.NoAutoClear, MSF.ClearOnEscape), -1, 8) + sel_chk:ApplyRequests(ms_io) + for n = 0, 7 do + ImGui.SetNextItemSelectionUserData(n) + local _, v = ImGui.Checkbox("Check " .. n, chk_bools[n + 1] == true) + rects["K" .. n] = rect() + local _ = v -- the selection is updated through requests (ApplyRequests), like upstream's demo + end + ms_io = ImGui.EndMultiSelect() + sel_chk:ApplyRequests(ms_io) + ImGui.End() +end + +local function tree_window() + ImGui.SetNextWindowPos(ImVec2(220, 320)); ImGui.SetNextWindowSize(ImVec2(200, 160)) + ImGui.Begin("T") + local ms_io = ImGui.BeginMultiSelect(MSF.None, sel_tree.Size, 5) + sel_tree:ApplyRequests(ms_io) + for n = 0, 4 do + local flags = bit32.bor(G.ImGuiTreeNodeFlags.Leaf, G.ImGuiTreeNodeFlags.NoTreePushOnOpen, G.ImGuiTreeNodeFlags.SpanAvailWidth) + if sel_tree:Contains(n) then flags = bit32.bor(flags, G.ImGuiTreeNodeFlags.Selected) end + ImGui.SetNextItemSelectionUserData(n) + ImGui.TreeNodeEx("Node " .. n, flags) + rects["T" .. n] = rect() + end + ms_io = ImGui.EndMultiSelect() + sel_tree:ApplyRequests(ms_io) + ImGui.End() +end + +game:GetService("RunService").Stepped:Connect(function() + f += 1 + G.ImGui_ImplRoblox.NewFrame() + local s = steps[f] + if s and s.input then s.input() end + ImGui.NewFrame() + list_window() + clipper_window() + external_window() + checkbox_window() + tree_window() + if show_metrics then + ImGui.SetNextWindowPos(ImVec2(620, 10)); ImGui.SetNextWindowSize(ImVec2(400, 600)) + ImGui.ShowMetricsWindow() + local ctx = ImGui.GetCurrentContext() + ImGui.Begin("DebugNodes") + ImGui.DebugNodeTypingSelectState(ctx.TypingSelectState) + for n = 0, ctx.MultiSelectStorage:GetMapSize() - 1 do + local st = ctx.MultiSelectStorage:GetByIndex(n) + if st then ImGui.SetNextItemOpen(true); ImGui.DebugNodeMultiSelectState(st) end + end + ImGui.End() + end + ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData()) + if s and s.check then s.check() end +end) +print(("t_multiselect: %d steps"):format(#steps))