--- Dear ImGui WIP -- (Core Code) --- Flags: -- IMGUI_DISABLE_OBSOLETE_FUNCTIONS = true -- This flag is not implemented -- NOTE: you must implement ImStd.ImFileOpen if you are not in GMod IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS = true -- Don't implement ImStd.ImFileOpen/ImStd.ImFileClose/ImStd.ImFileRead/ImFileWrite so you can implement them yourself IMGUI_DISABLE_DEBUG_TOOLS = false -- Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty --- @type ImGuiContext? local GImGui = nil ImGui = {} ImStd = {} -- Contains functions that originally don't belong to cpp namespaces -- IM_INCLUDE(name) is provided by the bundle (see tools/bundle.py): runs the bundled file once, returns its result. function ImGui._GetTypeFunc() return type end local function LUA_TableConstructorWrapper(T, size, userdata) local p = {} for i = 1, size do p[i] = T() end return p end local GImAllocatorAllocFunc = LUA_TableConstructorWrapper local GImAllocatorFreeFunc = function(p0, p1, p2) end local GImAllocatorUserData = nil --- @param constructor function --- @param size int function ImGui.MemAlloc(constructor, size) local p = GImAllocatorAllocFunc(constructor, size, GImAllocatorUserData) local ctx = GImGui if ctx then ImGui.DebugAllocHook(ctx.DebugAllocInfo, ctx.FrameCount, p, size) end return p end -- This does not set `owner[field]` to nil! --- @param owner table --- @param field string|int function ImGui.MemFree(owner, field) if owner ~= nil then local ctx = GImGui if ctx then ImGui.DebugAllocHook(ctx.DebugAllocInfo, ctx.FrameCount, owner, -1) end end return GImAllocatorFreeFunc(owner, field, GImAllocatorUserData) end --- @param info ImGuiDebugAllocInfo --- @param frame_count int --- @param ptr table --- @param size size_t function ImGui.DebugAllocHook(info, frame_count, ptr, size) local entry = info.LastEntriesBuf[info.LastEntriesIdx] if entry.FrameCount ~= frame_count then info.LastEntriesIdx = info.LastEntriesIdx % (#info.LastEntriesBuf) + 1 entry = info.LastEntriesBuf[info.LastEntriesIdx] entry.FrameCount = frame_count entry.AllocCount = 0 entry.FreeCount = 0 end if size ~= -1 then -- print(string.format("[%05d] MemAlloc(%d) -> %p", frame_count, size, ptr)) entry.AllocCount = entry.AllocCount + 1 info.TotalAllocCount = info.TotalAllocCount + 1 else -- print(string.format("[%05d] MemFree(%p)", frame_count, ptr)) entry.FreeCount = entry.FreeCount + 1 info.TotalFreeCount = info.TotalFreeCount + 1 end end IM_INCLUDE"imgui_h.lua" IM_INCLUDE"imgui_internal.lua" IM_INCLUDE"imgui_draw.lua" IM_INCLUDE"imgui_widgets.lua" IM_INCLUDE"imgui_tables.lua" do --[[ [SECTION] imgui_dragdrop (was imgui_dragdrop.lua) ]] --- Dear ImGui -- Drag and drop: 1:1 port of imgui.cpp [SECTION] DRAG AND DROP (docking branch) -- Payload `Data` is any Lua value (no copy is made). `data_size` is optional and only kept for API parity. function ImGui.IsDragDropActive() return ImGui.GetCurrentContext().DragDropActive end function ImGui.ClearDragDrop() local g = ImGui.GetCurrentContext() g.DragDropActive = false ImGuiPayload_Clear(g.DragDropPayload) g.DragDropAcceptFlagsCurr = ImGuiDragDropFlags.None g.DragDropAcceptIdCurr = 0 g.DragDropAcceptIdPrev = 0 g.DragDropAcceptIdCurrRectSurface = FLT_MAX g.DragDropAcceptFrameCount = -1 end function ImGui.BeginDragDropSource(flags) if flags == nil then flags = 0 end local g = ImGui.GetCurrentContext() local window = g.CurrentWindow local mouse_button = ImGuiMouseButton.Left local source_drag_active = false local source_id = 0 local source_parent_id = 0 if bit32.band(flags, ImGuiDragDropFlags.SourceExtern) == 0 then source_id = g.LastItemData.ID if source_id ~= 0 then if g.ActiveId ~= source_id then return false end if g.ActiveIdMouseButton ~= -1 then mouse_button = g.ActiveIdMouseButton end if g.IO.MouseDown[mouse_button] == false or window.SkipItems then return false end g.ActiveIdAllowOverlap = false else if g.IO.MouseDown[mouse_button] == false or window.SkipItems then return false end if bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredRect) == 0 and (g.ActiveId == 0 or g.ActiveIdWindow ~= window) then return false end if bit32.band(flags, ImGuiDragDropFlags.SourceAllowNullID) == 0 then IM_ASSERT(false, "Use ImGuiDragDropFlags.SourceAllowNullID for items without an ID") return false end source_id = window:GetIDFromRectangle(g.LastItemData.Rect) g.LastItemData.ID = source_id ImGui.KeepAliveID(source_id) local is_hovered = ImGui.ItemHoverable(g.LastItemData.Rect, source_id, g.LastItemData.ItemFlags) if is_hovered and g.IO.MouseClicked[mouse_button] then ImGui.SetActiveID(source_id, window) ImGui.FocusWindow(window) end if g.ActiveId == source_id then g.ActiveIdAllowOverlap = is_hovered end end if g.ActiveId ~= source_id then return false end source_parent_id = window.IDStack.Data[window.IDStack.Size] source_drag_active = ImGui.IsMouseDragging(mouse_button) ImGui.SetActiveIdUsingAllKeyboardKeys() else window = nil source_id = ImHashStr("#SourceExtern") source_drag_active = true mouse_button = g.IO.MouseDown[0] and 0 or -1 ImGui.KeepAliveID(source_id) ImGui.SetActiveID(source_id, nil) end IM_ASSERT(g.DragDropWithinTarget == false) if not source_drag_active then return false end if not g.DragDropActive then IM_ASSERT(source_id ~= 0) ImGui.ClearDragDrop() local payload = g.DragDropPayload payload.SourceId = source_id payload.SourceParentId = source_parent_id g.DragDropActive = true g.DragDropSourceFlags = flags g.DragDropMouseButton = mouse_button if payload.SourceId == g.ActiveId then g.ActiveIdNoClearOnFocusLoss = true end end g.DragDropSourceFrameCount = g.FrameCount g.DragDropWithinSource = true if bit32.band(flags, ImGuiDragDropFlags.SourceNoPreviewTooltip) == 0 then local ret if g.DragDropAcceptIdPrev ~= 0 and bit32.band(g.DragDropAcceptFlagsPrev, ImGuiDragDropFlags.AcceptNoPreviewTooltip) ~= 0 then ret = ImGui.BeginTooltipHidden() else ret = ImGui.BeginTooltip() end IM_ASSERT(ret) end if bit32.band(flags, ImGuiDragDropFlags.SourceNoDisableHover) == 0 and bit32.band(flags, ImGuiDragDropFlags.SourceExtern) == 0 then g.LastItemData.StatusFlags = bit32.band(g.LastItemData.StatusFlags, bit32.bnot(ImGuiItemStatusFlags.HoveredRect)) end return true end function ImGui.EndDragDropSource() local g = ImGui.GetCurrentContext() IM_ASSERT(g.DragDropActive) IM_ASSERT(g.DragDropWithinSource, "Not after a BeginDragDropSource()?") if bit32.band(g.DragDropSourceFlags, ImGuiDragDropFlags.SourceNoPreviewTooltip) == 0 then ImGui.EndTooltip() end if g.DragDropPayload.DataFrameCount == -1 then ImGui.ClearDragDrop() end g.DragDropWithinSource = false end --- @param data any # any Lua value --- @return bool accepted function ImGui.SetDragDropPayload(type, data, data_size, cond) local g = ImGui.GetCurrentContext() local payload = g.DragDropPayload if cond == nil or cond == 0 then cond = ImGuiCond.Always end IM_ASSERT(type ~= nil) IM_ASSERT(#type < 32, "Payload type can be at most 32 characters long") IM_ASSERT(cond == ImGuiCond.Always or cond == ImGuiCond.Once) IM_ASSERT(payload.SourceId ~= 0) if cond == ImGuiCond.Always or payload.DataFrameCount == -1 then payload.DataType = type payload.Data = data payload.DataSize = data_size or ((data ~= nil) and 1 or 0) end payload.DataFrameCount = g.FrameCount return (g.DragDropAcceptFrameCount == g.FrameCount) or (g.DragDropAcceptFrameCount == g.FrameCount - 1) end function ImGui.BeginDragDropTargetCustom(bb, id) local g = ImGui.GetCurrentContext() if not g.DragDropActive then return false end local window = g.CurrentWindow local hovered_window = g.HoveredWindowUnderMovingWindow if hovered_window == nil or window.RootWindowDockTree ~= hovered_window.RootWindowDockTree then return false end IM_ASSERT(id ~= 0) if not ImGui.IsMouseHoveringRect(bb.Min, bb.Max) or (id == g.DragDropPayload.SourceId) then return false end if window.SkipItems then return false end IM_ASSERT(g.DragDropWithinTarget == false and g.DragDropWithinSource == false) g.DragDropTargetRect = ImRect(bb.Min, bb.Max) g.DragDropTargetClipRect = ImRect(window.ClipRect.Min, window.ClipRect.Max) g.DragDropTargetId = id g.DragDropTargetFullViewport = 0 g.DragDropWithinTarget = true return true end function ImGui.BeginDragDropTargetViewport(viewport, p_bb) local g = ImGui.GetCurrentContext() if not g.DragDropActive then return false end local bb = p_bb or viewport:GetWorkRect() local id = viewport.ID if g.MouseViewport ~= viewport or not ImGui.IsMouseHoveringRect(bb.Min, bb.Max, false) or (id == g.DragDropPayload.SourceId) then return false end IM_ASSERT(g.DragDropWithinTarget == false and g.DragDropWithinSource == false) g.DragDropTargetRect = ImRect(bb.Min, bb.Max) g.DragDropTargetClipRect = ImRect(bb.Min, bb.Max) g.DragDropTargetId = id g.DragDropTargetFullViewport = id g.DragDropWithinTarget = true return true end function ImGui.BeginDragDropTarget() local g = ImGui.GetCurrentContext() if not g.DragDropActive then return false end local window = g.CurrentWindow if bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredRect) == 0 then return false end local hovered_window = g.HoveredWindowUnderMovingWindow if hovered_window == nil or window.RootWindowDockTree ~= hovered_window.RootWindowDockTree or window.SkipItems then return false end local display_rect = (bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HasDisplayRect) ~= 0) and g.LastItemData.DisplayRect or g.LastItemData.Rect local id = g.LastItemData.ID if id == 0 then id = window:GetIDFromRectangle(display_rect) ImGui.KeepAliveID(id) end if g.DragDropPayload.SourceId == id then return false end IM_ASSERT(g.DragDropWithinTarget == false and g.DragDropWithinSource == false) g.DragDropTargetRect = ImRect(display_rect.Min, display_rect.Max) local clip = (bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HasClipRect) ~= 0) and g.LastItemData.ClipRect or window.ClipRect g.DragDropTargetClipRect = ImRect(clip.Min, clip.Max) g.DragDropTargetId = id g.DragDropWithinTarget = true return true end function ImGui.IsDragDropPayloadBeingAccepted() local g = ImGui.GetCurrentContext() return g.DragDropActive and g.DragDropAcceptIdPrev ~= 0 end --- @return ImGuiPayload? function ImGui.AcceptDragDropPayload(type, flags) if flags == nil then flags = 0 end local g = ImGui.GetCurrentContext() local payload = g.DragDropPayload IM_ASSERT(g.DragDropActive) IM_ASSERT(payload.DataFrameCount ~= -1) if type ~= nil and not ImGuiPayload_IsDataType(payload, type) then return nil end local was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId) local r = g.DragDropTargetRect local r_surface = r:GetWidth() * r:GetHeight() if r_surface > g.DragDropAcceptIdCurrRectSurface then return nil end g.DragDropAcceptFlagsCurr = flags g.DragDropAcceptIdCurr = g.DragDropTargetId g.DragDropAcceptIdCurrRectSurface = r_surface payload.Preview = was_accepted_previously flags = bit32.bor(flags, bit32.band(g.DragDropSourceFlags, ImGuiDragDropFlags.AcceptNoDrawDefaultRect)) local draw_target_rect = payload.Preview and bit32.band(flags, ImGuiDragDropFlags.AcceptNoDrawDefaultRect) == 0 if draw_target_rect and g.DragDropTargetFullViewport ~= 0 then ImGui.RenderDragDropTargetRectForViewport(g.DragDropTargetFullViewport, g.DragDropTargetRect) elseif draw_target_rect then ImGui.RenderDragDropTargetRectForItem(r) end g.DragDropAcceptFrameCount = g.FrameCount if bit32.band(g.DragDropSourceFlags, ImGuiDragDropFlags.SourceExtern) ~= 0 and g.DragDropMouseButton == -1 then payload.Delivery = was_accepted_previously and (g.DragDropSourceFrameCount < g.FrameCount) else payload.Delivery = was_accepted_previously and not ImGui.IsMouseDown(g.DragDropMouseButton) end if not payload.Delivery and bit32.band(flags, ImGuiDragDropFlags.AcceptBeforeDelivery) == 0 then return nil end return payload end function ImGui.RenderDragDropTargetRectForItem(bb) local g = ImGui.GetCurrentContext() local window = g.CurrentWindow local bb_display = ImRect(bb.Min, bb.Max) bb_display:ClipWith(g.DragDropTargetClipRect) bb_display:Expand(g.Style.DragDropTargetPadding) local push_clip_rect = not window.ClipRect:Contains(bb_display) if push_clip_rect then window.DrawList:PushClipRectFullScreen() end ImGui.RenderDragDropTargetRectEx(window.DrawList, bb_display, g.Style.DragDropTargetRounding) if push_clip_rect then window.DrawList:PopClipRect() end end function ImGui.RenderDragDropTargetRectForViewport(viewport_id, bb) local g = ImGui.GetCurrentContext() local viewport = ImGui.FindViewportByID(viewport_id) IM_ASSERT(viewport ~= nil) local bb_padded = ImRect(bb.Min, bb.Max) bb_padded:Expand(-g.Style.DragDropTargetPadding) ImGui.RenderDragDropTargetRectEx(ImGui.GetForegroundDrawList(viewport), bb_padded, g.Style.DragDropTargetRounding) end function ImGui.RenderDragDropTargetRectEx(draw_list, bb, rounding) local g = ImGui.GetCurrentContext() draw_list:AddRectFilled(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.DragDropTargetBg), rounding, 0) draw_list:AddRect(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.DragDropTarget), rounding, g.Style.DragDropTargetBorderSize) end --- @return ImGuiPayload? function ImGui.GetDragDropPayload() local g = ImGui.GetCurrentContext() return (g.DragDropActive and g.DragDropPayload.DataFrameCount ~= -1) and g.DragDropPayload or nil end function ImGui.EndDragDropTarget() local g = ImGui.GetCurrentContext() IM_ASSERT(g.DragDropActive) IM_ASSERT(g.DragDropWithinTarget) g.DragDropWithinTarget = false if g.DragDropPayload.Delivery then ImGui.ClearDragDrop() end end -- Payload methods (C++ ImGuiPayload members): payload:IsDataType(t) etc. also work through these globals function ImGui.PayloadIsDataType(payload, t) return ImGuiPayload_IsDataType(payload, t) end end --[[ [SECTION] imgui_dragdrop (was imgui_dragdrop.lua) ]] do --[[ [SECTION] imgui_docking (was imgui_docking.lua) ]] --- Dear ImGui -- Docking: 1:1 port of imgui.cpp [SECTION] DOCKING (docking branch, 1.93 WIP) -- -- Port notes: -- - Main viewport only (no multi-viewports on Roblox): viewport creation/merging code paths are kept where cheap, but -- windows always live in the main viewport. -- - ImGuiDockContext.Nodes (ImGuiStorage in C++) is a Lua table ID -> node. Iterations that upstream does over the -- sorted storage are done over a sorted snapshot of the IDs (DockContextNodesSorted) for deterministic order. -- - node.ChildNodes is a table indexed [0] and [1] like upstream. -- - ImGuiDockPreviewData.DropRectsDraw is indexed [dir + 1] with dir in -1..3 (so keys 0..4), like upstream. local MT = ImGui.GetMetatables() IMGUI_PAYLOAD_TYPE_WINDOW = "_IMWINDOW" local DOCKING_HOST_DRAW_CHANNEL_BG = 0 -- Dock host: background fill local DOCKING_HOST_DRAW_CHANNEL_FG = 1 -- Dock host: decorations and contents local DOCKING_SPLITTER_SIZE = 2.0 --------------------------------------------------------------------------------------- -- Docking: flags & enums --------------------------------------------------------------------------------------- ImGuiDockNodeFlags = { None = 0, KeepAliveOnly = bit32.lshift(1, 0), NoDockingOverCentralNode = bit32.lshift(1, 2), PassthruCentralNode = bit32.lshift(1, 3), NoDockingSplit = bit32.lshift(1, 4), NoResize = bit32.lshift(1, 5), AutoHideTabBar = bit32.lshift(1, 6), NoUndocking = bit32.lshift(1, 7), -- [Internal] DockSpace = bit32.lshift(1, 10), CentralNode = bit32.lshift(1, 11), NoTabBar = bit32.lshift(1, 12), HiddenTabBar = bit32.lshift(1, 13), NoWindowMenuButton = bit32.lshift(1, 14), NoCloseButton = bit32.lshift(1, 15), NoResizeX = bit32.lshift(1, 16), NoResizeY = bit32.lshift(1, 17), DockedWindowsInFocusRoute = bit32.lshift(1, 18), NoDockingSplitOther = bit32.lshift(1, 19), NoDockingOverMe = bit32.lshift(1, 20), NoDockingOverOther = bit32.lshift(1, 21), NoDockingOverEmpty = bit32.lshift(1, 22), } do local F = ImGuiDockNodeFlags F.NoSplit = F.NoDockingSplit F.NoDockingInCentralNode = F.NoDockingOverCentralNode F.NoDocking = bit32.bor(F.NoDockingOverMe, F.NoDockingOverOther, F.NoDockingOverEmpty, F.NoDockingSplit, F.NoDockingSplitOther) F.SharedFlagsInheritMask_ = 0xFFFFFFFF F.NoResizeFlagsMask_ = bit32.bor(F.NoResize, F.NoResizeX, F.NoResizeY) F.LocalFlagsTransferMask_ = bit32.bor(F.NoDockingSplit, F.NoResizeFlagsMask_, F.AutoHideTabBar, F.CentralNode, F.NoTabBar, F.HiddenTabBar, F.NoWindowMenuButton, F.NoCloseButton) F.SavedFlagsMask_ = bit32.bor(F.NoResizeFlagsMask_, F.DockSpace, F.CentralNode, F.NoTabBar, F.HiddenTabBar, F.NoWindowMenuButton, F.NoCloseButton) end ImGuiDataAuthority = { Auto = 0, DockNode = 1, Window = 2 } ImGuiDockNodeState = { Unknown = 0, HostWindowHiddenBecauseSingleWindow = 1, HostWindowHiddenBecauseWindowsAreResizing = 2, HostWindowVisible = 3, } ImGuiWindowDockStyleCol = { Text = 0, TabHovered = 1, TabFocused = 2, TabSelected = 3, TabSelectedOverline = 4, TabDimmed = 5, TabDimmedSelected = 6, TabDimmedSelectedOverline = 7, UnsavedMarker = 8, COUNT = 9, } local ImGuiDockRequestType = { None = 0, Dock = 1, Undock = 2, Split = 3 } --------------------------------------------------------------------------------------- -- Docking: Internal Types --------------------------------------------------------------------------------------- local function ImGuiDockRequest() return { Type = ImGuiDockRequestType.None, DockTargetWindow = nil, DockTargetNode = nil, DockPayload = nil, DockSplitDir = ImGuiDir.None, DockSplitRatio = 0.5, DockSplitOuter = false, UndockTargetWindow = nil, UndockTargetNode = nil, } end local function ImGuiDockNodeSettings() return { ID = 0, ParentNodeId = 0, ParentWindowId = 0, SelectedTabId = 0, SplitAxis = ImGuiAxis.None, Depth = 0, Flags = 0, Pos = ImVec2(0, 0), Size = ImVec2(0, 0), SizeRef = ImVec2(0, 0), } end function ImGuiDockContext() return { Nodes = {}, Requests = ImVector(), NodesSettings = ImVector(), WantFullRebuild = false } end MT.ImGuiDockNode = {} MT.ImGuiDockNode.__index = MT.ImGuiDockNode local DN = MT.ImGuiDockNode function ImGuiDockNode(id) return setmetatable({ ID = id, SharedFlags = 0, LocalFlags = 0, LocalFlagsInWindows = 0, MergedFlags = 0, State = ImGuiDockNodeState.Unknown, ParentNode = nil, ChildNodes = { [0] = nil, [1] = nil }, Windows = ImVector(), TabBar = nil, Pos = ImVec2(0, 0), Size = ImVec2(0, 0), SizeRef = ImVec2(0, 0), SplitAxis = ImGuiAxis.None, LastBgColor = IM_COL32_WHITE, WindowClass = ImGuiWindowClass(), HostWindow = nil, VisibleWindow = nil, CentralNode = nil, OnlyNodeWithWindows = nil, CountNodeWithWindows = 0, LastFrameAlive = -1, LastFrameActive = -1, LastFrameFocused = -1, LastFocusedNodeId = 0, SelectedTabId = 0, WantCloseTabId = 0, RefViewportId = 0, AuthorityForPos = ImGuiDataAuthority.DockNode, AuthorityForSize = ImGuiDataAuthority.DockNode, AuthorityForViewport = ImGuiDataAuthority.Auto, IsVisible = true, IsFocused = false, IsBgDrawnThisFrame = false, HasCloseButton = false, HasWindowMenuButton = false, HasCentralNodeChild = false, WantCloseAll = false, WantLockSizeOnce = false, WantMouseMove = false, WantHiddenTabBarUpdate = false, WantHiddenTabBarToggle = false, }, DN) end function DN:IsRootNode() return self.ParentNode == nil end function DN:IsDockSpace() return bit32.band(self.MergedFlags, ImGuiDockNodeFlags.DockSpace) ~= 0 end function DN:IsFloatingNode() return self.ParentNode == nil and bit32.band(self.MergedFlags, ImGuiDockNodeFlags.DockSpace) == 0 end function DN:IsCentralNode() return bit32.band(self.MergedFlags, ImGuiDockNodeFlags.CentralNode) ~= 0 end function DN:IsHiddenTabBar() return bit32.band(self.MergedFlags, ImGuiDockNodeFlags.HiddenTabBar) ~= 0 end function DN:IsNoTabBar() return bit32.band(self.MergedFlags, ImGuiDockNodeFlags.NoTabBar) ~= 0 end function DN:IsSplitNode() return self.ChildNodes[0] ~= nil end function DN:IsLeafNode() return self.ChildNodes[0] == nil end function DN:IsEmpty() return self.ChildNodes[0] == nil and self.Windows.Size == 0 end function DN:Rect() return ImRect(self.Pos.x, self.Pos.y, self.Pos.x + self.Size.x, self.Pos.y + self.Size.y) end function DN:SetLocalFlags(flags) self.LocalFlags = flags; self:UpdateMergedFlags() end function DN:UpdateMergedFlags() self.MergedFlags = bit32.bor(self.SharedFlags, self.LocalFlags, self.LocalFlagsInWindows) end local function ImGuiDockPreviewData() local d = { FutureNode = ImGuiDockNode(0), IsDropAllowed = false, IsCenterAvailable = false, IsSidesAvailable = false, IsSplitDirExplicit = false, SplitNode = nil, SplitDir = ImGuiDir.None, SplitRatio = 0.0, DropRectsDraw = {}, } for n = 0, 4 do d.DropRectsDraw[n] = ImRect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX) end return d end local function DockNodeGetHostWindowTitle(node) return string.format("##DockNode_%02X", node.ID) end local function IsRectInverted(r) return r.Min.x > r.Max.x or r.Min.y > r.Max.y end --- Sorted snapshot of node IDs (upstream iterates ImGuiStorage which is sorted by key) local function DockContextNodesSorted(ctx) local ids = {} for id in pairs(ctx.DockContext.Nodes) do ids[#ids + 1] = id end table.sort(ids) return ids end ImGui.DockContextNodesSorted = DockContextNodesSorted --------------------------------------------------------------------------------------- -- Docking: ImGuiDockContext --------------------------------------------------------------------------------------- local DockSettingsHandler_ClearAll, DockSettingsHandler_ApplyAll, DockSettingsHandler_ReadOpen, DockSettingsHandler_ReadLine, DockSettingsHandler_WriteAll local DockSettingsRenameNodeReferences, DockSettingsRemoveNodeReferences, DockSettingsFindNodeSettings function ImGui.DockContextInitialize(ctx) local g = ctx local ini_handler = ImGuiSettingsHandler() ini_handler.TypeName = "Docking" ini_handler.TypeHash = ImHashStr("Docking") ini_handler.ClearAllFn = DockSettingsHandler_ClearAll ini_handler.ReadInitFn = DockSettingsHandler_ClearAll ini_handler.ReadOpenFn = DockSettingsHandler_ReadOpen ini_handler.ReadLineFn = DockSettingsHandler_ReadLine ini_handler.ApplyAllFn = DockSettingsHandler_ApplyAll ini_handler.WriteAllFn = DockSettingsHandler_WriteAll g.SettingsHandlers:push_back(ini_handler) g.DockNodeWindowMenuHandler = ImGui.DockNodeWindowMenuHandler_Default end function ImGui.DockContextShutdown(ctx) for _, id in ipairs(DockContextNodesSorted(ctx)) do local node = ctx.DockContext.Nodes[id] if node then ImGui.DockContextDeleteNode(ctx, node) end end end function ImGui.DockContextClearNodes(ctx, root_id, clear_settings_refs) ImGui.DockBuilderRemoveNodeDockedWindows(root_id, clear_settings_refs) ImGui.DockBuilderRemoveNodeChildNodes(root_id) end function ImGui.DockContextRebuildNodes(ctx) local dc = ctx.DockContext ImGui.SaveIniSettingsToMemory() local root_id = 0 ImGui.DockContextClearNodes(ctx, root_id, false) ImGui.DockContextBuildNodesFromSettings(ctx, dc.NodesSettings.Data, dc.NodesSettings.Size) ImGui.DockContextBuildAddWindowsToNodes(ctx, root_id) end function ImGui.DockContextNewFrameUpdateUndocking(ctx) local g = ctx local dc = ctx.DockContext if bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.DockingEnable) == 0 then if next(dc.Nodes) ~= nil or dc.Requests.Size > 0 then ImGui.DockContextClearNodes(ctx, 0, true) end return end if g.IO.ConfigDockingNoSplit then for _, id in ipairs(DockContextNodesSorted(ctx)) do local node = dc.Nodes[id] if node and node:IsRootNode() and node:IsSplitNode() then ImGui.DockBuilderRemoveNodeChildNodes(node.ID) end end end if dc.WantFullRebuild then ImGui.DockContextRebuildNodes(ctx) dc.WantFullRebuild = false end for _, req in dc.Requests:iter() do if req.Type == ImGuiDockRequestType.Undock and req.UndockTargetWindow then ImGui.DockContextProcessUndockWindow(ctx, req.UndockTargetWindow) elseif req.Type == ImGuiDockRequestType.Undock and req.UndockTargetNode then ImGui.DockContextProcessUndockNode(ctx, req.UndockTargetNode) end end end function ImGui.DockContextNewFrameUpdateDocking(ctx) local g = ctx local dc = ctx.DockContext if bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.DockingEnable) == 0 then return end g.DebugHoveredDockNode = nil local hovered_window = g.HoveredWindowUnderMovingWindow if hovered_window then if hovered_window.DockNodeAsHost then g.DebugHoveredDockNode = ImGui.DockNodeTreeFindVisibleNodeByPos(hovered_window.DockNodeAsHost, g.IO.MousePos) elseif hovered_window.RootWindow.DockNode then g.DebugHoveredDockNode = hovered_window.RootWindow.DockNode end end for _, req in dc.Requests:iter() do if req.Type == ImGuiDockRequestType.Dock then ImGui.DockContextProcessDock(ctx, req) end end dc.Requests:resize(0) for _, id in ipairs(DockContextNodesSorted(ctx)) do local node = dc.Nodes[id] if node and node:IsFloatingNode() then ImGui.DockNodeUpdate(node) end end end function ImGui.DockContextEndFrame(ctx) local g = ctx local dc = g.DockContext for _, id in ipairs(DockContextNodesSorted(ctx)) do local node = dc.Nodes[id] if node and node.LastFrameActive == g.FrameCount and node.IsVisible and node.HostWindow and node:IsLeafNode() and not node.IsBgDrawnThisFrame then local bg_rect = ImRect(node.Pos + ImVec2(0.0, ImGui.GetFrameHeight()), node.Pos + node.Size) local bg_rounding_flags = ImGui.CalcRoundingFlagsForRectInRect(bg_rect, node.HostWindow:Rect(), g.Style.DockingSeparatorSize) node.HostWindow.DrawList:ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG) node.HostWindow.DrawList:AddRectFilled(bg_rect.Min, bg_rect.Max, node.LastBgColor, node.HostWindow.WindowRounding, bg_rounding_flags) end end end function ImGui.DockContextFindNodeByID(ctx, id) return ctx.DockContext.Nodes[id] end function ImGui.DockContextGenNodeID(ctx) local id = 0x0001 while ImGui.DockContextFindNodeByID(ctx, id) ~= nil do id = id + 1 end return id end function ImGui.DockContextAddNode(ctx, id) if id == 0 then id = ImGui.DockContextGenNodeID(ctx) else IM_ASSERT(ImGui.DockContextFindNodeByID(ctx, id) == nil) end local node = ImGuiDockNode(id) ctx.DockContext.Nodes[node.ID] = node return node end function ImGui.DockContextRemoveNode(ctx, node, merge_sibling_into_parent_node) IM_ASSERT(ImGui.DockContextFindNodeByID(ctx, node.ID) == node) IM_ASSERT(node.ChildNodes[0] == nil and node.ChildNodes[1] == nil) IM_ASSERT(node.Windows.Size == 0) if node.HostWindow then node.HostWindow.DockNodeAsHost = nil end local parent_node = node.ParentNode local merge = (merge_sibling_into_parent_node and parent_node ~= nil) if merge then IM_ASSERT(parent_node.ChildNodes[0] == node or parent_node.ChildNodes[1] == node) local sibling_node = (parent_node.ChildNodes[0] == node) and parent_node.ChildNodes[1] or parent_node.ChildNodes[0] ImGui.DockNodeTreeMerge(ctx, parent_node, sibling_node) else if parent_node then for n = 0, 1 do if parent_node.ChildNodes[n] == node then node.ParentNode.ChildNodes[n] = nil end end end ImGui.DockContextDeleteNode(ctx, node) end end function ImGui.DockContextDeleteNode(ctx, node) local dc = ctx.DockContext node.TabBar = nil dc.Nodes[node.ID] = nil end function ImGui.DockContextPruneUnusedSettingsNodes(ctx) local g = ctx local dc = ctx.DockContext IM_ASSERT(g.Windows.Size == 0) local pool = {} local function GetOrAdd(id) local d = pool[id] if not d then d = { CountWindows = 0, CountChildWindows = 0, CountChildNodes = 0, RootId = 0 }; pool[id] = d end return d end for _, settings in dc.NodesSettings:iter() do if pool[settings.ID] ~= nil then settings.ID = 0 else local parent_data = (settings.ParentNodeId ~= 0) and pool[settings.ParentNodeId] or nil GetOrAdd(settings.ID).RootId = parent_data and parent_data.RootId or settings.ID if settings.ParentNodeId ~= 0 then local p = GetOrAdd(settings.ParentNodeId) p.CountChildNodes = p.CountChildNodes + 1 end end end for _, settings in dc.NodesSettings:iter() do if settings.ParentWindowId ~= 0 then local window_settings = ImGui.FindWindowSettingsByID(settings.ParentWindowId) if window_settings and window_settings.DockId ~= 0 then local data = pool[window_settings.DockId] if data then data.CountChildNodes = data.CountChildNodes + 1 end end end end for _, settings in g.SettingsWindows:iter() do local dock_id = settings.DockId if dock_id and dock_id ~= 0 then local data = pool[dock_id] if data then data.CountWindows = data.CountWindows + 1 local data_root = (data.RootId == dock_id) and data or pool[data.RootId] if data_root then data_root.CountChildWindows = data_root.CountChildWindows + 1 end end end end for _, settings in dc.NodesSettings:iter() do local data = pool[settings.ID] if data ~= nil and data.CountWindows <= 1 then local data_root = (settings.ID == data.RootId) and data or pool[data.RootId] local data_parent = (settings.ParentNodeId ~= 0) and pool[settings.ParentNodeId] or nil local remove = false remove = remove or (data.CountWindows == 1 and settings.ParentNodeId == 0 and data.CountChildNodes == 0 and bit32.band(settings.Flags, ImGuiDockNodeFlags.CentralNode) == 0) remove = remove or (data.CountWindows == 0 and settings.ParentNodeId == 0 and data.CountChildNodes == 0) remove = remove or (data_root == nil or data_root.CountChildWindows == 0) if remove then DockSettingsRemoveNodeReferences({ settings.ID }, 1) settings.ID = 0 elseif data_parent and data_parent.CountChildNodes == 1 then DockSettingsRenameNodeReferences(settings.ID, settings.ParentNodeId) settings.ID = 0 end end end end --- @param node_settings_array table # 1-based array function ImGui.DockContextBuildNodesFromSettings(ctx, node_settings_array, node_settings_count) for node_n = 1, node_settings_count do local settings = node_settings_array[node_n] if settings.ID ~= 0 and ImGui.DockContextFindNodeByID(ctx, settings.ID) == nil then local node = ImGui.DockContextAddNode(ctx, settings.ID) node.ParentNode = (settings.ParentNodeId ~= 0) and ImGui.DockContextFindNodeByID(ctx, settings.ParentNodeId) or nil node.Pos = ImVec2(settings.Pos.x, settings.Pos.y) node.Size = ImVec2(settings.Size.x, settings.Size.y) node.SizeRef = ImVec2(settings.SizeRef.x, settings.SizeRef.y) node.AuthorityForPos = ImGuiDataAuthority.DockNode node.AuthorityForSize = ImGuiDataAuthority.DockNode node.AuthorityForViewport = ImGuiDataAuthority.DockNode if node.ParentNode and node.ParentNode.ChildNodes[0] == nil then node.ParentNode.ChildNodes[0] = node elseif node.ParentNode and node.ParentNode.ChildNodes[1] == nil then node.ParentNode.ChildNodes[1] = node end node.SelectedTabId = settings.SelectedTabId node.SplitAxis = settings.SplitAxis node:SetLocalFlags(bit32.band(settings.Flags, ImGuiDockNodeFlags.SavedFlagsMask_)) local root_node = ImGui.DockNodeGetRootNode(node) node.HostWindow = ImGui.FindWindowByName(DockNodeGetHostWindowTitle(root_node)) end end end function ImGui.DockContextBuildAddWindowsToNodes(ctx, root_id) local g = ctx for _, window in g.Windows:iter() do if window.DockId ~= 0 and window.LastFrameActive >= g.FrameCount - 1 and window.DockNode == nil then local node = ImGui.DockContextFindNodeByID(ctx, window.DockId) IM_ASSERT(node ~= nil) if root_id == 0 or ImGui.DockNodeGetRootNode(node).ID == root_id then ImGui.DockNodeAddWindow(node, window, true) end end end end --------------------------------------------------------------------------------------- -- Docking: ImGuiDockContext Docking/Undocking functions --------------------------------------------------------------------------------------- function ImGui.DockContextQueueDock(ctx, target, target_node, payload, split_dir, split_ratio, split_outer) IM_ASSERT(target ~= payload) local req = ImGuiDockRequest() req.Type = ImGuiDockRequestType.Dock req.DockTargetWindow = target req.DockTargetNode = target_node req.DockPayload = payload req.DockSplitDir = split_dir req.DockSplitRatio = split_ratio req.DockSplitOuter = split_outer ctx.DockContext.Requests:push_back(req) end function ImGui.DockContextQueueUndockWindow(ctx, window) local req = ImGuiDockRequest() req.Type = ImGuiDockRequestType.Undock req.UndockTargetWindow = window ctx.DockContext.Requests:push_back(req) end function ImGui.DockContextQueueUndockNode(ctx, node) local req = ImGuiDockRequest() req.Type = ImGuiDockRequestType.Undock req.UndockTargetNode = node ctx.DockContext.Requests:push_back(req) end function ImGui.DockContextQueueNotifyRemovedNode(ctx, node) for _, req in ctx.DockContext.Requests:iter() do if req.DockTargetNode == node then req.Type = ImGuiDockRequestType.None end end end function ImGui.DockContextProcessDock(ctx, req) IM_ASSERT((req.Type == ImGuiDockRequestType.Dock and req.DockPayload ~= nil) or (req.Type == ImGuiDockRequestType.Split and req.DockPayload == nil)) IM_ASSERT(req.DockTargetWindow ~= nil or req.DockTargetNode ~= nil) local g = ctx local payload_window = req.DockPayload local target_window = req.DockTargetWindow local node = req.DockTargetNode local next_selected_id = 0 local payload_node = nil if payload_window then payload_node = payload_window.DockNodeAsHost payload_window.DockNodeAsHost = nil if payload_node and payload_node:IsLeafNode() then next_selected_id = (payload_node.TabBar.NextSelectedTabId ~= 0) and payload_node.TabBar.NextSelectedTabId or payload_node.TabBar.SelectedTabId end if payload_node == nil then next_selected_id = payload_window.TabId end end if node then IM_ASSERT(node.LastFrameAlive <= g.FrameCount) end if node and target_window and node == target_window.DockNodeAsHost then IM_ASSERT(node.Windows.Size > 0 or node:IsSplitNode() or node:IsCentralNode()) end if node == nil then node = ImGui.DockContextAddNode(ctx, 0) node.Pos = ImVec2(target_window.Pos.x, target_window.Pos.y) node.Size = ImVec2(target_window.Size.x, target_window.Size.y) if target_window.DockNodeAsHost == nil then ImGui.DockNodeAddWindow(node, target_window, true) local t0 = node.TabBar.Tabs.Data[1] t0.Flags = bit32.band(t0.Flags, bit32.bnot(ImGuiTabItemFlags.Unsorted)) target_window.DockIsActive = true end end local split_dir = req.DockSplitDir if split_dir ~= ImGuiDir.None then local split_axis = (split_dir == ImGuiDir.Left or split_dir == ImGuiDir.Right) and ImGuiAxis.X or ImGuiAxis.Y local split_inheritor_child_idx = (split_dir == ImGuiDir.Left or split_dir == ImGuiDir.Up) and 1 or 0 local split_ratio = req.DockSplitRatio ImGui.DockNodeTreeSplit(ctx, node, split_axis, split_inheritor_child_idx, split_ratio, payload_node) local new_node = node.ChildNodes[bit32.bxor(split_inheritor_child_idx, 1)] new_node.HostWindow = node.HostWindow node = new_node end node:SetLocalFlags(bit32.band(node.LocalFlags, bit32.bnot(ImGuiDockNodeFlags.HiddenTabBar))) if node ~= payload_node then if node.Windows.Size > 0 and node.TabBar == nil then ImGui.DockNodeAddTabBar(node) for n = 1, node.Windows.Size do ImGui.TabBarAddTab(node.TabBar, ImGuiTabItemFlags.None, node.Windows.Data[n]) end end if payload_node ~= nil then if payload_node:IsSplitNode() then if node.Windows.Size > 0 then IM_ASSERT(payload_node.OnlyNodeWithWindows ~= nil) local visible_node = payload_node.OnlyNodeWithWindows if visible_node.TabBar then IM_ASSERT(visible_node.TabBar.Tabs.Size > 0) end ImGui.DockNodeMoveWindows(node, visible_node) ImGui.DockNodeMoveWindows(visible_node, node) DockSettingsRenameNodeReferences(node.ID, visible_node.ID) end if node:IsCentralNode() then local last_focused_node = ImGui.DockContextFindNodeByID(ctx, payload_node.LastFocusedNodeId) IM_ASSERT(last_focused_node ~= nil) local last_focused_root_node = ImGui.DockNodeGetRootNode(last_focused_node) IM_ASSERT(last_focused_root_node == ImGui.DockNodeGetRootNode(payload_node)) last_focused_node:SetLocalFlags(bit32.bor(last_focused_node.LocalFlags, ImGuiDockNodeFlags.CentralNode)) node:SetLocalFlags(bit32.band(node.LocalFlags, bit32.bnot(ImGuiDockNodeFlags.CentralNode))) last_focused_root_node.CentralNode = last_focused_node end IM_ASSERT(node.Windows.Size == 0) ImGui.DockNodeMoveChildNodes(node, payload_node) else local payload_dock_id = payload_node.ID ImGui.DockNodeMoveWindows(node, payload_node) DockSettingsRenameNodeReferences(payload_dock_id, node.ID) end ImGui.DockContextRemoveNode(ctx, payload_node, true) elseif payload_window then local payload_dock_id = payload_window.DockId node.VisibleWindow = payload_window ImGui.DockNodeAddWindow(node, payload_window, true) if payload_dock_id ~= 0 then DockSettingsRenameNodeReferences(payload_dock_id, node.ID) end end else node.WantHiddenTabBarUpdate = true end local tab_bar = node.TabBar if tab_bar then tab_bar.NextSelectedTabId = next_selected_id end ImGui.MarkIniSettingsDirty() end local function FixLargeWindowsWhenUndocking(size, ref_viewport) if ref_viewport == nil then return size end local max_size = ImTrunc(ref_viewport.WorkSize * 0.90) return ImVec2(ImMin(size.x, max_size.x), ImMin(size.y, max_size.y)) end function ImGui.DockContextProcessUndockWindow(ctx, window, clear_persistent_docking_ref) if clear_persistent_docking_ref == nil then clear_persistent_docking_ref = true end -- C++ default argument if window.DockNode then ImGui.DockNodeRemoveWindow(window.DockNode, window, clear_persistent_docking_ref and 0 or window.DockId) else window.DockId = 0 end window.Collapsed = false window.DockIsActive = false window.DockNodeIsVisible = false window.DockTabIsVisible = false local sz = FixLargeWindowsWhenUndocking(window.SizeFull, window.Viewport) ImVec2_Copy(window.Size, sz) ImVec2_Copy(window.SizeFull, sz) ImGui.MarkIniSettingsDirty() end function ImGui.DockContextProcessUndockNode(ctx, node) IM_ASSERT(node:IsLeafNode()) IM_ASSERT(node.Windows.Size >= 1) if node:IsRootNode() or node:IsCentralNode() then local new_node = ImGui.DockContextAddNode(ctx, 0) new_node.Pos = ImVec2(node.Pos.x, node.Pos.y) new_node.Size = ImVec2(node.Size.x, node.Size.y) new_node.SizeRef = ImVec2(node.SizeRef.x, node.SizeRef.y) ImGui.DockNodeMoveWindows(new_node, node) DockSettingsRenameNodeReferences(node.ID, new_node.ID) node = new_node else IM_ASSERT(node.ParentNode.ChildNodes[0] == node or node.ParentNode.ChildNodes[1] == node) local index_in_parent = (node.ParentNode.ChildNodes[0] == node) and 0 or 1 node.ParentNode.ChildNodes[index_in_parent] = nil ImGui.DockNodeTreeMerge(ctx, node.ParentNode, node.ParentNode.ChildNodes[bit32.bxor(index_in_parent, 1)]) node.ParentNode.AuthorityForViewport = ImGuiDataAuthority.Window node.ParentNode = nil end for _, window in node.Windows:iter() do window.Flags = bit32.band(window.Flags, bit32.bnot(ImGuiWindowFlags.ChildWindow)) if window.ParentWindow then window.ParentWindow.DC.ChildWindows:find_erase(window) end ImGui.UpdateWindowParentAndRootLinks(window, window.Flags, nil) end node.AuthorityForPos = ImGuiDataAuthority.DockNode node.AuthorityForSize = ImGuiDataAuthority.DockNode node.Size = FixLargeWindowsWhenUndocking(node.Size, node.Windows.Data[1].Viewport) node.WantMouseMove = true ImGui.MarkIniSettingsDirty() end --- @return bool ok, ImVec2? out_pos function ImGui.DockContextCalcDropPosForDocking(target, target_node, payload_window, payload_node, split_dir, split_outer) if target ~= nil and target_node == nil then target_node = target.DockNode end if target_node and target_node.ParentNode == nil and target_node:IsCentralNode() and split_dir ~= ImGuiDir.None then split_outer = true end local split_data = ImGuiDockPreviewData() ImGui.DockNodePreviewDockSetup(target, target_node, payload_window, payload_node, split_data, false, split_outer) local r = split_data.DropRectsDraw[split_dir + 1] if IsRectInverted(r) then return false, nil end return true, r:GetCenter() end --------------------------------------------------------------------------------------- -- Docking: ImGuiDockNode --------------------------------------------------------------------------------------- function ImGui.DockNodeGetTabOrder(window) local tab_bar = window.DockNode.TabBar if tab_bar == nil then return -1 end local tab = ImGui.TabBarFindTabByID(tab_bar, window.TabId) return tab and ImGui.TabBarGetTabOrder(tab_bar, tab) or -1 end local function DockNodeHideWindowDuringHostWindowCreation(window) window.Hidden = true window.HiddenFramesCanSkipItems = window.Active and 1 or 2 end function ImGui.DockNodeAddWindow(node, window, add_to_tab_bar) if window.DockNode then IM_ASSERT(window.DockNode.ID ~= node.ID) ImGui.DockNodeRemoveWindow(window.DockNode, window, 0) end IM_ASSERT(window.DockNode == nil or window.DockNodeAsHost == nil) if node.HostWindow == nil and node.Windows.Size == 1 and node.Windows.Data[1].WasActive == false then DockNodeHideWindowDuringHostWindowCreation(node.Windows.Data[1]) end node.Windows:push_back(window) node.WantHiddenTabBarUpdate = true window.DockNode = node window.DockId = node.ID window.DockIsActive = (node.Windows.Size > 1) window.DockTabWantClose = false if node.HostWindow == nil and node:IsFloatingNode() then if node.AuthorityForPos == ImGuiDataAuthority.Auto then node.AuthorityForPos = ImGuiDataAuthority.Window end if node.AuthorityForSize == ImGuiDataAuthority.Auto then node.AuthorityForSize = ImGuiDataAuthority.Window end if node.AuthorityForViewport == ImGuiDataAuthority.Auto then node.AuthorityForViewport = ImGuiDataAuthority.Window end end if add_to_tab_bar then if node.TabBar == nil then ImGui.DockNodeAddTabBar(node) node.TabBar.SelectedTabId = node.SelectedTabId node.TabBar.NextSelectedTabId = node.SelectedTabId for n = 1, node.Windows.Size - 1 do ImGui.TabBarAddTab(node.TabBar, ImGuiTabItemFlags.None, node.Windows.Data[n]) end end ImGui.TabBarAddTab(node.TabBar, ImGuiTabItemFlags.Unsorted, window) end ImGui.DockNodeUpdateVisibleFlag(node) if node.HostWindow then ImGui.UpdateWindowParentAndRootLinks(window, bit32.bor(window.Flags, ImGuiWindowFlags.ChildWindow), node.HostWindow) end end function ImGui.DockNodeRemoveWindow(node, window, save_dock_id) IM_ASSERT(window.DockNode == node) IM_ASSERT(save_dock_id == 0 or save_dock_id == node.ID) window.DockNode = nil window.DockIsActive = false window.DockTabWantClose = false window.DockId = save_dock_id window.Flags = bit32.band(window.Flags, bit32.bnot(ImGuiWindowFlags.ChildWindow)) if window.ParentWindow then window.ParentWindow.DC.ChildWindows:find_erase(window) end ImGui.UpdateWindowParentAndRootLinks(window, window.Flags, nil) if node.HostWindow and node.HostWindow.ViewportOwned then window.Viewport = nil window.ViewportId = 0 window.ViewportOwned = false window.Hidden = true end local idx = node.Windows:find_index(window) IM_ASSERT(idx ~= nil) node.Windows:erase(idx) if node.VisibleWindow == window then node.VisibleWindow = nil end node.WantHiddenTabBarUpdate = true if node.TabBar then ImGui.TabBarRemoveTab(node.TabBar, window.TabId) local tab_count_threshold_for_tab_bar = node:IsCentralNode() and 1 or 2 if node.Windows.Size < tab_count_threshold_for_tab_bar then ImGui.DockNodeRemoveTabBar(node) end end if node.Windows.Size == 0 and not node:IsCentralNode() and not node:IsDockSpace() and window.DockId ~= node.ID then ImGui.DockContextRemoveNode(ImGui.GetCurrentContext(), node, true) return end if node.Windows.Size == 1 and not node:IsCentralNode() and node.HostWindow then local remaining_window = node.Windows.Data[1] remaining_window.Collapsed = node.HostWindow.Collapsed end ImGui.DockNodeUpdateVisibleFlag(node) end function ImGui.DockNodeMoveChildNodes(dst_node, src_node) IM_ASSERT(dst_node.Windows.Size == 0) dst_node.ChildNodes[0] = src_node.ChildNodes[0] dst_node.ChildNodes[1] = src_node.ChildNodes[1] if dst_node.ChildNodes[0] then dst_node.ChildNodes[0].ParentNode = dst_node end if dst_node.ChildNodes[1] then dst_node.ChildNodes[1].ParentNode = dst_node end dst_node.SplitAxis = src_node.SplitAxis dst_node.SizeRef = ImVec2(src_node.SizeRef.x, src_node.SizeRef.y) src_node.ChildNodes[0] = nil src_node.ChildNodes[1] = nil end function ImGui.DockNodeMoveWindows(dst_node, src_node) IM_ASSERT(src_node and dst_node and dst_node ~= src_node) local src_tab_bar = src_node.TabBar if src_tab_bar ~= nil then IM_ASSERT(src_node.Windows.Size <= src_node.TabBar.Tabs.Size) end local move_tab_bar = (src_tab_bar ~= nil) and (dst_node.TabBar == nil) if move_tab_bar then dst_node.TabBar = src_node.TabBar src_node.TabBar = nil end -- Snapshot: DockNodeAddWindow() won't touch src_node.Windows since window.DockNode is cleared first local windows = {} for i = 1, src_node.Windows.Size do windows[i] = src_node.Windows.Data[i] end for _, window in ipairs(windows) do window.DockNode = nil window.DockIsActive = false ImGui.DockNodeAddWindow(dst_node, window, not move_tab_bar) end src_node.Windows:clear() if not move_tab_bar and src_node.TabBar then if dst_node.TabBar then dst_node.TabBar.SelectedTabId = src_node.TabBar.SelectedTabId end ImGui.DockNodeRemoveTabBar(src_node) end end function ImGui.DockNodeApplyPosSizeToWindows(node) for _, window in node.Windows:iter() do ImGui.SetWindowPos(window, node.Pos, ImGuiCond.Always) ImGui.SetWindowSize(window, node.Size, ImGuiCond.Always) end end function ImGui.DockNodeHideHostWindow(node) if node.HostWindow then if node.HostWindow.DockNodeAsHost == node then node.HostWindow.DockNodeAsHost = nil end node.HostWindow = nil end if node.Windows.Size == 1 then node.VisibleWindow = node.Windows.Data[1] node.Windows.Data[1].DockIsActive = false end if node.TabBar then ImGui.DockNodeRemoveTabBar(node) end end local function DockNodeFindInfo(node, info) if node.Windows.Size > 0 then if info.FirstNodeWithWindows == nil then info.FirstNodeWithWindows = node end info.CountNodesWithWindows = info.CountNodesWithWindows + 1 end if node:IsCentralNode() then IM_ASSERT(info.CentralNode == nil) IM_ASSERT(node:IsLeafNode(), "If you get this assert: please submit .ini file + repro of actions leading to this.") info.CentralNode = node end if info.CountNodesWithWindows > 1 and info.CentralNode ~= nil then return end if node.ChildNodes[0] then DockNodeFindInfo(node.ChildNodes[0], info) end if node.ChildNodes[1] then DockNodeFindInfo(node.ChildNodes[1], info) end end function ImGui.DockNodeFindWindowByID(node, id) IM_ASSERT(id ~= 0) for _, window in node.Windows:iter() do if window.ID == id then return window end end return nil end function ImGui.DockNodeUpdateFlagsAndCollapse(node) local g = ImGui.GetCurrentContext() IM_ASSERT(node.ParentNode == nil or node.ParentNode.ChildNodes[0] == node or node.ParentNode.ChildNodes[1] == node) if node.ParentNode then node.SharedFlags = bit32.band(node.ParentNode.SharedFlags, ImGuiDockNodeFlags.SharedFlagsInheritMask_) end node.HasCentralNodeChild = false if node.ChildNodes[0] then ImGui.DockNodeUpdateFlagsAndCollapse(node.ChildNodes[0]) end if node.ChildNodes[1] then ImGui.DockNodeUpdateFlagsAndCollapse(node.ChildNodes[1]) end node.LocalFlagsInWindows = ImGuiDockNodeFlags.None local window_n = 1 while window_n <= node.Windows.Size do local window = node.Windows.Data[window_n] IM_ASSERT(window.DockNode == node) local node_was_active = (node.LastFrameActive + 1 == g.FrameCount) local remove = false remove = remove or (node_was_active and (window.WasActive == false)) remove = remove or (node_was_active and (node.WantCloseAll or node.WantCloseTabId == window.TabId) and window.HasCloseButton and bit32.band(window.Flags, ImGuiWindowFlags.UnsavedDocument) == 0) remove = remove or window.DockTabWantClose if remove then window.DockTabWantClose = false if node.Windows.Size == 1 and not node:IsCentralNode() then ImGui.DockNodeHideHostWindow(node) node.State = ImGuiDockNodeState.HostWindowHiddenBecauseSingleWindow ImGui.DockNodeRemoveWindow(node, window, node.ID) return end ImGui.DockNodeRemoveWindow(node, window, node.ID) else node.LocalFlagsInWindows = bit32.bor(node.LocalFlagsInWindows, window.WindowClass.DockNodeFlagsOverrideSet) window_n = window_n + 1 end end node:UpdateMergedFlags() local node_flags = node.MergedFlags if node.WantHiddenTabBarUpdate and node.Windows.Size == 1 and bit32.band(node_flags, ImGuiDockNodeFlags.AutoHideTabBar) ~= 0 and not node:IsHiddenTabBar() then node.WantHiddenTabBarToggle = true end node.WantHiddenTabBarUpdate = false if node.WantHiddenTabBarToggle and node.VisibleWindow and bit32.band(node.VisibleWindow.WindowClass.DockNodeFlagsOverrideSet, ImGuiDockNodeFlags.HiddenTabBar) ~= 0 then node.WantHiddenTabBarToggle = false end local prev_local_flags = node.LocalFlags if node.Windows.Size > 1 then node:SetLocalFlags(bit32.band(node.LocalFlags, bit32.bnot(ImGuiDockNodeFlags.HiddenTabBar))) elseif node.WantHiddenTabBarToggle then node:SetLocalFlags(bit32.bxor(node.LocalFlags, ImGuiDockNodeFlags.HiddenTabBar)) end if bit32.band(bit32.bxor(node.LocalFlags, prev_local_flags), ImGuiDockNodeFlags.SavedFlagsMask_) ~= 0 then ImGui.MarkIniSettingsDirty() end node.WantHiddenTabBarToggle = false ImGui.DockNodeUpdateVisibleFlag(node) end function ImGui.DockNodeUpdateHasCentralNodeChild(node) node.HasCentralNodeChild = false if node.ChildNodes[0] then ImGui.DockNodeUpdateHasCentralNodeChild(node.ChildNodes[0]) end if node.ChildNodes[1] then ImGui.DockNodeUpdateHasCentralNodeChild(node.ChildNodes[1]) end if node:IsRootNode() then local mark_node = node.CentralNode while mark_node do mark_node.HasCentralNodeChild = true mark_node = mark_node.ParentNode end end end function ImGui.DockNodeUpdateVisibleFlag(node) local is_visible = (node.ParentNode == nil) and node:IsDockSpace() or node:IsCentralNode() is_visible = is_visible or (node.Windows.Size > 0) is_visible = is_visible or (node.ChildNodes[0] ~= nil and node.ChildNodes[0].IsVisible) is_visible = is_visible or (node.ChildNodes[1] ~= nil and node.ChildNodes[1].IsVisible) node.IsVisible = is_visible and true or false end function ImGui.DockNodeStartMouseMovingWindow(node, window) local g = ImGui.GetCurrentContext() IM_ASSERT(node.WantMouseMove == true) ImGui.StartMouseMovingWindow(window) ImVec2_Copy(g.ActiveIdClickOffset, g.IO.MouseClickedPos[0] - node.Pos) g.MovingWindow = window node.WantMouseMove = false end function ImGui.DockNodeUpdateForRootNode(node) ImGui.DockNodeUpdateFlagsAndCollapse(node) local info = { CentralNode = nil, FirstNodeWithWindows = nil, CountNodesWithWindows = 0 } DockNodeFindInfo(node, info) node.CentralNode = info.CentralNode node.OnlyNodeWithWindows = (info.CountNodesWithWindows == 1) and info.FirstNodeWithWindows or nil node.CountNodeWithWindows = info.CountNodesWithWindows if node.LastFocusedNodeId == 0 and info.FirstNodeWithWindows ~= nil then node.LastFocusedNodeId = info.FirstNodeWithWindows.ID end local first_node_with_windows = info.FirstNodeWithWindows if first_node_with_windows then node.WindowClass = first_node_with_windows.Windows.Data[1].WindowClass for n = 2, first_node_with_windows.Windows.Size do if first_node_with_windows.Windows.Data[n].WindowClass.DockingAllowUnclassed == false then node.WindowClass = first_node_with_windows.Windows.Data[n].WindowClass break end end end local mark_node = node.CentralNode while mark_node do mark_node.HasCentralNodeChild = true mark_node = mark_node.ParentNode end end local function DockNodeSetupHostWindow(node, host_window) if node.HostWindow and node.HostWindow ~= host_window and node.HostWindow.DockNodeAsHost == node then node.HostWindow.DockNodeAsHost = nil end host_window.DockNodeAsHost = node node.HostWindow = host_window end function ImGui.DockNodeUpdate(node) local g = ImGui.GetCurrentContext() IM_ASSERT(node.LastFrameActive ~= g.FrameCount) node.LastFrameAlive = g.FrameCount node.IsBgDrawnThisFrame = false node.CentralNode = nil node.OnlyNodeWithWindows = nil if node:IsRootNode() then ImGui.DockNodeUpdateForRootNode(node) end if node.TabBar and node:IsNoTabBar() then ImGui.DockNodeRemoveTabBar(node) end local want_to_hide_host_window = false if node:IsFloatingNode() then if node.Windows.Size <= 1 and node:IsLeafNode() then if not g.IO.ConfigDockingAlwaysTabBar and (node.Windows.Size == 0 or not node.Windows.Data[1].WindowClass.DockingAlwaysTabBar) then want_to_hide_host_window = true end end if node.CountNodeWithWindows == 0 then want_to_hide_host_window = true end end if want_to_hide_host_window then if node.Windows.Size == 1 then local single_window = node.Windows.Data[1] node.Pos = ImVec2(single_window.Pos.x, single_window.Pos.y) node.Size = ImVec2(single_window.SizeFull.x, single_window.SizeFull.y) node.AuthorityForPos = ImGuiDataAuthority.Window node.AuthorityForSize = ImGuiDataAuthority.Window node.AuthorityForViewport = ImGuiDataAuthority.Window if node.HostWindow and g.NavWindow == node.HostWindow then ImGui.FocusWindow(single_window) end if node.HostWindow then single_window.Viewport = node.HostWindow.Viewport single_window.ViewportId = node.HostWindow.ViewportId end node.RefViewportId = single_window.ViewportId or 0 end ImGui.DockNodeHideHostWindow(node) node.State = ImGuiDockNodeState.HostWindowHiddenBecauseSingleWindow node.WantCloseAll = false node.WantCloseTabId = 0 node.HasCloseButton = false node.HasWindowMenuButton = false node.LastFrameActive = g.FrameCount if node.WantMouseMove and node.Windows.Size == 1 then ImGui.DockNodeStartMouseMovingWindow(node, node.Windows.Data[1]) end return end if node.IsVisible and node.HostWindow == nil and node:IsFloatingNode() and node:IsLeafNode() then IM_ASSERT(node.Windows.Size > 0) local ref_window = nil if node.SelectedTabId ~= 0 then ref_window = ImGui.DockNodeFindWindowByID(node, node.SelectedTabId) end if ref_window == nil then ref_window = node.Windows.Data[1] end if ref_window.AutoFitFramesX > 0 or ref_window.AutoFitFramesY > 0 then node.State = ImGuiDockNodeState.HostWindowHiddenBecauseWindowsAreResizing return end end local node_flags = node.MergedFlags node.HasWindowMenuButton = (node.Windows.Size > 0) and bit32.band(node_flags, ImGuiDockNodeFlags.NoWindowMenuButton) == 0 node.HasCloseButton = false for _, window in node.Windows:iter() do node.HasCloseButton = node.HasCloseButton or window.HasCloseButton window.DockIsActive = (node.Windows.Size > 1) end if bit32.band(node_flags, ImGuiDockNodeFlags.NoCloseButton) ~= 0 or not g.Style.DockingNodeHasCloseButton then node.HasCloseButton = false end local host_window = nil local beginned_into_host_window = false if node:IsDockSpace() then IM_ASSERT(node.HostWindow) host_window = node.HostWindow else if node:IsRootNode() and node.IsVisible then local ref_window = (node.Windows.Size > 0) and node.Windows.Data[1] or nil if node.AuthorityForPos == ImGuiDataAuthority.Window and ref_window then ImGui.SetNextWindowPos(ref_window.Pos) elseif node.AuthorityForPos == ImGuiDataAuthority.DockNode then ImGui.SetNextWindowPos(node.Pos) end if node.AuthorityForSize == ImGuiDataAuthority.Window and ref_window then ImGui.SetNextWindowSize(ref_window.SizeFull) elseif node.AuthorityForSize == ImGuiDataAuthority.DockNode then ImGui.SetNextWindowSize(node.Size) end if node.AuthorityForSize == ImGuiDataAuthority.Window and ref_window then ImGui.SetNextWindowCollapsed(ref_window.Collapsed) end ImGui.SetNextWindowClass(node.WindowClass) local window_label = DockNodeGetHostWindowTitle(node) local window_flags = bit32.bor(ImGuiWindowFlags.NoScrollbar, ImGuiWindowFlags.NoScrollWithMouse, ImGuiWindowFlags.DockNodeHost) window_flags = bit32.bor(window_flags, ImGuiWindowFlags.NoFocusOnAppearing) window_flags = bit32.bor(window_flags, ImGuiWindowFlags.NoSavedSettings, ImGuiWindowFlags.NoNavFocus, ImGuiWindowFlags.NoCollapse) window_flags = bit32.bor(window_flags, ImGuiWindowFlags.NoTitleBar) ImGui.SetNextWindowBgAlpha(0.0) ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, ImVec2(0, 0)) ImGui.Begin(window_label, nil, window_flags) ImGui.PopStyleVar() beginned_into_host_window = true host_window = g.CurrentWindow DockNodeSetupHostWindow(node, host_window) ImVec2_Copy(host_window.DC.CursorPos, host_window.Pos) node.Pos = ImVec2(host_window.Pos.x, host_window.Pos.y) node.Size = ImVec2(host_window.Size.x, host_window.Size.y) if node.HostWindow.Appearing then ImGui.BringWindowToDisplayFront(node.HostWindow) end node.AuthorityForPos = ImGuiDataAuthority.Auto node.AuthorityForSize = ImGuiDataAuthority.Auto node.AuthorityForViewport = ImGuiDataAuthority.Auto elseif node.ParentNode then host_window = node.ParentNode.HostWindow node.HostWindow = host_window node.AuthorityForPos = ImGuiDataAuthority.Auto node.AuthorityForSize = ImGuiDataAuthority.Auto node.AuthorityForViewport = ImGuiDataAuthority.Auto end if node.WantMouseMove and node.HostWindow then ImGui.DockNodeStartMouseMovingWindow(node, node.HostWindow) end end node.RefViewportId = 0 if node:IsSplitNode() then IM_ASSERT(node.TabBar == nil) end if node:IsRootNode() then local p_window = g.NavWindow and g.NavWindow.RootWindow or nil while p_window ~= nil and p_window.DockNode ~= nil do local p_node = ImGui.DockNodeGetRootNode(p_window.DockNode) if p_node == node then node.LastFocusedNodeId = p_window.DockNode.ID break end p_window = p_node.HostWindow and p_node.HostWindow.RootWindow or nil end end local central_node = node.CentralNode local central_node_hole = node:IsRootNode() and host_window ~= nil and bit32.band(node_flags, ImGuiDockNodeFlags.PassthruCentralNode) ~= 0 and central_node ~= nil and central_node:IsEmpty() local central_node_hole_register_hit_test_hole = central_node_hole if central_node_hole then local payload = ImGui.GetDragDropPayload() if payload and ImGuiPayload_IsDataType(payload, IMGUI_PAYLOAD_TYPE_WINDOW) and ImGui.DockNodeIsDropAllowed(host_window, payload.Data) then central_node_hole_register_hit_test_hole = false end end if central_node_hole_register_hit_test_hole then IM_ASSERT(node:IsDockSpace()) local root_node = ImGui.DockNodeGetRootNode(central_node) local root_rect = ImRect(root_node.Pos, root_node.Pos + root_node.Size) local hole_rect = ImRect(central_node.Pos, central_node.Pos + central_node.Size) if hole_rect.Min.x > root_rect.Min.x then hole_rect.Min.x = hole_rect.Min.x + g.WindowsBorderHoverPadding end if hole_rect.Max.x < root_rect.Max.x then hole_rect.Max.x = hole_rect.Max.x - g.WindowsBorderHoverPadding end if hole_rect.Min.y > root_rect.Min.y then hole_rect.Min.y = hole_rect.Min.y + g.WindowsBorderHoverPadding end if hole_rect.Max.y < root_rect.Max.y then hole_rect.Max.y = hole_rect.Max.y - g.WindowsBorderHoverPadding end if central_node_hole and not IsRectInverted(hole_rect) then ImGui.SetWindowHitTestHole(host_window, hole_rect.Min, hole_rect.Max - hole_rect.Min) if host_window.ParentWindow then ImGui.SetWindowHitTestHole(host_window.ParentWindow, hole_rect.Min, hole_rect.Max - hole_rect.Min) end end end if node:IsRootNode() and host_window then ImGui.DockNodeTreeUpdatePosSize(node, host_window.Pos, host_window.Size) ImGui.PushStyleColor(ImGuiCol.Separator, g.Style.Colors[ImGuiCol.Border]) ImGui.PushStyleColor(ImGuiCol.SeparatorActive, g.Style.Colors[ImGuiCol.ResizeGripActive]) ImGui.PushStyleColor(ImGuiCol.SeparatorHovered, g.Style.Colors[ImGuiCol.ResizeGripHovered]) ImGui.DockNodeTreeUpdateSplitter(node) ImGui.PopStyleColor(3) end if host_window and node:IsEmpty() and node.IsVisible then host_window.DrawList:ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG) node.LastBgColor = (bit32.band(node_flags, ImGuiDockNodeFlags.PassthruCentralNode) ~= 0) and 0 or ImGui.GetColorU32(ImGuiCol.DockingEmptyBg) if node.LastBgColor ~= 0 then host_window.DrawList:AddRectFilled(node.Pos, node.Pos + node.Size, node.LastBgColor) end node.IsBgDrawnThisFrame = true end local render_dockspace_bg = node:IsRootNode() and host_window ~= nil and bit32.band(node_flags, ImGuiDockNodeFlags.PassthruCentralNode) ~= 0 if render_dockspace_bg and node.IsVisible then host_window.DrawList:ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG) if central_node_hole then ImGui.RenderRectFilledWithHole(host_window.DrawList, node:Rect(), central_node:Rect(), ImGui.GetColorU32(ImGuiCol.WindowBg), 0.0) else host_window.DrawList:AddRectFilled(node.Pos, node.Pos + node.Size, ImGui.GetColorU32(ImGuiCol.WindowBg), 0.0) end end if host_window then host_window.DrawList:ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG) end if host_window and node.Windows.Size > 0 then ImGui.DockNodeUpdateTabBar(node, host_window) else node.WantCloseAll = false node.WantCloseTabId = 0 node.IsFocused = false end if node.TabBar and node.TabBar.SelectedTabId ~= 0 then node.SelectedTabId = node.TabBar.SelectedTabId elseif node.Windows.Size > 0 then node.SelectedTabId = node.Windows.Data[1].TabId end if host_window and node.IsVisible then if node:IsRootNode() and (g.MovingWindow == nil or g.MovingWindow.RootWindowDockTree ~= host_window) then ImGui.BeginDockableDragDropTarget(host_window) end end node.LastFrameActive = g.FrameCount if host_window then if node.ChildNodes[0] then ImGui.DockNodeUpdate(node.ChildNodes[0]) end if node.ChildNodes[1] then ImGui.DockNodeUpdate(node.ChildNodes[1]) end if node:IsRootNode() then ImGui.RenderWindowOuterBorders(host_window) end end if beginned_into_host_window then ImGui.End() end end local GWindowDockStyleColors = { [0] = ImGuiCol.Text, ImGuiCol.TabHovered, ImGuiCol.Tab, ImGuiCol.TabSelected, ImGuiCol.TabSelectedOverline, ImGuiCol.TabDimmed, ImGuiCol.TabDimmedSelected, ImGuiCol.TabDimmedSelectedOverline, ImGuiCol.UnsavedMarker, } ImGui.GWindowDockStyleColors = GWindowDockStyleColors local function TabItemComparerByDockOrder(ta, tb) local a, b = ta.Window, tb.Window local ao = (a.DockOrder == -1) and INT_MAX or a.DockOrder local bo = (b.DockOrder == -1) and INT_MAX or b.DockOrder if ao ~= bo then return ao < bo end return a.BeginOrderWithinContext < b.BeginOrderWithinContext end function ImGui.DockNodeWindowMenuHandler_Default(ctx, node, tab_bar) if tab_bar.Tabs.Size == 1 then if ImGui.MenuItem(ImGui.LocalizeGetMsg(ImGuiLocKey.DockingHideTabBar), nil, node:IsHiddenTabBar()) then node.WantHiddenTabBarToggle = true end else for tab_n = 1, tab_bar.Tabs.Size do local tab = tab_bar.Tabs.Data[tab_n] if bit32.band(tab.Flags, ImGuiTabItemFlags.Button) == 0 then if ImGui.Selectable(ImGui.TabBarGetTabName(tab_bar, tab), tab.ID == tab_bar.SelectedTabId) then ImGui.TabBarQueueFocus(tab_bar, tab) end ImGui.SameLine() ImGui.Text(" ") end end end end function ImGui.DockNodeWindowMenuUpdate(node, tab_bar) local g = ImGui.GetCurrentContext() if g.Style.WindowMenuButtonPosition == ImGuiDir.Left then ImGui.SetNextWindowPos(ImVec2(node.Pos.x, node.Pos.y + ImGui.GetFrameHeight()), ImGuiCond.Always, ImVec2(0.0, 0.0)) else ImGui.SetNextWindowPos(ImVec2(node.Pos.x + node.Size.x, node.Pos.y + ImGui.GetFrameHeight()), ImGuiCond.Always, ImVec2(1.0, 0.0)) end if ImGui.BeginPopup("#WindowMenu") then node.IsFocused = true g.DockNodeWindowMenuHandler(g, node, tab_bar) ImGui.EndPopup() end end function ImGui.DockNodeBeginAmendTabBar(node) if node.TabBar == nil or node.HostWindow == nil then return false end if bit32.band(node.MergedFlags, ImGuiDockNodeFlags.KeepAliveOnly) ~= 0 then return false end if node.TabBar.ID == 0 then return false end ImGui.Begin(node.HostWindow.Name) ImGui.PushOverrideID(node.ID) local ret = ImGui.BeginTabBarEx(node.TabBar, node.TabBar.BarRect, node.TabBar.Flags) IM_ASSERT(ret) return true end function ImGui.DockNodeEndAmendTabBar() ImGui.EndTabBar() ImGui.PopID() ImGui.End() end local function IsDockNodeTitleBarHighlighted(node, root_node) local g = ImGui.GetCurrentContext() if g.NavWindowingTarget then return g.NavWindowingTarget.DockNode == node end if g.NavWindow and root_node.LastFocusedNodeId == node.ID then local parent_window = g.NavWindow.RootWindow while bit32.band(parent_window.Flags, ImGuiWindowFlags.ChildMenu) ~= 0 do parent_window = parent_window.ParentWindow.RootWindow end local parent_node = parent_window.DockNodeAsHost or parent_window.DockNode while parent_node ~= nil do parent_node = ImGui.DockNodeGetRootNode(parent_node) if parent_node == root_node then return true end parent_node = parent_node.HostWindow and parent_node.HostWindow.RootWindow.DockNode or nil end end return false end function ImGui.DockNodeUpdateTabBar(node, host_window) local g = ImGui.GetCurrentContext() local style = g.Style local node_was_active = (node.LastFrameActive + 1 == g.FrameCount) node.WantCloseAll = false node.WantCloseTabId = 0 local is_focused = false local root_node = ImGui.DockNodeGetRootNode(node) if IsDockNodeTitleBarHighlighted(node, root_node) then is_focused = true end if node:IsHiddenTabBar() or node:IsNoTabBar() then node.VisibleWindow = (node.Windows.Size > 0) and node.Windows.Data[1] or nil node.IsFocused = is_focused if is_focused then node.LastFrameFocused = g.FrameCount end if node.VisibleWindow then if is_focused or root_node.VisibleWindow == nil then root_node.VisibleWindow = node.VisibleWindow end if node.TabBar then node.TabBar.VisibleTabId = node.VisibleWindow.TabId end end return end local backup_skip_item = host_window.SkipItems if not node:IsDockSpace() then host_window.SkipItems = false host_window.DC.NavLayerCurrent = ImGuiNavLayer.Menu end ImGui.PushOverrideID(node.ID) local tab_bar = node.TabBar local tab_bar_is_recreated = (tab_bar == nil) if tab_bar == nil then ImGui.DockNodeAddTabBar(node) tab_bar = node.TabBar end local focus_tab_id = 0 node.IsFocused = is_focused local node_flags = node.MergedFlags local has_window_menu_button = bit32.band(node_flags, ImGuiDockNodeFlags.NoWindowMenuButton) == 0 and (style.WindowMenuButtonPosition ~= ImGuiDir.None) if has_window_menu_button and ImGui.IsPopupOpen("#WindowMenu") then local next_selected_tab_id = tab_bar.NextSelectedTabId ImGui.DockNodeWindowMenuUpdate(node, tab_bar) if tab_bar.NextSelectedTabId ~= 0 and tab_bar.NextSelectedTabId ~= next_selected_tab_id then focus_tab_id = tab_bar.NextSelectedTabId end is_focused = is_focused or node.IsFocused end local title_bar_rect, tab_bar_rect, window_menu_button_pos, close_button_pos = ImGui.DockNodeCalcTabBarLayout(node) local tabs_count_old = tab_bar.Tabs.Size for window_n = 1, node.Windows.Size do local window = node.Windows.Data[window_n] if ImGui.TabBarFindTabByID(tab_bar, window.TabId) == nil then ImGui.TabBarAddTab(tab_bar, ImGuiTabItemFlags.Unsorted, window) end end if is_focused then node.LastFrameFocused = g.FrameCount end local title_bar_col = ImGui.GetColorU32(host_window.Collapsed and ImGuiCol.TitleBgCollapsed or (is_focused and ImGuiCol.TitleBgActive or ImGuiCol.TitleBg)) local rounding_flags = ImGui.CalcRoundingFlagsForRectInRect(title_bar_rect, host_window:Rect(), g.Style.DockingSeparatorSize) host_window.DrawList:AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, host_window.WindowRounding, rounding_flags) if has_window_menu_button then if ImGui.CollapseButton(host_window:GetID("#COLLAPSE"), window_menu_button_pos, node) then ImGui.OpenPopup("#WindowMenu") end if ImGui.IsItemActive() then focus_tab_id = tab_bar.SelectedTabId end if ImGui.IsItemHovered(bit32.bor(ImGuiHoveredFlags.ForTooltip, ImGuiHoveredFlags.DelayNormal)) and g.HoveredIdTimer > 0.5 then ImGui.SetTooltip("%s", ImGui.LocalizeGetMsg(ImGuiLocKey.DockingDragToUndockOrMoveNode)) end end local tabs_unsorted_start = tab_bar.Tabs.Size -- 0-based local tab_n = tab_bar.Tabs.Size - 1 while tab_n >= 0 and bit32.band(tab_bar.Tabs.Data[tab_n + 1].Flags, ImGuiTabItemFlags.Unsorted) ~= 0 do local t = tab_bar.Tabs.Data[tab_n + 1] t.Flags = bit32.band(t.Flags, bit32.bnot(ImGuiTabItemFlags.Unsorted)) tabs_unsorted_start = tab_n tab_n = tab_n - 1 end if tab_bar.Tabs.Size > tabs_unsorted_start + 1 then local slice = {} for i = tabs_unsorted_start + 1, tab_bar.Tabs.Size do slice[#slice + 1] = tab_bar.Tabs.Data[i] end table.sort(slice, TabItemComparerByDockOrder) for i, t in ipairs(slice) do tab_bar.Tabs.Data[tabs_unsorted_start + i] = t end end if g.NavWindow and g.NavWindow.RootWindow.DockNode == node then tab_bar.SelectedTabId = g.NavWindow.RootWindow.TabId end if tab_bar_is_recreated and ImGui.TabBarFindTabByID(tab_bar, node.SelectedTabId) ~= nil then tab_bar.SelectedTabId = node.SelectedTabId tab_bar.NextSelectedTabId = node.SelectedTabId elseif tab_bar.Tabs.Size > tabs_count_old then tab_bar.SelectedTabId = tab_bar.Tabs.Data[tab_bar.Tabs.Size].Window.TabId tab_bar.NextSelectedTabId = tab_bar.SelectedTabId end local tab_bar_flags = bit32.bor(ImGuiTabBarFlags.Reorderable, ImGuiTabBarFlags.AutoSelectNewTabs) tab_bar_flags = bit32.bor(tab_bar_flags, ImGuiTabBarFlags.SaveSettings, ImGuiTabBarFlags.DockNode) tab_bar_flags = bit32.bor(tab_bar_flags, ImGuiTabBarFlags.FittingPolicyMixed) tab_bar_flags = bit32.bor(tab_bar_flags, ImGuiTabBarFlags.DrawSelectedOverline) if not host_window.Collapsed and is_focused then tab_bar_flags = bit32.bor(tab_bar_flags, ImGuiTabBarFlags.IsFocused) end tab_bar.ID = node.ID tab_bar.SeparatorMinX = node.Pos.x + host_window.WindowBorderSize tab_bar.SeparatorMaxX = node.Pos.x + node.Size.x - host_window.WindowBorderSize ImGui.BeginTabBarEx(tab_bar, tab_bar_rect, tab_bar_flags) local backup_style_cols = {} for color_n = 0, ImGuiWindowDockStyleCol.COUNT - 1 do local c = g.Style.Colors[GWindowDockStyleColors[color_n]] backup_style_cols[color_n] = ImVec4(c.x, c.y, c.z, c.w) end node.VisibleWindow = nil for window_n = 1, node.Windows.Size do local window = node.Windows.Data[window_n] if not (window.LastFrameActive + 1 < g.FrameCount and node_was_active) then local tab_item_flags = 0 tab_item_flags = bit32.bor(tab_item_flags, window.WindowClass.TabItemFlagsOverrideSet or 0) if bit32.band(window.Flags, ImGuiWindowFlags.UnsavedDocument) ~= 0 then tab_item_flags = bit32.bor(tab_item_flags, ImGuiTabItemFlags.UnsavedDocument) end if bit32.band(tab_bar.Flags, ImGuiTabBarFlags.NoCloseWithMiddleMouseButton) ~= 0 then tab_item_flags = bit32.bor(tab_item_flags, ImGuiTabItemFlags.NoCloseWithMiddleMouseButton) end for color_n = 0, ImGuiWindowDockStyleCol.COUNT - 1 do ImVec4_Copy(g.Style.Colors[GWindowDockStyleColors[color_n]], ImGui.ColorConvertU32ToFloat4(window.DockStyle.Colors[color_n])) end local _, tab_open = ImGui.TabItemEx(tab_bar, window.Name, window.HasCloseButton and true or nil, tab_item_flags, window) if tab_open == false then node.WantCloseTabId = window.TabId end if tab_bar.VisibleTabId == window.TabId then node.VisibleWindow = window end window.DC.DockTabItemStatusFlags = g.LastItemData.StatusFlags window.DC.DockTabItemRect = ImRect(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max) if g.NavWindow and g.NavWindow.RootWindow == window and bit32.band(window.DC.NavLayersActiveMask, bit32.lshift(1, ImGuiNavLayer.Menu)) == 0 then host_window.NavLastIds[1] = window.TabId end end end for color_n = 0, ImGuiWindowDockStyleCol.COUNT - 1 do ImVec4_Copy(g.Style.Colors[GWindowDockStyleColors[color_n]], backup_style_cols[color_n]) end if node.VisibleWindow then if is_focused or root_node.VisibleWindow == nil then root_node.VisibleWindow = node.VisibleWindow end end local close_button_is_enabled = node.HasCloseButton and node.VisibleWindow and node.VisibleWindow.HasCloseButton local close_button_is_visible = node.HasCloseButton if close_button_is_visible then if not close_button_is_enabled then ImGui.PushItemFlag(ImGuiItemFlags.Disabled, true) local tc = style.Colors[ImGuiCol.Text] ImGui.PushStyleColor(ImGuiCol.Text, ImVec4(tc.x, tc.y, tc.z, tc.w * 0.4)) end if ImGui.CloseButton(host_window:GetID("#CLOSE"), close_button_pos) then node.WantCloseAll = true for n = 1, tab_bar.Tabs.Size do ImGui.TabBarCloseTab(tab_bar, tab_bar.Tabs.Data[n]) end end if not close_button_is_enabled then ImGui.PopStyleColor() ImGui.PopItemFlag() end end local title_bar_id = host_window:GetID("#TITLEBAR") if g.HoveredId == 0 or g.HoveredId == title_bar_id or g.ActiveId == title_bar_id then ImGui.KeepAliveID(title_bar_id) local _, _, held = ImGui.ButtonBehavior(title_bar_rect, title_bar_id, ImGuiButtonFlags.AllowOverlap) if g.HoveredId == title_bar_id then g.LastItemData.ID = title_bar_id end if held then if ImGui.IsMouseClicked(0) then focus_tab_id = tab_bar.SelectedTabId end local tab = ImGui.TabBarFindTabByID(tab_bar, tab_bar.SelectedTabId) if tab then ImGui.StartMouseMovingWindowOrNode(tab.Window or node.HostWindow, node, false) end end end if tab_bar.NextSelectedTabId ~= 0 then focus_tab_id = tab_bar.NextSelectedTabId end if focus_tab_id ~= 0 then local tab = ImGui.TabBarFindTabByID(tab_bar, focus_tab_id) if tab and tab.Window then ImGui.FocusWindow(tab.Window) if g.NavId == 0 then ImGui.NavInitWindow(tab.Window, false) end end end ImGui.EndTabBar() ImGui.PopID() if not node:IsDockSpace() then host_window.DC.NavLayerCurrent = ImGuiNavLayer.Main host_window.SkipItems = backup_skip_item end end function ImGui.DockNodeAddTabBar(node) IM_ASSERT(node.TabBar == nil) node.TabBar = ImGuiTabBar() end function ImGui.DockNodeRemoveTabBar(node) if node.TabBar == nil then return end node.TabBar = nil end local function DockNodeIsDropAllowedOne(payload, host_window) if host_window.DockNodeAsHost and host_window.DockNodeAsHost:IsDockSpace() and payload.BeginOrderWithinContext < host_window.BeginOrderWithinContext then return false end local host_class = host_window.DockNodeAsHost and host_window.DockNodeAsHost.WindowClass or host_window.WindowClass local payload_class = payload.WindowClass if host_class.ClassId ~= payload_class.ClassId then local pass = false if host_class.ClassId ~= 0 and host_class.DockingAllowUnclassed and payload_class.ClassId == 0 then pass = true end if payload_class.ClassId ~= 0 and payload_class.DockingAllowUnclassed and host_class.ClassId == 0 then pass = true end if not pass then return false end end local g = ImGui.GetCurrentContext() for i = g.OpenPopupStack.Size, 1, -1 do local popup_window = g.OpenPopupStack.Data[i].Window if popup_window and ImGui.IsWindowWithinBeginStackOf(payload, popup_window) then return false end end return true end function ImGui.DockNodeIsDropAllowed(host_window, root_payload) if root_payload.DockNodeAsHost and root_payload.DockNodeAsHost:IsSplitNode() then return true end local payload_count = root_payload.DockNodeAsHost and root_payload.DockNodeAsHost.Windows.Size or 1 for payload_n = 1, payload_count do local payload = root_payload.DockNodeAsHost and root_payload.DockNodeAsHost.Windows.Data[payload_n] or root_payload if DockNodeIsDropAllowedOne(payload, host_window) then return true end end return false end --- @return ImRect title_rect, ImRect tab_bar_rect, ImVec2 window_menu_button_pos, ImVec2? close_button_pos function ImGui.DockNodeCalcTabBarLayout(node) local g = ImGui.GetCurrentContext() local style = g.Style local r = ImRect(node.Pos.x, node.Pos.y, node.Pos.x + node.Size.x, node.Pos.y + g.FontSize + g.Style.FramePadding.y * 2.0) local out_title_rect = ImRect(r.Min, r.Max) r.Min.x = r.Min.x + style.WindowBorderSize r.Max.x = r.Max.x - style.WindowBorderSize local button_sz = g.FontSize r.Min.x = r.Min.x + style.FramePadding.x r.Max.x = r.Max.x - style.FramePadding.x local window_menu_button_pos = ImVec2(r.Min.x, r.Min.y + style.FramePadding.y) local out_close_button_pos = nil if node.HasCloseButton then out_close_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y) r.Max.x = r.Max.x - (button_sz + style.ItemInnerSpacing.x) end if node.HasWindowMenuButton and style.WindowMenuButtonPosition == ImGuiDir.Left then r.Min.x = r.Min.x + button_sz + style.ItemInnerSpacing.x elseif node.HasWindowMenuButton and style.WindowMenuButtonPosition == ImGuiDir.Right then window_menu_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y) r.Max.x = r.Max.x - (button_sz + style.ItemInnerSpacing.x) end return out_title_rect, r, window_menu_button_pos, out_close_button_pos end --- Modifies pos_old/size_old/pos_new/size_new in place (ImVec2 tables) function ImGui.DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, dir, size_new_desired) local g = ImGui.GetCurrentContext() local dock_spacing = g.Style.ItemInnerSpacing.x local axis = (dir == ImGuiDir.Left or dir == ImGuiDir.Right) and ImGuiAxis.X or ImGuiAxis.Y local a, o = axis, 3 - axis -- the port's ImGuiAxis is 1-based (X=1,Y=2): it is directly the ImVec2 index pos_new[o] = pos_old[o] size_new[o] = size_old[o] local w_avail = size_old[a] - dock_spacing if size_new_desired[a] > 0.0 and size_new_desired[a] <= w_avail * 0.5 then size_new[a] = size_new_desired[a] size_old[a] = IM_TRUNC(w_avail - size_new[a]) else size_new[a] = IM_TRUNC(w_avail * 0.5) size_old[a] = IM_TRUNC(w_avail - size_new[a]) end if dir == ImGuiDir.Right or dir == ImGuiDir.Down then pos_new[a] = pos_old[a] + size_old[a] + dock_spacing elseif dir == ImGuiDir.Left or dir == ImGuiDir.Up then pos_new[a] = pos_old[a] pos_old[a] = pos_new[a] + size_new[a] + dock_spacing end end local function ImGetDirQuadrantFromDeltaLocal(dx, dy) if math.abs(dx) > math.abs(dy) then return (dx > 0.0) and ImGuiDir.Right or ImGuiDir.Left end return (dy > 0.0) and ImGuiDir.Down or ImGuiDir.Up end --- @return bool hit, ImRect out_r function ImGui.DockNodeCalcDropRectsAndTestMousePos(parent, dir, outer_docking, test_mouse_pos) local g = ImGui.GetCurrentContext() local parent_smaller_axis = ImMin(parent:GetWidth(), parent:GetHeight()) local hs_for_central_nodes = ImMin(g.FontSize * 1.5, ImMax(g.FontSize * 0.5, parent_smaller_axis / 8.0)) local hs_w, hs_h, off if outer_docking then hs_w = ImTrunc(hs_for_central_nodes * 1.50) hs_h = ImTrunc(hs_for_central_nodes * 0.80) off = ImVec2(ImTrunc(parent:GetWidth() * 0.5 - hs_h), ImTrunc(parent:GetHeight() * 0.5 - hs_h)) else hs_w = ImTrunc(hs_for_central_nodes) hs_h = ImTrunc(hs_for_central_nodes * 0.90) off = ImVec2(ImTrunc(hs_w * 2.40), ImTrunc(hs_w * 2.40)) end local pc = parent:GetCenter() local c = ImVec2(ImTrunc(pc.x), ImTrunc(pc.y)) local out_r if dir == ImGuiDir.None then out_r = ImRect(c.x - hs_w, c.y - hs_w, c.x + hs_w, c.y + hs_w) elseif dir == ImGuiDir.Up then out_r = ImRect(c.x - hs_w, c.y - off.y - hs_h, c.x + hs_w, c.y - off.y + hs_h) elseif dir == ImGuiDir.Down then out_r = ImRect(c.x - hs_w, c.y + off.y - hs_h, c.x + hs_w, c.y + off.y + hs_h) elseif dir == ImGuiDir.Left then out_r = ImRect(c.x - off.x - hs_h, c.y - hs_w, c.x - off.x + hs_h, c.y + hs_w) elseif dir == ImGuiDir.Right then out_r = ImRect(c.x + off.x - hs_h, c.y - hs_w, c.x + off.x + hs_h, c.y + hs_w) end if test_mouse_pos == nil then return false, out_r end local hit_r = ImRect(out_r.Min, out_r.Max) if not outer_docking then hit_r:Expand(ImTrunc(hs_w * 0.30)) local mdx, mdy = test_mouse_pos.x - c.x, test_mouse_pos.y - c.y local mouse_delta_len2 = mdx * mdx + mdy * mdy local r_threshold_center = hs_w * 1.4 local r_threshold_sides = hs_w * (1.4 + 1.2) if mouse_delta_len2 < r_threshold_center * r_threshold_center then return (dir == ImGuiDir.None), out_r end if mouse_delta_len2 < r_threshold_sides * r_threshold_sides then return (dir == ImGetDirQuadrantFromDeltaLocal(mdx, mdy)), out_r end end return hit_r:Contains(test_mouse_pos), out_r end function ImGui.DockNodePreviewDockSetup(host_window, host_node, payload_window, payload_node, data, is_explicit_target, is_outer_docking) local g = ImGui.GetCurrentContext() if payload_node == nil then payload_node = payload_window.DockNodeAsHost end local ref_node_for_rect = (host_node and not host_node.IsVisible) and ImGui.DockNodeGetRootNode(host_node) or host_node if ref_node_for_rect then IM_ASSERT(ref_node_for_rect.IsVisible == true) end local src_node_flags = payload_node and payload_node.MergedFlags or payload_window.WindowClass.DockNodeFlagsOverrideSet local dst_node_flags = host_node and host_node.MergedFlags or host_window.WindowClass.DockNodeFlagsOverrideSet data.IsCenterAvailable = true if is_outer_docking then data.IsCenterAvailable = false elseif g.IO.ConfigDockingNoDockingOver then data.IsCenterAvailable = false elseif bit32.band(dst_node_flags, ImGuiDockNodeFlags.NoDockingOverMe) ~= 0 then data.IsCenterAvailable = false elseif host_node and bit32.band(dst_node_flags, ImGuiDockNodeFlags.NoDockingOverCentralNode) ~= 0 and host_node:IsCentralNode() then data.IsCenterAvailable = false elseif (not host_node or not host_node:IsEmpty()) and payload_node and payload_node:IsSplitNode() and (payload_node.OnlyNodeWithWindows == nil) then data.IsCenterAvailable = false elseif bit32.band(src_node_flags, ImGuiDockNodeFlags.NoDockingOverOther) ~= 0 and (not host_node or not host_node:IsEmpty()) then data.IsCenterAvailable = false elseif bit32.band(src_node_flags, ImGuiDockNodeFlags.NoDockingOverEmpty) ~= 0 and host_node and host_node:IsEmpty() then data.IsCenterAvailable = false end data.IsSidesAvailable = true if bit32.band(dst_node_flags, ImGuiDockNodeFlags.NoDockingSplit) ~= 0 or g.IO.ConfigDockingNoSplit then data.IsSidesAvailable = false elseif not is_outer_docking and host_node and host_node.ParentNode == nil and host_node:IsCentralNode() then data.IsSidesAvailable = false elseif bit32.band(src_node_flags, ImGuiDockNodeFlags.NoDockingSplitOther) ~= 0 then data.IsSidesAvailable = false end data.FutureNode.HasCloseButton = (host_node and host_node.HasCloseButton or (not host_node and host_window.HasCloseButton)) or payload_window.HasCloseButton data.FutureNode.HasWindowMenuButton = host_node and true or (bit32.band(host_window.Flags, ImGuiWindowFlags.NoCollapse) == 0) local rp = ref_node_for_rect and ref_node_for_rect.Pos or host_window.Pos local rs = ref_node_for_rect and ref_node_for_rect.Size or host_window.Size data.FutureNode.Pos = ImVec2(rp.x, rp.y) data.FutureNode.Size = ImVec2(rs.x, rs.y) data.SplitNode = host_node data.SplitDir = ImGuiDir.None data.IsSplitDirExplicit = false if not host_window.Collapsed then for dir = ImGuiDir.None, ImGuiDir.COUNT - 1 do if not ((dir == ImGuiDir.None and not data.IsCenterAvailable) or (dir ~= ImGuiDir.None and not data.IsSidesAvailable)) then local hit, r = ImGui.DockNodeCalcDropRectsAndTestMousePos(data.FutureNode:Rect(), dir, is_outer_docking, g.IO.MousePos) data.DropRectsDraw[dir + 1] = r if hit then data.SplitDir = dir data.IsSplitDirExplicit = true end end end end data.IsDropAllowed = (data.SplitDir ~= ImGuiDir.None) or data.IsCenterAvailable if not is_explicit_target and not data.IsSplitDirExplicit and not g.IO.ConfigDockingWithShift then data.IsDropAllowed = false end data.SplitRatio = 0.0 if data.SplitDir ~= ImGuiDir.None then local split_dir = data.SplitDir local split_axis = (split_dir == ImGuiDir.Left or split_dir == ImGuiDir.Right) and ImGuiAxis.X or ImGuiAxis.Y local pos_new, pos_old = ImVec2(0, 0), ImVec2(data.FutureNode.Pos.x, data.FutureNode.Pos.y) local size_new, size_old = ImVec2(0, 0), ImVec2(data.FutureNode.Size.x, data.FutureNode.Size.y) ImGui.DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, payload_window.Size) local split_ratio = ImSaturate(size_new[split_axis] / data.FutureNode.Size[split_axis]) data.FutureNode.Pos = pos_new data.FutureNode.Size = size_new data.SplitRatio = (split_dir == ImGuiDir.Right or split_dir == ImGuiDir.Down) and (1.0 - split_ratio) or split_ratio end end function ImGui.DockNodePreviewDockRender(host_window, host_node, root_payload, data) local g = ImGui.GetCurrentContext() IM_ASSERT(g.CurrentWindow == host_window) local is_transparent_payload = g.IO.ConfigDockingTransparentPayload local overlay_draw_lists = { ImGui.GetForegroundDrawList(host_window.Viewport) } local overlay_col_main = ImGui.GetColorU32(ImGuiCol.DockingPreview, is_transparent_payload and 0.60 or 0.40) local overlay_col_drop = ImGui.GetColorU32(ImGuiCol.DockingPreview, is_transparent_payload and 0.90 or 0.70) local overlay_col_drop_hovered = ImGui.GetColorU32(ImGuiCol.DockingPreview, is_transparent_payload and 1.20 or 1.00) local overlay_col_lines = ImGui.GetColorU32(ImGuiCol.NavWindowingHighlight, is_transparent_payload and 0.80 or 0.60) local can_preview_tabs = (root_payload.DockNodeAsHost == nil or root_payload.DockNodeAsHost.Windows.Size > 0) if data.IsDropAllowed then local overlay_rect = data.FutureNode:Rect() if data.SplitDir == ImGuiDir.None and can_preview_tabs then overlay_rect.Min.y = overlay_rect.Min.y + ImGui.GetFrameHeight() end if data.SplitDir ~= ImGuiDir.None or data.IsCenterAvailable then for _, dl in ipairs(overlay_draw_lists) do dl:AddRectFilled(overlay_rect.Min, overlay_rect.Max, overlay_col_main, host_window.WindowRounding, ImGui.CalcRoundingFlagsForRectInRect(overlay_rect, host_window:Rect(), g.Style.DockingSeparatorSize)) end end end if data.IsDropAllowed and can_preview_tabs and data.SplitDir == ImGuiDir.None and data.IsCenterAvailable then local _, tab_bar_rect = ImGui.DockNodeCalcTabBarLayout(data.FutureNode) local tab_pos = ImVec2(tab_bar_rect.Min.x, tab_bar_rect.Min.y) if host_node and host_node.TabBar then if not host_node:IsHiddenTabBar() and not host_node:IsNoTabBar() then tab_pos.x = tab_pos.x + host_node.TabBar.WidthAllTabs + g.Style.ItemInnerSpacing.x else tab_pos.x = tab_pos.x + g.Style.ItemInnerSpacing.x + ImGui.TabItemCalcSize(host_node.Windows.Data[1]).x end elseif bit32.band(host_window.Flags, ImGuiWindowFlags.DockNodeHost) == 0 then tab_pos.x = tab_pos.x + g.Style.ItemInnerSpacing.x + ImGui.TabItemCalcSize(host_window).x end if root_payload.DockNodeAsHost then IM_ASSERT(root_payload.DockNodeAsHost.Windows.Size <= root_payload.DockNodeAsHost.TabBar.Tabs.Size) end local tab_bar_with_payload = root_payload.DockNodeAsHost and root_payload.DockNodeAsHost.TabBar or nil local payload_count = tab_bar_with_payload and tab_bar_with_payload.Tabs.Size or 1 for payload_n = 1, payload_count do local payload_window = tab_bar_with_payload and tab_bar_with_payload.Tabs.Data[payload_n].Window or root_payload if not (tab_bar_with_payload and payload_window == nil) and DockNodeIsDropAllowedOne(payload_window, host_window) then local tab_size = ImGui.TabItemCalcSize(payload_window) local tab_bb = ImRect(tab_pos.x, tab_pos.y, tab_pos.x + tab_size.x, tab_pos.y + tab_size.y) tab_pos.x = tab_pos.x + tab_size.x + g.Style.ItemInnerSpacing.x local overlay_col_text = ImGui.GetColorU32(payload_window.DockStyle.Colors[ImGuiWindowDockStyleCol.Text], 1.0, true) local overlay_col_tabs = ImGui.GetColorU32(payload_window.DockStyle.Colors[ImGuiWindowDockStyleCol.TabSelected], 1.0, true) local overlay_col_unsaved_marker = ImGui.GetColorU32(payload_window.DockStyle.Colors[ImGuiWindowDockStyleCol.UnsavedMarker], 1.0, true) ImGui.PushStyleColor(ImGuiCol.Text, ImGui.ColorConvertU32ToFloat4(overlay_col_text)) ImGui.PushStyleColor(ImGuiCol.UnsavedMarker, ImGui.ColorConvertU32ToFloat4(overlay_col_unsaved_marker)) for _, dl in ipairs(overlay_draw_lists) do local tab_flags = bit32.band(payload_window.Flags, ImGuiWindowFlags.UnsavedDocument) ~= 0 and ImGuiTabItemFlags.UnsavedDocument or 0 local contains = tab_bar_rect:Contains(tab_bb) if not contains then dl:PushClipRect(tab_bar_rect.Min, tab_bar_rect.Max) end ImGui.TabItemBackground(dl, tab_bb, tab_flags, overlay_col_tabs) ImGui.TabItemLabelAndCloseButton(dl, tab_bb, tab_flags, g.Style.FramePadding, payload_window.Name, 0, 0, false) if not contains then dl:PopClipRect() end end ImGui.PopStyleColor(2) end end end local scale = g.Style._MainScale or 1.0 local overlay_rounding = ImMax(math.floor(3.0 * scale), g.Style.FrameRounding) for dir = ImGuiDir.None, ImGuiDir.COUNT - 1 do local draw_r = data.DropRectsDraw[dir + 1] if not IsRectInverted(draw_r) then local draw_r_in = ImRect(draw_r.Min, draw_r.Max) draw_r_in:Expand(math.floor(-2.0 * scale)) local overlay_col = (data.SplitDir == dir and data.IsSplitDirExplicit) and overlay_col_drop_hovered or overlay_col_drop local thickness = math.floor(1.0 * scale) for _, dl in ipairs(overlay_draw_lists) do local cc = draw_r_in:GetCenter() local center = ImVec2(math.floor(cc.x), math.floor(cc.y)) dl:AddRectFilled(draw_r.Min, draw_r.Max, overlay_col, overlay_rounding) dl:AddRect(draw_r_in.Min, draw_r_in.Max, overlay_col_lines, overlay_rounding, thickness) if dir == ImGuiDir.Left or dir == ImGuiDir.Right then dl:AddLineV(center.x, draw_r_in.Min.y, draw_r_in.Max.y, overlay_col_lines, thickness) end if dir == ImGuiDir.Up or dir == ImGuiDir.Down then dl:AddLineH(draw_r_in.Min.x, draw_r_in.Max.x, center.y, overlay_col_lines, thickness) end end end if (host_node and bit32.band(host_node.MergedFlags, ImGuiDockNodeFlags.NoDockingSplit) ~= 0) or g.IO.ConfigDockingNoSplit then return end end end --------------------------------------------------------------------------------------- -- Docking: ImGuiDockNode Tree manipulation functions --------------------------------------------------------------------------------------- function ImGui.DockNodeTreeSplit(ctx, parent_node, split_axis, split_inheritor_child_idx, split_ratio, new_node) local g = ImGui.GetCurrentContext() IM_ASSERT(split_axis ~= ImGuiAxis.None) local child_0 = (new_node and split_inheritor_child_idx ~= 0) and new_node or ImGui.DockContextAddNode(ctx, 0) child_0.ParentNode = parent_node local child_1 = (new_node and split_inheritor_child_idx ~= 1) and new_node or ImGui.DockContextAddNode(ctx, 0) child_1.ParentNode = parent_node local child_inheritor = (split_inheritor_child_idx == 0) and child_0 or child_1 ImGui.DockNodeMoveChildNodes(child_inheritor, parent_node) parent_node.ChildNodes[0] = child_0 parent_node.ChildNodes[1] = child_1 parent_node.ChildNodes[split_inheritor_child_idx].VisibleWindow = parent_node.VisibleWindow parent_node.SplitAxis = split_axis parent_node.VisibleWindow = nil parent_node.AuthorityForPos = ImGuiDataAuthority.DockNode parent_node.AuthorityForSize = ImGuiDataAuthority.DockNode local a = split_axis local size_avail = parent_node.Size[a] - g.Style.DockingSeparatorSize size_avail = ImMax(size_avail, g.Style.WindowMinSize[a] * 2.0) IM_ASSERT(size_avail > 0.0) child_0.SizeRef = ImVec2(parent_node.Size.x, parent_node.Size.y) child_1.SizeRef = ImVec2(parent_node.Size.x, parent_node.Size.y) child_0.SizeRef[a] = ImTrunc(size_avail * split_ratio) child_1.SizeRef[a] = ImTrunc(size_avail - child_0.SizeRef[a]) ImGui.DockNodeMoveWindows(parent_node.ChildNodes[split_inheritor_child_idx], parent_node) DockSettingsRenameNodeReferences(parent_node.ID, parent_node.ChildNodes[split_inheritor_child_idx].ID) ImGui.DockNodeUpdateHasCentralNodeChild(ImGui.DockNodeGetRootNode(parent_node)) ImGui.DockNodeTreeUpdatePosSize(parent_node, parent_node.Pos, parent_node.Size) child_0.SharedFlags = bit32.band(parent_node.SharedFlags, ImGuiDockNodeFlags.SharedFlagsInheritMask_) child_1.SharedFlags = bit32.band(parent_node.SharedFlags, ImGuiDockNodeFlags.SharedFlagsInheritMask_) child_inheritor.LocalFlags = bit32.band(parent_node.LocalFlags, ImGuiDockNodeFlags.LocalFlagsTransferMask_) parent_node.LocalFlags = bit32.band(parent_node.LocalFlags, bit32.bnot(ImGuiDockNodeFlags.LocalFlagsTransferMask_)) child_0:UpdateMergedFlags() child_1:UpdateMergedFlags() parent_node:UpdateMergedFlags() if child_inheritor:IsCentralNode() then ImGui.DockNodeGetRootNode(parent_node).CentralNode = child_inheritor end end function ImGui.DockNodeTreeMerge(ctx, parent_node, merge_lead_child) local child_0 = parent_node.ChildNodes[0] local child_1 = parent_node.ChildNodes[1] IM_ASSERT(child_0 or child_1) IM_ASSERT(merge_lead_child == child_0 or merge_lead_child == child_1) if (child_0 and child_0.Windows.Size > 0) or (child_1 and child_1.Windows.Size > 0) then IM_ASSERT(parent_node.TabBar == nil) IM_ASSERT(parent_node.Windows.Size == 0) end local backup_last_explicit_size = ImVec2(parent_node.SizeRef.x, parent_node.SizeRef.y) ImGui.DockNodeMoveChildNodes(parent_node, merge_lead_child) if child_0 then ImGui.DockNodeMoveWindows(parent_node, child_0) DockSettingsRenameNodeReferences(child_0.ID, parent_node.ID) end if child_1 then ImGui.DockNodeMoveWindows(parent_node, child_1) DockSettingsRenameNodeReferences(child_1.ID, parent_node.ID) end ImGui.DockNodeApplyPosSizeToWindows(parent_node) parent_node.AuthorityForPos = ImGuiDataAuthority.Auto parent_node.AuthorityForSize = ImGuiDataAuthority.Auto parent_node.AuthorityForViewport = ImGuiDataAuthority.Auto parent_node.VisibleWindow = merge_lead_child.VisibleWindow parent_node.SizeRef = backup_last_explicit_size local M = ImGuiDockNodeFlags.LocalFlagsTransferMask_ parent_node.LocalFlags = bit32.band(parent_node.LocalFlags, bit32.bnot(M)) parent_node.LocalFlags = bit32.bor(parent_node.LocalFlags, bit32.band(child_0 and child_0.LocalFlags or 0, M)) parent_node.LocalFlags = bit32.bor(parent_node.LocalFlags, bit32.band(child_1 and child_1.LocalFlags or 0, M)) parent_node.LocalFlagsInWindows = bit32.bor(child_0 and child_0.LocalFlagsInWindows or 0, child_1 and child_1.LocalFlagsInWindows or 0) parent_node:UpdateMergedFlags() if child_0 then ImGui.DockContextDeleteNode(ctx, child_0) end if child_1 then ImGui.DockContextDeleteNode(ctx, child_1) end end function ImGui.DockNodeTreeUpdatePosSize(node, pos, size, only_write_to_single_node) local g = ImGui.GetCurrentContext() local write_to_node = only_write_to_single_node == nil or only_write_to_single_node == node if write_to_node then node.Pos = ImVec2(pos.x, pos.y) node.Size = ImVec2(size.x, size.y) end if node:IsLeafNode() then return end local child_0 = node.ChildNodes[0] local child_1 = node.ChildNodes[1] local child_0_pos, child_1_pos = ImVec2(pos.x, pos.y), ImVec2(pos.x, pos.y) local child_0_size, child_1_size = ImVec2(size.x, size.y), ImVec2(size.x, size.y) local child_0_is_toward_single_node = (only_write_to_single_node ~= nil and ImGui.DockNodeIsInHierarchyOf(only_write_to_single_node, child_0)) local child_1_is_toward_single_node = (only_write_to_single_node ~= nil and ImGui.DockNodeIsInHierarchyOf(only_write_to_single_node, child_1)) local child_0_is_or_will_be_visible = child_0.IsVisible or child_0_is_toward_single_node local child_1_is_or_will_be_visible = child_1.IsVisible or child_1_is_toward_single_node if child_0_is_or_will_be_visible and child_1_is_or_will_be_visible then local spacing = g.Style.DockingSeparatorSize local a = node.SplitAxis local size_avail = ImMax(size[a] - spacing, 0.0) local size_min_each = ImTrunc(ImMin(size_avail, g.Style.WindowMinSize[a] * 2.0) * 0.5) if child_0.WantLockSizeOnce and not child_1.WantLockSizeOnce then child_0_size[a] = ImMin(size_avail - 1.0, child_0.Size[a]); child_0.SizeRef[a] = child_0_size[a] child_1_size[a] = size_avail - child_0_size[a]; child_1.SizeRef[a] = child_1_size[a] elseif child_1.WantLockSizeOnce and not child_0.WantLockSizeOnce then child_1_size[a] = ImMin(size_avail - 1.0, child_1.Size[a]); child_1.SizeRef[a] = child_1_size[a] child_0_size[a] = size_avail - child_1_size[a]; child_0.SizeRef[a] = child_0_size[a] elseif child_0.WantLockSizeOnce and child_1.WantLockSizeOnce then local split_ratio = child_0_size[a] / (child_0_size[a] + child_1_size[a]) child_0_size[a] = ImTrunc(size_avail * split_ratio); child_0.SizeRef[a] = child_0_size[a] child_1_size[a] = size_avail - child_0_size[a]; child_1.SizeRef[a] = child_1_size[a] elseif child_0.SizeRef[a] ~= 0.0 and child_1.HasCentralNodeChild then child_0_size[a] = ImMin(size_avail - size_min_each, child_0.SizeRef[a]) child_1_size[a] = size_avail - child_0_size[a] elseif child_1.SizeRef[a] ~= 0.0 and child_0.HasCentralNodeChild then child_1_size[a] = ImMin(size_avail - size_min_each, child_1.SizeRef[a]) child_0_size[a] = size_avail - child_1_size[a] else local split_ratio = child_0.SizeRef[a] / (child_0.SizeRef[a] + child_1.SizeRef[a]) child_0_size[a] = ImMax(size_min_each, ImTrunc(size_avail * split_ratio + 0.5)) child_1_size[a] = size_avail - child_0_size[a] end child_1_pos[a] = child_1_pos[a] + spacing + child_0_size[a] end if only_write_to_single_node == nil then child_0.WantLockSizeOnce = false child_1.WantLockSizeOnce = false end local child_0_recurse = only_write_to_single_node and child_0_is_toward_single_node or (not only_write_to_single_node and child_0.IsVisible) local child_1_recurse = only_write_to_single_node and child_1_is_toward_single_node or (not only_write_to_single_node and child_1.IsVisible) if child_0_recurse then ImGui.DockNodeTreeUpdatePosSize(child_0, child_0_pos, child_0_size) end if child_1_recurse then ImGui.DockNodeTreeUpdatePosSize(child_1, child_1_pos, child_1_size) end end local function DockNodeTreeUpdateSplitterFindTouchingNode(node, axis, side, touching_nodes) if node:IsLeafNode() then table.insert(touching_nodes, node) return end if node.ChildNodes[0].IsVisible then if node.SplitAxis ~= axis or side == 0 or not node.ChildNodes[1].IsVisible then DockNodeTreeUpdateSplitterFindTouchingNode(node.ChildNodes[0], axis, side, touching_nodes) end end if node.ChildNodes[1].IsVisible then if node.SplitAxis ~= axis or side == 1 or not node.ChildNodes[0].IsVisible then DockNodeTreeUpdateSplitterFindTouchingNode(node.ChildNodes[1], axis, side, touching_nodes) end end end local WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04 function ImGui.DockNodeTreeUpdateSplitter(node) if node:IsLeafNode() then return end local g = ImGui.GetCurrentContext() local child_0 = node.ChildNodes[0] local child_1 = node.ChildNodes[1] if child_0.IsVisible and child_1.IsVisible then local axis = node.SplitAxis IM_ASSERT(axis ~= ImGuiAxis.None) local a, o = axis, 3 - axis -- the port's ImGuiAxis is 1-based (X=1,Y=2): it is directly the ImVec2 index local bb = ImRect(ImVec2(child_0.Pos.x, child_0.Pos.y), ImVec2(child_1.Pos.x, child_1.Pos.y)) bb.Min[a] = bb.Min[a] + child_0.Size[a] bb.Max[o] = bb.Max[o] + child_1.Size[o] local merged_flags = bit32.bor(child_0.MergedFlags, child_1.MergedFlags) local no_resize_axis_flag = (axis == ImGuiAxis.X) and ImGuiDockNodeFlags.NoResizeX or ImGuiDockNodeFlags.NoResizeY if bit32.band(merged_flags, ImGuiDockNodeFlags.NoResize) ~= 0 or bit32.band(merged_flags, no_resize_axis_flag) ~= 0 then local window = g.CurrentWindow window.DrawList:AddRectFilled(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.Separator), g.Style.FrameRounding) else ImGui.PushID(node.ID) local touching_nodes = { [0] = {}, [1] = {} } local min_size = g.Style.WindowMinSize[a] local resize_limits = { [0] = node.ChildNodes[0].Pos[a] + min_size, [1] = node.ChildNodes[1].Pos[a] + node.ChildNodes[1].Size[a] - min_size } local splitter_id = ImGui.GetID("##Splitter") if g.ActiveId == splitter_id then DockNodeTreeUpdateSplitterFindTouchingNode(child_0, axis, 1, touching_nodes[0]) DockNodeTreeUpdateSplitterFindTouchingNode(child_1, axis, 0, touching_nodes[1]) for _, tn in ipairs(touching_nodes[0]) do resize_limits[0] = ImMax(resize_limits[0], tn:Rect().Min[a] + min_size) end for _, tn in ipairs(touching_nodes[1]) do resize_limits[1] = ImMin(resize_limits[1], tn:Rect().Max[a] - min_size) end end local cur_size_0 = child_0.Size[a] local cur_size_1 = child_1.Size[a] local min_size_0 = resize_limits[0] - child_0.Pos[a] local min_size_1 = child_1.Pos[a] + child_1.Size[a] - resize_limits[1] local bg_col = ImGui.GetColorU32(ImGuiCol.WindowBg) local changed changed, cur_size_0, cur_size_1 = ImGui.SplitterBehavior(bb, ImGui.GetID("##Splitter"), axis, cur_size_0, cur_size_1, min_size_0, min_size_1, g.WindowsBorderHoverPadding, WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER, bg_col) if changed then if #touching_nodes[0] > 0 and #touching_nodes[1] > 0 then child_0.Size[a] = cur_size_0; child_0.SizeRef[a] = cur_size_0 child_1.Pos[a] = child_1.Pos[a] - (cur_size_1 - child_1.Size[a]) child_1.Size[a] = cur_size_1; child_1.SizeRef[a] = cur_size_1 for side_n = 0, 1 do for _, touching_node in ipairs(touching_nodes[side_n]) do while touching_node.ParentNode ~= node do if touching_node.ParentNode.SplitAxis == axis then local node_to_preserve = touching_node.ParentNode.ChildNodes[side_n] node_to_preserve.WantLockSizeOnce = true end touching_node = touching_node.ParentNode end end end ImGui.DockNodeTreeUpdatePosSize(child_0, child_0.Pos, child_0.Size) ImGui.DockNodeTreeUpdatePosSize(child_1, child_1.Pos, child_1.Size) ImGui.MarkIniSettingsDirty() end end ImGui.PopID() end end if child_0.IsVisible then ImGui.DockNodeTreeUpdateSplitter(child_0) end if child_1.IsVisible then ImGui.DockNodeTreeUpdateSplitter(child_1) end end function ImGui.DockNodeTreeFindFallbackLeafNode(node) if node:IsLeafNode() then return node end local leaf_node = ImGui.DockNodeTreeFindFallbackLeafNode(node.ChildNodes[0]) if leaf_node then return leaf_node end leaf_node = ImGui.DockNodeTreeFindFallbackLeafNode(node.ChildNodes[1]) if leaf_node then return leaf_node end return nil end function ImGui.DockNodeTreeFindVisibleNodeByPos(node, pos) if not node.IsVisible then return nil end local r = ImRect(node.Pos, node.Pos + node.Size) if not r:Contains(pos) then return nil end if node:IsLeafNode() then return node end local hovered_node = ImGui.DockNodeTreeFindVisibleNodeByPos(node.ChildNodes[0], pos) if hovered_node then return hovered_node end hovered_node = ImGui.DockNodeTreeFindVisibleNodeByPos(node.ChildNodes[1], pos) if hovered_node then return hovered_node end return node end --------------------------------------------------------------------------------------- -- Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport) --------------------------------------------------------------------------------------- function ImGui.SetWindowDock(window, dock_id, cond) if cond and cond ~= 0 and bit32.band(window.SetWindowDockAllowFlags, cond) == 0 then return end window.SetWindowDockAllowFlags = bit32.band(window.SetWindowDockAllowFlags, bit32.bnot(bit32.bor(ImGuiCond.Once, ImGuiCond.FirstUseEver, ImGuiCond.Appearing))) if window.DockId == dock_id then return end local g = ImGui.GetCurrentContext() local new_node = ImGui.DockContextFindNodeByID(g, dock_id) if new_node and new_node:IsSplitNode() then new_node = ImGui.DockNodeGetRootNode(new_node) if new_node.CentralNode then IM_ASSERT(new_node.CentralNode:IsCentralNode()) dock_id = new_node.CentralNode.ID else dock_id = new_node.LastFocusedNodeId end end if window.DockId == dock_id then return end if window.DockNode then ImGui.DockNodeRemoveWindow(window.DockNode, window, 0) end window.DockId = dock_id end function ImGui.DockSpace(dockspace_id, size_arg, flags, window_class) if size_arg == nil then size_arg = ImVec2(0, 0) end if flags == nil then flags = 0 end local g = ImGui.GetCurrentContext() local window = ImGui.GetCurrentWindowRead() if bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.DockingEnable) == 0 then return 0 end if window.SkipItems then flags = bit32.bor(flags, ImGuiDockNodeFlags.KeepAliveOnly) end if bit32.band(flags, ImGuiDockNodeFlags.KeepAliveOnly) == 0 then window = ImGui.GetCurrentWindow() end IM_ASSERT(bit32.band(flags, ImGuiDockNodeFlags.DockSpace) == 0) IM_ASSERT(bit32.band(flags, ImGuiDockNodeFlags.CentralNode) == 0) IM_ASSERT(dockspace_id ~= 0) local node = ImGui.DockContextFindNodeByID(g, dockspace_id) if node == nil then node = ImGui.DockContextAddNode(g, dockspace_id) node:SetLocalFlags(ImGuiDockNodeFlags.CentralNode) end node.SharedFlags = flags node.WindowClass = window_class or ImGuiWindowClass() if node.LastFrameActive == g.FrameCount and bit32.band(flags, ImGuiDockNodeFlags.KeepAliveOnly) == 0 then IM_ASSERT(node:IsDockSpace() == false, "Cannot call DockSpace() twice a frame with the same ID") node:SetLocalFlags(bit32.bor(node.LocalFlags, ImGuiDockNodeFlags.DockSpace)) return dockspace_id end node:SetLocalFlags(bit32.bor(node.LocalFlags, ImGuiDockNodeFlags.DockSpace)) if bit32.band(flags, ImGuiDockNodeFlags.KeepAliveOnly) ~= 0 then node.LastFrameAlive = g.FrameCount return dockspace_id end local content_avail = ImGui.GetContentRegionAvail() local size = ImVec2(ImTrunc(size_arg.x), ImTrunc(size_arg.y)) if size.x <= 0.0 then size.x = ImMax(content_avail.x + size.x, 4.0) end if size.y <= 0.0 then size.y = ImMax(content_avail.y + size.y, 4.0) end IM_ASSERT(size.x > 0.0 and size.y > 0.0) node.Pos = ImVec2(window.DC.CursorPos.x, window.DC.CursorPos.y) node.Size = ImVec2(size.x, size.y) node.SizeRef = ImVec2(size.x, size.y) ImGui.SetNextWindowPos(node.Pos) ImGui.SetNextWindowSize(node.Size) g.NextWindowData.PosUndock = false local window_flags = bit32.bor(ImGuiWindowFlags.ChildWindow, ImGuiWindowFlags.DockNodeHost) window_flags = bit32.bor(window_flags, ImGuiWindowFlags.NoSavedSettings, ImGuiWindowFlags.NoResize, ImGuiWindowFlags.NoCollapse, ImGuiWindowFlags.NoTitleBar) window_flags = bit32.bor(window_flags, ImGuiWindowFlags.NoScrollbar, ImGuiWindowFlags.NoScrollWithMouse) window_flags = bit32.bor(window_flags, ImGuiWindowFlags.NoBackground) local title = string.format("%s/DockSpace_%08X", window.Name, dockspace_id) ImGui.PushStyleVar(ImGuiStyleVar.ChildBorderSize, 0.0) ImGui.Begin(title, nil, window_flags) ImGui.PopStyleVar() local host_window = g.CurrentWindow DockNodeSetupHostWindow(node, host_window) host_window.ChildId = window:GetID(title) node.OnlyNodeWithWindows = nil IM_ASSERT(node:IsRootNode()) if node:IsLeafNode() and not node:IsCentralNode() then node:SetLocalFlags(bit32.bor(node.LocalFlags, ImGuiDockNodeFlags.CentralNode)) end ImGui.DockNodeUpdate(node) ImGui.End() local bb = ImRect(node.Pos, node.Pos + size) ImGui.ItemSize(size) ImGui.ItemAdd(bb, dockspace_id, nil, ImGuiItemFlags.NoNav) if bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredRect) ~= 0 and ImGui.IsWindowChildOf(g.HoveredWindow, host_window, false, true) then g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredWindow) end return dockspace_id end function ImGui.DockSpaceOverViewport(dockspace_id, viewport, dockspace_flags, window_class) if dockspace_id == nil then dockspace_id = 0 end if dockspace_flags == nil then dockspace_flags = 0 end if viewport == nil then viewport = ImGui.GetMainViewport() end ImGui.SetNextWindowPos(viewport.WorkPos) ImGui.SetNextWindowSize(viewport.WorkSize) ImGui.SetNextWindowViewport(viewport.ID) local host_window_flags = 0 host_window_flags = bit32.bor(host_window_flags, ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoCollapse, ImGuiWindowFlags.NoResize, ImGuiWindowFlags.NoMove, ImGuiWindowFlags.NoDocking) host_window_flags = bit32.bor(host_window_flags, ImGuiWindowFlags.NoBringToFrontOnFocus, ImGuiWindowFlags.NoNavFocus) if bit32.band(dockspace_flags, ImGuiDockNodeFlags.PassthruCentralNode) ~= 0 then host_window_flags = bit32.bor(host_window_flags, ImGuiWindowFlags.NoBackground) end if bit32.band(dockspace_flags, ImGuiDockNodeFlags.KeepAliveOnly) ~= 0 then host_window_flags = bit32.bor(host_window_flags, ImGuiWindowFlags.NoMouseInputs) end local label = string.format("WindowOverViewport_%08X", viewport.ID) ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 0.0) ImGui.PushStyleVar(ImGuiStyleVar.WindowBorderSize, 0.0) ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, ImVec2(0.0, 0.0)) ImGui.Begin(label, nil, host_window_flags) ImGui.PopStyleVar(3) if dockspace_id == 0 then dockspace_id = ImGui.GetID("DockSpace") end ImGui.DockSpace(dockspace_id, ImVec2(0.0, 0.0), dockspace_flags, window_class) ImGui.End() return dockspace_id end --------------------------------------------------------------------------------------- -- Docking: Builder Functions --------------------------------------------------------------------------------------- function ImGui.DockBuilderDockWindow(window_name, node_id) local window_id = ImHashStr(window_name) local window = ImGui.FindWindowByID(window_id) if window then local prev_node_id = window.DockId ImGui.SetWindowDock(window, node_id, ImGuiCond.Always) if window.DockId ~= prev_node_id then window.DockOrder = -1 end else local settings = ImGui.FindWindowSettingsByID(window_id) if settings == nil then settings = ImGui.CreateNewWindowSettings(window_name) end if settings.DockId ~= node_id then settings.DockOrder = -1 end settings.DockId = node_id end end function ImGui.DockBuilderGetNode(node_id) return ImGui.DockContextFindNodeByID(ImGui.GetCurrentContext(), node_id) end function ImGui.DockBuilderGetCentralNode(node_id) local node = ImGui.DockBuilderGetNode(node_id) if not node then return nil end return ImGui.DockNodeGetRootNode(node).CentralNode end function ImGui.DockBuilderSetNodePos(node_id, pos) local node = ImGui.DockContextFindNodeByID(ImGui.GetCurrentContext(), node_id) if node == nil then return end node.Pos = ImVec2(pos.x, pos.y) node.AuthorityForPos = ImGuiDataAuthority.DockNode end function ImGui.DockBuilderSetNodeSize(node_id, size) local node = ImGui.DockContextFindNodeByID(ImGui.GetCurrentContext(), node_id) if node == nil then return end IM_ASSERT(size.x > 0.0 and size.y > 0.0) node.Size = ImVec2(size.x, size.y) node.SizeRef = ImVec2(size.x, size.y) node.AuthorityForSize = ImGuiDataAuthority.DockNode end function ImGui.DockBuilderAddNode(node_id, flags) if node_id == nil then node_id = 0 end if flags == nil then flags = 0 end local g = ImGui.GetCurrentContext() if node_id ~= 0 then ImGui.DockBuilderRemoveNode(node_id) end local node if bit32.band(flags, ImGuiDockNodeFlags.DockSpace) ~= 0 then ImGui.DockSpace(node_id, ImVec2(0, 0), bit32.bor(bit32.band(flags, bit32.bnot(ImGuiDockNodeFlags.DockSpace)), ImGuiDockNodeFlags.KeepAliveOnly)) node = ImGui.DockContextFindNodeByID(g, node_id) else node = ImGui.DockContextAddNode(g, node_id) node:SetLocalFlags(flags) end node.LastFrameAlive = g.FrameCount return node.ID end function ImGui.DockBuilderRemoveNode(node_id) local g = ImGui.GetCurrentContext() local node = ImGui.DockContextFindNodeByID(g, node_id) if node == nil then return end ImGui.DockBuilderRemoveNodeDockedWindows(node_id, true) ImGui.DockBuilderRemoveNodeChildNodes(node_id) node = ImGui.DockContextFindNodeByID(g, node_id) if node == nil then return end if node:IsCentralNode() and node.ParentNode then node.ParentNode:SetLocalFlags(bit32.bor(node.ParentNode.LocalFlags, ImGuiDockNodeFlags.CentralNode)) end ImGui.DockContextRemoveNode(g, node, true) end function ImGui.DockBuilderRemoveNodeChildNodes(root_id) local g = ImGui.GetCurrentContext() local dc = g.DockContext local root_node = (root_id ~= 0) and ImGui.DockContextFindNodeByID(g, root_id) or nil if root_id ~= 0 and root_node == nil then return end local has_central_node = false local backup_root_node_authority_for_pos = root_node and root_node.AuthorityForPos or ImGuiDataAuthority.Auto local backup_root_node_authority_for_size = root_node and root_node.AuthorityForSize or ImGuiDataAuthority.Auto local nodes_to_remove = {} for _, id in ipairs(DockContextNodesSorted(g)) do local node = dc.Nodes[id] if node then local want_removal = (root_id == 0) or (node.ID ~= root_id and ImGui.DockNodeGetRootNode(node).ID == root_id) if want_removal then if node:IsCentralNode() then has_central_node = true end if root_id ~= 0 then ImGui.DockContextQueueNotifyRemovedNode(g, node) end if root_node then ImGui.DockNodeMoveWindows(root_node, node) DockSettingsRenameNodeReferences(node.ID, root_node.ID) end table.insert(nodes_to_remove, node) end end end if root_node then root_node.AuthorityForPos = backup_root_node_authority_for_pos root_node.AuthorityForSize = backup_root_node_authority_for_size end for _, settings in g.SettingsWindows:iter() do local window_settings_dock_id = settings.DockId if window_settings_dock_id and window_settings_dock_id ~= 0 then for _, n in ipairs(nodes_to_remove) do if n.ID == window_settings_dock_id then settings.DockId = root_id break end end end end if #nodes_to_remove > 1 then local depth = {} for _, n in ipairs(nodes_to_remove) do depth[n] = ImGui.DockNodeGetDepth(n) end table.sort(nodes_to_remove, function(a, b) return depth[a] > depth[b] end) end for _, n in ipairs(nodes_to_remove) do ImGui.DockContextRemoveNode(g, n, false) end if root_id == 0 then dc.Nodes = {} dc.Requests:clear() elseif has_central_node then root_node.CentralNode = root_node root_node:SetLocalFlags(bit32.bor(root_node.LocalFlags, ImGuiDockNodeFlags.CentralNode)) end end function ImGui.DockBuilderRemoveNodeDockedWindows(root_id, clear_settings_refs) if clear_settings_refs == nil then clear_settings_refs = true end local g = ImGui.GetCurrentContext() if clear_settings_refs then for _, settings in g.SettingsWindows:iter() do local want_removal = (root_id == 0) or (settings.DockId == root_id) if not want_removal and settings.DockId ~= 0 then local node = ImGui.DockContextFindNodeByID(g, settings.DockId) if node and ImGui.DockNodeGetRootNode(node).ID == root_id then want_removal = true end end if want_removal then settings.DockId = 0 end end end for n = 1, g.Windows.Size do local window = g.Windows.Data[n] local want_removal = (root_id == 0) or (window.DockNode and ImGui.DockNodeGetRootNode(window.DockNode).ID == root_id) or (window.DockNodeAsHost and window.DockNodeAsHost.ID == root_id) if want_removal then local backup_dock_id = window.DockId ImGui.DockContextProcessUndockWindow(g, window, clear_settings_refs) if not clear_settings_refs then IM_ASSERT(window.DockId == backup_dock_id) end end end end --- @return ImGuiID id_at_dir, ImGuiID id_at_opposite_dir function ImGui.DockBuilderSplitNode(id, split_dir, size_ratio_for_node_at_dir) local g = ImGui.GetCurrentContext() IM_ASSERT(split_dir ~= ImGuiDir.None) local node = ImGui.DockContextFindNodeByID(g, id) if node == nil then IM_ASSERT(node ~= nil) return 0, 0 end local req = ImGuiDockRequest() req.Type = ImGuiDockRequestType.Split req.DockTargetWindow = nil req.DockTargetNode = node req.DockPayload = nil req.DockSplitDir = split_dir req.DockSplitRatio = ImSaturate((split_dir == ImGuiDir.Left or split_dir == ImGuiDir.Up) and size_ratio_for_node_at_dir or (1.0 - size_ratio_for_node_at_dir)) req.DockSplitOuter = false ImGui.DockContextProcessDock(g, req) local first = (split_dir == ImGuiDir.Left or split_dir == ImGuiDir.Up) local id_at_dir = node.ChildNodes[first and 0 or 1].ID local id_at_opposite_dir = node.ChildNodes[first and 1 or 0].ID return id_at_dir, id_at_opposite_dir end local function DockBuilderCopyNodeRec(src_node, dst_node_id_if_known, out_node_remap_pairs) local g = ImGui.GetCurrentContext() local dst_node = ImGui.DockContextAddNode(g, dst_node_id_if_known) dst_node.SharedFlags = src_node.SharedFlags dst_node.LocalFlags = src_node.LocalFlags dst_node.LocalFlagsInWindows = ImGuiDockNodeFlags.None dst_node.Pos = ImVec2(src_node.Pos.x, src_node.Pos.y) dst_node.Size = ImVec2(src_node.Size.x, src_node.Size.y) dst_node.SizeRef = ImVec2(src_node.SizeRef.x, src_node.SizeRef.y) dst_node.SplitAxis = src_node.SplitAxis dst_node:UpdateMergedFlags() table.insert(out_node_remap_pairs, src_node.ID) table.insert(out_node_remap_pairs, dst_node.ID) for child_n = 0, 1 do if src_node.ChildNodes[child_n] then dst_node.ChildNodes[child_n] = DockBuilderCopyNodeRec(src_node.ChildNodes[child_n], 0, out_node_remap_pairs) dst_node.ChildNodes[child_n].ParentNode = dst_node end end return dst_node end --- @param out_node_remap_pairs table # 1-based array, cleared and filled with (src_id, dst_id) pairs function ImGui.DockBuilderCopyNode(src_node_id, dst_node_id, out_node_remap_pairs) local g = ImGui.GetCurrentContext() IM_ASSERT(src_node_id ~= 0) IM_ASSERT(dst_node_id ~= 0) IM_ASSERT(out_node_remap_pairs ~= nil) ImGui.DockBuilderRemoveNode(dst_node_id) local src_node = ImGui.DockContextFindNodeByID(g, src_node_id) IM_ASSERT(src_node ~= nil) for k in pairs(out_node_remap_pairs) do out_node_remap_pairs[k] = nil end DockBuilderCopyNodeRec(src_node, dst_node_id, out_node_remap_pairs) IM_ASSERT((#out_node_remap_pairs % 2) == 0) end function ImGui.DockBuilderCopyWindowSettings(src_name, dst_name) local src_window = ImGui.FindWindowByName(src_name) if src_window == nil then return end local dst_window = ImGui.FindWindowByName(dst_name) if dst_window then ImVec2_Copy(dst_window.Pos, src_window.Pos) ImVec2_Copy(dst_window.Size, src_window.Size) ImVec2_Copy(dst_window.SizeFull, src_window.SizeFull) dst_window.Collapsed = src_window.Collapsed else local dst_settings = ImGui.FindWindowSettingsByID(ImHashStr(dst_name)) if not dst_settings then dst_settings = ImGui.CreateNewWindowSettings(dst_name) end local wx, wy = ImTrunc(src_window.Pos.x), ImTrunc(src_window.Pos.y) if src_window.ViewportId ~= 0 and src_window.ViewportId ~= 0x11111111 then ImVec2_CopyV(dst_settings.ViewportPos, wx, wy) dst_settings.ViewportId = src_window.ViewportId ImVec2_CopyV(dst_settings.Pos, 0, 0) else ImVec2_CopyV(dst_settings.Pos, wx, wy) end ImVec2_CopyV(dst_settings.Size, ImTrunc(src_window.SizeFull.x), ImTrunc(src_window.SizeFull.y)) dst_settings.Collapsed = src_window.Collapsed end end --- @param in_window_remap_pairs table # 1-based array of window names: src0, dst0, src1, dst1, ... function ImGui.DockBuilderCopyDockSpace(src_dockspace_id, dst_dockspace_id, in_window_remap_pairs) IM_ASSERT(src_dockspace_id ~= 0) IM_ASSERT(dst_dockspace_id ~= 0) IM_ASSERT(in_window_remap_pairs ~= nil) IM_ASSERT((#in_window_remap_pairs % 2) == 0) local node_remap_pairs = {} ImGui.DockBuilderCopyNode(src_dockspace_id, dst_dockspace_id, node_remap_pairs) local src_windows = {} for remap_window_n = 1, #in_window_remap_pairs, 2 do local src_window_name = in_window_remap_pairs[remap_window_n] local dst_window_name = in_window_remap_pairs[remap_window_n + 1] local src_window_id = ImHashStr(src_window_name) src_windows[src_window_id] = true local src_dock_id = 0 local src_window = ImGui.FindWindowByID(src_window_id) if src_window then src_dock_id = src_window.DockId else local src_window_settings = ImGui.FindWindowSettingsByID(src_window_id) if src_window_settings then src_dock_id = src_window_settings.DockId end end local dst_dock_id = 0 for dock_remap_n = 1, #node_remap_pairs, 2 do if node_remap_pairs[dock_remap_n] == src_dock_id then dst_dock_id = node_remap_pairs[dock_remap_n + 1] break end end if dst_dock_id ~= 0 then ImGui.DockBuilderDockWindow(dst_window_name, dst_dock_id) else ImGui.DockBuilderCopyWindowSettings(src_window_name, dst_window_name) end end local dock_remaining_windows = {} for dock_remap_n = 1, #node_remap_pairs, 2 do local src_dock_id = node_remap_pairs[dock_remap_n] if src_dock_id ~= 0 then local dst_dock_id = node_remap_pairs[dock_remap_n + 1] local node = ImGui.DockBuilderGetNode(src_dock_id) for _, window in node.Windows:iter() do if not src_windows[window.ID] then table.insert(dock_remaining_windows, { Window = window, DockId = dst_dock_id }) end end end end for _, task in ipairs(dock_remaining_windows) do ImGui.DockBuilderDockWindow(task.Window.Name, task.DockId) end end function ImGui.DockBuilderFinish(root_id) ImGui.DockContextBuildAddWindowsToNodes(ImGui.GetCurrentContext(), root_id) end --------------------------------------------------------------------------------------- -- Docking: Begin/End Support Functions (called from Begin/End) --------------------------------------------------------------------------------------- function ImGui.GetWindowAlwaysWantOwnTabBar(window) local g = ImGui.GetCurrentContext() if g.IO.ConfigDockingAlwaysTabBar or window.WindowClass.DockingAlwaysTabBar then if bit32.band(window.Flags, bit32.bor(ImGuiWindowFlags.ChildWindow, ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoDocking)) == 0 then if not window.IsFallbackWindow then return true end end end return false end function ImGui.DockContextBindNodeToWindow(ctx, window) local g = ctx local node = ImGui.DockContextFindNodeByID(ctx, window.DockId) IM_ASSERT(window.DockNode == nil) if node and node:IsSplitNode() then ImGui.DockContextProcessUndockWindow(ctx, window) return nil end if node == nil then node = ImGui.DockContextAddNode(ctx, window.DockId) node.AuthorityForPos = ImGuiDataAuthority.Window node.AuthorityForSize = ImGuiDataAuthority.Window node.AuthorityForViewport = ImGuiDataAuthority.Window node.LastFrameAlive = g.FrameCount end if not node.IsVisible then local ancestor_node = node while not ancestor_node.IsVisible and ancestor_node.ParentNode do ancestor_node = ancestor_node.ParentNode end IM_ASSERT(ancestor_node.Size.x > 0.0 and ancestor_node.Size.y > 0.0) ImGui.DockNodeUpdateHasCentralNodeChild(ImGui.DockNodeGetRootNode(ancestor_node)) ImGui.DockNodeTreeUpdatePosSize(ancestor_node, ancestor_node.Pos, ancestor_node.Size, node) end local node_was_visible = node.IsVisible ImGui.DockNodeAddWindow(node, window, true) node.IsVisible = node_was_visible IM_ASSERT(node == window.DockNode) return node end local function StoreDockStyleForWindow(window) local g = ImGui.GetCurrentContext() for color_n = 0, ImGuiWindowDockStyleCol.COUNT - 1 do window.DockStyle.Colors[color_n] = ImGui.ColorConvertFloat4ToU32(g.Style.Colors[GWindowDockStyleColors[color_n]]) end end ImGui.StoreDockStyleForWindow = StoreDockStyleForWindow --- @return bool? p_open function ImGui.BeginDocked(window, p_open) local g = ImGui.GetCurrentContext() if window.IsFallbackWindow and not window.WasActive then DockNodeHideWindowDuringHostWindowCreation(window) return p_open end local auto_dock_node = ImGui.GetWindowAlwaysWantOwnTabBar(window) if auto_dock_node then if window.DockId == 0 then IM_ASSERT(window.DockNode == nil) window.DockId = ImGui.DockContextGenNodeID(g) end else local want_undock = false want_undock = want_undock or bit32.band(window.Flags, ImGuiWindowFlags.NoDocking) ~= 0 want_undock = want_undock or (bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasPos) ~= 0 and bit32.band(window.SetWindowPosAllowFlags, g.NextWindowData.PosCond) ~= 0 and g.NextWindowData.PosUndock) if want_undock then ImGui.DockContextProcessUndockWindow(g, window) return p_open end end local node = window.DockNode if node ~= nil then IM_ASSERT(window.DockId == node.ID) end if window.DockId ~= 0 and node == nil then node = ImGui.DockContextBindNodeToWindow(g, window) if node == nil then return p_open end end if node.LastFrameAlive < g.FrameCount then local root_node = ImGui.DockNodeGetRootNode(node) if root_node.LastFrameAlive < g.FrameCount then ImGui.DockContextProcessUndockWindow(g, window) else window.DockIsActive = true end return p_open end StoreDockStyleForWindow(window) if node.HostWindow == nil then if node.State == ImGuiDockNodeState.HostWindowHiddenBecauseWindowsAreResizing then window.DockIsActive = true end if node.Windows.Size > 1 and window.Appearing then DockNodeHideWindowDuringHostWindowCreation(window) end return p_open end IM_ASSERT(node.HostWindow) IM_ASSERT(node:IsLeafNode()) IM_ASSERT(node.Size.x >= 0.0 and node.Size.y >= 0.0) node.State = ImGuiDockNodeState.HostWindowVisible if bit32.band(node.MergedFlags, ImGuiDockNodeFlags.KeepAliveOnly) == 0 and window.BeginOrderWithinContext < node.HostWindow.BeginOrderWithinContext then ImGui.DockContextProcessUndockWindow(g, window) return p_open end ImGui.SetNextWindowPos(node.Pos) ImGui.SetNextWindowSize(node.Size) g.NextWindowData.PosUndock = false window.DockIsActive = true window.DockNodeIsVisible = true window.DockTabIsVisible = false if bit32.band(node.MergedFlags, ImGuiDockNodeFlags.KeepAliveOnly) ~= 0 then return p_open end if node.VisibleWindow == window then window.DockTabIsVisible = true end IM_ASSERT(bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) == 0) window.Flags = bit32.bor(window.Flags, ImGuiWindowFlags.ChildWindow, ImGuiWindowFlags.NoResize) window.ChildFlags = bit32.bor(window.ChildFlags, ImGuiChildFlags.AlwaysUseWindowPadding) if node:IsHiddenTabBar() or node:IsNoTabBar() then window.Flags = bit32.bor(window.Flags, ImGuiWindowFlags.NoTitleBar) else window.Flags = bit32.band(window.Flags, bit32.bnot(ImGuiWindowFlags.NoTitleBar)) end if node.TabBar and window.WasActive then window.DockOrder = ImGui.DockNodeGetTabOrder(window) end if (node.WantCloseAll or node.WantCloseTabId == window.TabId) and p_open ~= nil then p_open = false end local parent_window = window.DockNode.HostWindow window.ChildId = parent_window:GetID(window.Name) return p_open end function ImGui.BeginDockableDragDropSource(window) local g = ImGui.GetCurrentContext() IM_ASSERT(g.ActiveId == window.MoveId) IM_ASSERT(g.MovingWindow == window) IM_ASSERT(g.CurrentWindow == window) if g.IO.ConfigDockingWithShift ~= g.IO.KeyShift then if g.IO.ConfigDockingWithShift and g.MouseStationaryTimer >= 1.0 and g.ActiveId >= 1 then ImGui.SetTooltip("%s", ImGui.LocalizeGetMsg(ImGuiLocKey.DockingHoldShiftToDock)) end return end g.LastItemData.ID = window.MoveId window = window.RootWindowDockTree IM_ASSERT(bit32.band(window.Flags, ImGuiWindowFlags.NoDocking) == 0) local is_drag_docking = g.IO.ConfigDockingWithShift or ImRect(0, 0, window.SizeFull.x, ImGui.GetFrameHeight()):Contains(g.ActiveIdClickOffset) local drag_drop_flags = bit32.bor(ImGuiDragDropFlags.SourceNoPreviewTooltip, ImGuiDragDropFlags.SourceNoHoldToOpenOthers, ImGuiDragDropFlags.PayloadAutoExpire, ImGuiDragDropFlags.PayloadNoCrossContext, ImGuiDragDropFlags.PayloadNoCrossProcess) if is_drag_docking and ImGui.BeginDragDropSource(drag_drop_flags) then ImGui.SetDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, window) ImGui.EndDragDropSource() StoreDockStyleForWindow(window) end end function ImGui.BeginDockableDragDropTarget(window) local g = ImGui.GetCurrentContext() IM_ASSERT(bit32.band(window.Flags, ImGuiWindowFlags.NoDocking) == 0) if not g.DragDropActive then return end if not ImGui.BeginDragDropTargetCustom(window:Rect(), window.ID) then return end local payload = g.DragDropPayload if not ImGuiPayload_IsDataType(payload, IMGUI_PAYLOAD_TYPE_WINDOW) or not ImGui.DockNodeIsDropAllowed(window, payload.Data) then ImGui.EndDragDropTarget() return end local payload_window = payload.Data if ImGui.AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, bit32.bor(ImGuiDragDropFlags.AcceptBeforeDelivery, ImGuiDragDropFlags.AcceptNoDrawDefaultRect)) then local dock_into_floating_window = false local node = nil if window.DockNodeAsHost then node = ImGui.DockNodeTreeFindVisibleNodeByPos(window.DockNodeAsHost, g.IO.MousePos) if node and node:IsDockSpace() and node:IsRootNode() then node = (node.CentralNode and node:IsLeafNode()) and node.CentralNode or ImGui.DockNodeTreeFindFallbackLeafNode(node) end else if window.DockNode then node = window.DockNode else dock_into_floating_window = true end end local explicit_target_rect if node and node.TabBar and not node:IsHiddenTabBar() and not node:IsNoTabBar() then explicit_target_rect = node.TabBar.BarRect else explicit_target_rect = ImRect(window.Pos, window.Pos + ImVec2(window.Size.x, ImGui.GetFrameHeight())) end local is_explicit_target = g.IO.ConfigDockingWithShift or ImGui.IsMouseHoveringRect(explicit_target_rect.Min, explicit_target_rect.Max) local do_preview = ImGuiPayload_IsPreview(payload) or ImGuiPayload_IsDelivery(payload) if do_preview and (node ~= nil or dock_into_floating_window) then local split_inner = ImGuiDockPreviewData() local split_outer = ImGuiDockPreviewData() local split_data = split_inner if node and (node.ParentNode or node:IsCentralNode() or not node:IsLeafNode()) then local root_node = ImGui.DockNodeGetRootNode(node) if root_node then ImGui.DockNodePreviewDockSetup(window, root_node, payload_window, nil, split_outer, is_explicit_target, true) if split_outer.IsSplitDirExplicit then split_data = split_outer end end end if not node or node:IsLeafNode() then ImGui.DockNodePreviewDockSetup(window, node, payload_window, nil, split_inner, is_explicit_target, false) end if split_data == split_outer then split_inner.IsDropAllowed = false end ImGui.DockNodePreviewDockRender(window, node, payload_window, split_inner) ImGui.DockNodePreviewDockRender(window, node, payload_window, split_outer) if split_data.IsDropAllowed and ImGuiPayload_IsDelivery(payload) then ImGui.DockContextQueueDock(g, window, split_data.SplitNode, payload_window, split_data.SplitDir, split_data.SplitRatio, split_data == split_outer) end end end ImGui.EndDragDropTarget() end --------------------------------------------------------------------------------------- -- Docking: Settings --------------------------------------------------------------------------------------- DockSettingsRenameNodeReferences = function(old_node_id, new_node_id) local g = ImGui.GetCurrentContext() for _, window in g.Windows:iter() do if window.DockId == old_node_id and window.DockNode == nil then window.DockId = new_node_id end end for _, settings in g.SettingsWindows:iter() do if settings.DockId == old_node_id then settings.DockId = new_node_id end end end --- @param node_ids table # 1-based array DockSettingsRemoveNodeReferences = function(node_ids, node_ids_count) local g = ImGui.GetCurrentContext() local found = 0 for _, settings in g.SettingsWindows:iter() do for node_n = 1, node_ids_count do if settings.DockId == node_ids[node_n] then settings.DockId = 0 settings.DockOrder = -1 found = found + 1 if found < node_ids_count then break end return end end end end DockSettingsFindNodeSettings = function(ctx, id) for _, s in ctx.DockContext.NodesSettings:iter() do if s.ID == id then return s end end return nil end DockSettingsHandler_ClearAll = function(ctx, handler) local dc = ctx.DockContext dc.NodesSettings:clear() ImGui.DockContextClearNodes(ctx, 0, true) end DockSettingsHandler_ApplyAll = function(ctx, handler) local dc = ctx.DockContext if ctx.Windows.Size == 0 then ImGui.DockContextPruneUnusedSettingsNodes(ctx) end ImGui.DockContextBuildNodesFromSettings(ctx, dc.NodesSettings.Data or {}, dc.NodesSettings.Size) ImGui.DockContextBuildAddWindowsToNodes(ctx, 0) end DockSettingsHandler_ReadOpen = function(ctx, handler, name) if name ~= "Data" then return nil end return true end DockSettingsHandler_ReadLine = function(ctx, handler, entry, line) local node = ImGuiDockNodeSettings() line = line:gsub("^[ \t]+", "") if line:sub(1, 8) == "DockNode" then line = line:sub(9) elseif line:sub(1, 9) == "DockSpace" then line = line:sub(10) node.Flags = bit32.bor(node.Flags, ImGuiDockNodeFlags.DockSpace) else return end line = line:gsub("^[ \t]+", "") local function take(pattern) local caps = { line:match("^" .. pattern .. "()") } if #caps == 0 then return nil end line = line:sub(caps[#caps]) caps[#caps] = nil return table.unpack(caps) end local v = take("ID=0x(%x+)") if not v then return end node.ID = tonumber(v, 16) v = take(" Parent=0x(%x+)") if v then node.ParentNodeId = tonumber(v, 16); if node.ParentNodeId == 0 then return end end v = take(" Window=0x(%x+)") if v then node.ParentWindowId = tonumber(v, 16); if node.ParentWindowId == 0 then return end end if node.ParentNodeId == 0 then local x, y = take(" Pos=(%-?%d+),(%-?%d+)") if not x then return end node.Pos = ImVec2(tonumber(x), tonumber(y)) x, y = take(" Size=(%-?%d+),(%-?%d+)") if not x then return end node.Size = ImVec2(tonumber(x), tonumber(y)) else local x, y = take(" SizeRef=(%-?%d+),(%-?%d+)") if x then node.SizeRef = ImVec2(tonumber(x), tonumber(y)) end end local c = take(" Split=(%a)") if c then if c == "X" then node.SplitAxis = ImGuiAxis.X elseif c == "Y" then node.SplitAxis = ImGuiAxis.Y end end local function flag(name, f) local x = take(" " .. name .. "=(%-?%d+)") if x and tonumber(x) ~= 0 then node.Flags = bit32.bor(node.Flags, f) end end flag("NoResize", ImGuiDockNodeFlags.NoResize) flag("CentralNode", ImGuiDockNodeFlags.CentralNode) flag("NoTabBar", ImGuiDockNodeFlags.NoTabBar) flag("HiddenTabBar", ImGuiDockNodeFlags.HiddenTabBar) flag("NoWindowMenuButton", ImGuiDockNodeFlags.NoWindowMenuButton) flag("NoCloseButton", ImGuiDockNodeFlags.NoCloseButton) v = take(" Selected=0x(%x+)") if v then node.SelectedTabId = tonumber(v, 16) end if node.ParentNodeId ~= 0 then local parent_settings = DockSettingsFindNodeSettings(ctx, node.ParentNodeId) if parent_settings then node.Depth = parent_settings.Depth + 1 end end ctx.DockContext.NodesSettings:push_back(node) end local function DockSettingsHandler_DockNodeToSettings(dc, node, depth) local node_settings = ImGuiDockNodeSettings() node_settings.ID = node.ID node_settings.ParentNodeId = node.ParentNode and node.ParentNode.ID or 0 node_settings.ParentWindowId = (node:IsDockSpace() and node.HostWindow and node.HostWindow.ParentWindow) and node.HostWindow.ParentWindow.ID or 0 node_settings.SelectedTabId = node.SelectedTabId node_settings.SplitAxis = node:IsSplitNode() and node.SplitAxis or ImGuiAxis.None node_settings.Depth = depth node_settings.Flags = bit32.band(node.LocalFlags, ImGuiDockNodeFlags.SavedFlagsMask_) node_settings.Pos = ImVec2(ImTrunc(node.Pos.x), ImTrunc(node.Pos.y)) node_settings.Size = ImVec2(ImTrunc(node.Size.x), ImTrunc(node.Size.y)) node_settings.SizeRef = ImVec2(ImTrunc(node.SizeRef.x), ImTrunc(node.SizeRef.y)) dc.NodesSettings:push_back(node_settings) if node.ChildNodes[0] then DockSettingsHandler_DockNodeToSettings(dc, node.ChildNodes[0], depth + 1) end if node.ChildNodes[1] then DockSettingsHandler_DockNodeToSettings(dc, node.ChildNodes[1], depth + 1) end end DockSettingsHandler_WriteAll = function(ctx, handler, buf) local g = ctx local dc = ctx.DockContext if bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.DockingEnable) == 0 then return end dc.NodesSettings:resize(0) for _, id in ipairs(DockContextNodesSorted(ctx)) do local node = dc.Nodes[id] if node and node:IsRootNode() then DockSettingsHandler_DockNodeToSettings(dc, node, 0) end end local max_depth = 0 for _, s in dc.NodesSettings:iter() do max_depth = ImMax(s.Depth, max_depth) end buf:appendf("[%s][Data]\n", handler.TypeName) for _, s in dc.NodesSettings:iter() do buf:append(string.rep(" ", s.Depth * 2) .. ((bit32.band(s.Flags, ImGuiDockNodeFlags.DockSpace) ~= 0) and "DockSpace" or "DockNode ") .. string.rep(" ", (max_depth - s.Depth) * 2)) buf:appendf(" ID=0x%08X", s.ID) if s.ParentNodeId ~= 0 then buf:appendf(" Parent=0x%08X SizeRef=%d,%d", s.ParentNodeId, s.SizeRef.x, s.SizeRef.y) else if s.ParentWindowId ~= 0 then buf:appendf(" Window=0x%08X", s.ParentWindowId) end buf:appendf(" Pos=%d,%d Size=%d,%d", s.Pos.x, s.Pos.y, s.Size.x, s.Size.y) end if s.SplitAxis ~= ImGuiAxis.None then buf:appendf(" Split=%s", (s.SplitAxis == ImGuiAxis.X) and "X" or "Y") end if bit32.band(s.Flags, ImGuiDockNodeFlags.NoResize) ~= 0 then buf:append(" NoResize=1") end if bit32.band(s.Flags, ImGuiDockNodeFlags.CentralNode) ~= 0 then buf:append(" CentralNode=1") end if bit32.band(s.Flags, ImGuiDockNodeFlags.NoTabBar) ~= 0 then buf:append(" NoTabBar=1") end if bit32.band(s.Flags, ImGuiDockNodeFlags.HiddenTabBar) ~= 0 then buf:append(" HiddenTabBar=1") end if bit32.band(s.Flags, ImGuiDockNodeFlags.NoWindowMenuButton) ~= 0 then buf:append(" NoWindowMenuButton=1") end if bit32.band(s.Flags, ImGuiDockNodeFlags.NoCloseButton) ~= 0 then buf:append(" NoCloseButton=1") end if s.SelectedTabId ~= 0 then buf:appendf(" Selected=0x%08X", s.SelectedTabId) end buf:append("\n") end buf:append("\n") end --------------------------------------------------------------------------------------- -- Docking: helpers (imgui_internal.h inlines) + widgets used by docking --------------------------------------------------------------------------------------- function ImGui.DockNodeGetRootNode(node) while node.ParentNode do node = node.ParentNode end return node end function ImGui.DockNodeIsInHierarchyOf(node, parent) while node do if node == parent then return true end node = node.ParentNode end return false end function ImGui.DockNodeGetDepth(node) local depth = 0 while node.ParentNode do node = node.ParentNode; depth = depth + 1 end return depth end function ImGui.DockNodeGetWindowMenuButtonId(node) return ImHashStr("#COLLAPSE", nil, node.ID) end function ImGui.GetWindowDockID() return ImGui.GetCurrentContext().CurrentWindow.DockId end function ImGui.IsWindowDocked() return ImGui.GetCurrentContext().CurrentWindow.DockIsActive end function ImGui.SetNextWindowDockID(id, cond) local g = ImGui.GetCurrentContext() g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasDock) g.NextWindowData.DockCond = (cond and cond ~= 0) and cond or ImGuiCond.Always g.NextWindowData.DockId = id end function ImGui.StartMouseMovingWindowOrNode(window, node, undock) local g = ImGui.GetCurrentContext() local can_undock_node = false if undock and node ~= nil and node.VisibleWindow and bit32.band(node.VisibleWindow.Flags, ImGuiWindowFlags.NoMove) == 0 and bit32.band(node.MergedFlags, ImGuiDockNodeFlags.NoUndocking) == 0 then local root_node = ImGui.DockNodeGetRootNode(node) if root_node.OnlyNodeWithWindows ~= node or root_node.CentralNode ~= nil then can_undock_node = true end end local clicked = ImGui.IsMouseClicked(0) local dragging = ImGui.IsMouseDragging(0) if can_undock_node and dragging then ImGui.DockContextQueueUndockNode(g, node) elseif not can_undock_node and (clicked or dragging) and g.MovingWindow ~= window then ImGui.StartMouseMovingWindow(window) end end function ImGui.RenderArrowDockMenu(draw_list, p_min, sz, col) draw_list:AddRectFilled(p_min + ImVec2(sz * 0.20, sz * 0.15), p_min + ImVec2(sz * 0.80, sz * 0.30), col) ImGui.RenderArrowPointingAt(draw_list, p_min + ImVec2(sz * 0.50, sz * 0.85), ImVec2(sz * 0.30, sz * 0.40), ImGuiDir.Down, col) end -- Upstream CollapseButton (docking branch): replaces the port's simpler version function ImGui.CollapseButton(id, pos, dock_node) local g = ImGui.GetCurrentContext() local window = g.CurrentWindow local bb = ImRect(pos, pos + ImVec2(g.FontSize, g.FontSize)) local is_clipped = not ImGui.ItemAdd(bb, id) local pressed, hovered, held = ImGui.ButtonBehavior(bb, id, ImGuiButtonFlags.None) if is_clipped then return pressed end local bg_col = ImGui.GetColorU32((held and hovered) and ImGuiCol.ButtonActive or (hovered and ImGuiCol.ButtonHovered or ImGuiCol.Button)) local text_col = ImGui.GetColorU32(ImGuiCol.Text) if hovered or held then window.DrawList:AddRectFilled(bb.Min, bb.Max, bg_col) end ImGui.RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags and ImGuiNavRenderCursorFlags.Compact or nil) if dock_node then ImGui.RenderArrowDockMenu(window.DrawList, bb.Min, g.FontSize, text_col) else ImGui.RenderArrow(window.DrawList, bb.Min, text_col, window.Collapsed and ImGuiDir.Right or ImGuiDir.Down, 1.0) end if ImGui.IsItemActive() and ImGui.IsMouseDragging(0) then ImGui.StartMouseMovingWindowOrNode(window, dock_node, true) end return pressed end --- @return bool held, float size1, float size2 function ImGui.SplitterBehavior(bb, id, axis, size1, size2, min_size1, min_size2, hover_extend, hover_visibility_delay, bg_col) hover_extend = hover_extend or 0.0 hover_visibility_delay = hover_visibility_delay or 0.0 bg_col = bg_col or 0 local g = ImGui.GetCurrentContext() local window = g.CurrentWindow if not ImGui.ItemAdd(bb, id, nil, ImGuiItemFlags.NoNav) then return false, size1, size2 end local button_flags = bit32.bor(ImGuiButtonFlags.FlattenChildren, ImGuiButtonFlags.AllowOverlap) local bb_interact = ImRect(bb.Min, bb.Max) bb_interact:Expand(axis == ImGuiAxis.Y and ImVec2(0.0, hover_extend) or ImVec2(hover_extend, 0.0)) local _, hovered, held = ImGui.ButtonBehavior(bb_interact, id, button_flags) if hovered then g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredRect) end if held or (hovered and g.HoveredIdPreviousFrame == id and g.HoveredIdTimer >= hover_visibility_delay) then ImGui.SetMouseCursor(axis == ImGuiAxis.Y and ImGuiMouseCursor.ResizeNS or ImGuiMouseCursor.ResizeEW) end local bb_render = ImRect(bb.Min, bb.Max) if held then local d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min local mouse_delta = d[axis] local size_1_maximum_delta = ImMax(0.0, size1 - min_size1) local size_2_maximum_delta = ImMax(0.0, size2 - min_size2) if mouse_delta < -size_1_maximum_delta then mouse_delta = -size_1_maximum_delta end if mouse_delta > size_2_maximum_delta then mouse_delta = size_2_maximum_delta end if mouse_delta ~= 0.0 then size1 = ImMax(size1 + mouse_delta, min_size1) size2 = ImMax(size2 - mouse_delta, min_size2) bb_render:Translate(axis == ImGuiAxis.X and ImVec2(mouse_delta, 0.0) or ImVec2(0.0, mouse_delta)) ImGui.MarkItemEdited(id) end end if bit32.band(bg_col, IM_COL32_A_MASK) ~= 0 then window.DrawList:AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0) end local col = ImGui.GetColorU32(held and ImGuiCol.SeparatorActive or ((hovered and g.HoveredIdTimer >= hover_visibility_delay) and ImGuiCol.SeparatorHovered or ImGuiCol.Separator)) window.DrawList:AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0) return held, size1, size2 end end --[[ [SECTION] imgui_docking (was imgui_docking.lua) ]] local IMGUI_DEBUG_NAV_SCORING = false local IMGUI_DEBUG_NAV_RECTS = false local FONT_DEFAULT_SIZE_BASE = 20 local WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04 local WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 0.70 local TOOLTIP_DEFAULT_OFFSET_MOUSE = ImVec2(16, 10) -- Multiplied by g.Style.MouseCursorScale local TOOLTIP_DEFAULT_OFFSET_TOUCH = ImVec2(0, -20) -- Multiplied by g.Style.MouseCursorScale local TOOLTIP_DEFAULT_PIVOT_TOUCH = ImVec2(0.5, 1.0) -- Multiplied by g.Style.MouseCursorScale local IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111 local string = string ImFormatString = string.format -- TODO: an simplified version that operates on byte tables directly? --- @module "imstd_minstdio" ImStd.sscanf = IM_INCLUDE"imstd_minstdio.lua" local math = math local bit32 = bit32 --------------------------------------------------------------------------------------- -- [SECTION] MISC HELPERS/UTILITIES (File functions) --------------------------------------------------------------------------------------- --- This closes the _file. --- @param _file any # File object function IM_FILE_CLOSE(_file) end --- This returns the size of _file in bytes. --- @param _file any # File object --- @return int function IM_FILE_SIZE(_file) return -1 end --- This writes _str into _file. --- @param _file any # File object --- @param _str string function IM_FILE_WRITE(_file, _str) end --- This reads the specified _count of chars and returns them as a binary string. --- @param _file any # File object --- @param _count int --- @return string function IM_FILE_READ(_file, _count) return "" end --- This opens the file at _filename in _mode and returns the File object. --- @param _filename string --- @param _mode string # e.g. "rb" --- @return any function ImStd.ImFileOpen(_filename, _mode) end --- [GMod] Platform specific if gmod then IM_FILE_CLOSE = FindMetaTable("File").Close --- @type function IM_FILE_SIZE = FindMetaTable("File").Size --- @type function IM_FILE_WRITE = FindMetaTable("File").Write --- @type function IM_FILE_READ = FindMetaTable("File").Read --- @type function end if not IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS then --- [GMod] Another Platform specific if gmod then function ImStd.ImFileOpen(filename, mode) return file.Open(filename, mode, "GAME") end end function ImStd.ImFileClose(f) IM_FILE_CLOSE(f) end function ImStd.ImFileGetSize(f) return IM_FILE_SIZE(f) end --- @param f any # File object --- @param data table # 1-based table to store result data --- @param count int # Amount of bytes to read from `f` function ImStd.ImFileRead(f, data, count) local CHUNK_SIZE = 8000 -- We don't read byte by byte local offset = 0 while offset < count do local read_size = math.min(CHUNK_SIZE, count - offset) local str = IM_FILE_READ(f, read_size) local bytes = {string.byte(str, 1, read_size)} for i = 1, read_size do data[offset + i] = bytes[i] end offset = offset + read_size end end --- @param filename string --- @param mode string --- @return table?, integer? function ImStd.ImFileLoadToMemory(filename, mode) local f = ImStd.ImFileOpen(filename, mode) if not f then return end local file_size = ImStd.ImFileGetSize(f) if file_size <= 0 then ImStd.ImFileClose(f) return end local file_data = {} ImStd.ImFileRead(f, file_data, file_size) if #file_data == 0 then ImStd.ImFileClose(f) return end ImStd.ImFileClose(f) return file_data, file_size end end --- Forward Declarations local CalcNextScrollFromScrollTargetAndClamp local MT = ImGui.GetMetatables() --- @param scale_factor float function MT.ImGuiStyle:ScaleAllSizes(scale_factor) self._MainScale = self._MainScale * scale_factor self.WindowPadding = ImTrunc(self.WindowPadding * scale_factor) self.WindowRounding = ImTrunc(self.WindowRounding * scale_factor) self.WindowBorderSize = ImTrunc(self.WindowBorderSize * scale_factor) self.WindowMinSize = ImTrunc(self.WindowMinSize * scale_factor) self.WindowBorderHoverPadding = ImTrunc(self.WindowBorderHoverPadding * scale_factor) self.ChildRounding = ImTrunc(self.ChildRounding * scale_factor) self.ChildBorderSize = ImTrunc(self.ChildBorderSize * scale_factor) self.PopupRounding = ImTrunc(self.PopupRounding * scale_factor) self.PopupBorderSize = ImTrunc(self.PopupBorderSize * scale_factor) self.FramePadding = ImTrunc(self.FramePadding * scale_factor) self.FrameBorderSize = ImTrunc(self.FrameBorderSize * scale_factor) self.FrameRounding = ImTrunc(self.FrameRounding * scale_factor) self.ItemSpacing = ImTrunc(self.ItemSpacing * scale_factor) self.ItemInnerSpacing = ImTrunc(self.ItemInnerSpacing * scale_factor) self.CellPadding = ImTrunc(self.CellPadding * scale_factor) self.TouchExtraPadding = ImTrunc(self.TouchExtraPadding * scale_factor) self.IndentSpacing = ImTrunc(self.IndentSpacing * scale_factor) self.ColumnsMinSpacing = ImTrunc(self.ColumnsMinSpacing * scale_factor) self.ScrollbarSize = ImTrunc(self.ScrollbarSize * scale_factor) self.ScrollbarRounding = ImTrunc(self.ScrollbarRounding * scale_factor) self.ScrollbarPadding = ImTrunc(self.ScrollbarPadding * scale_factor) self.GrabMinSize = ImTrunc(self.GrabMinSize * scale_factor) self.GrabRounding = ImTrunc(self.GrabRounding * scale_factor) self.LogSliderDeadzone = ImTrunc(self.LogSliderDeadzone * scale_factor) self.ImageRounding = ImTrunc(self.ImageRounding * scale_factor) self.ImageBorderSize = ImTrunc(self.ImageBorderSize * scale_factor) self.TabRounding = ImTrunc(self.TabRounding * scale_factor) self.TabBorderSize = ImTrunc(self.TabBorderSize * scale_factor) self.TabMinWidthBase = ImTrunc(self.TabMinWidthBase * scale_factor) self.TabMinWidthShrink = ImTrunc(self.TabMinWidthShrink * scale_factor) self.TabCloseButtonMinWidthSelected = (self.TabCloseButtonMinWidthSelected > 0.0 and self.TabCloseButtonMinWidthSelected ~= FLT_MAX) and ImTrunc(self.TabCloseButtonMinWidthSelected * scale_factor) or self.TabCloseButtonMinWidthSelected self.TabCloseButtonMinWidthUnselected = (self.TabCloseButtonMinWidthUnselected > 0.0 and self.TabCloseButtonMinWidthUnselected ~= FLT_MAX) and ImTrunc(self.TabCloseButtonMinWidthUnselected * scale_factor) or self.TabCloseButtonMinWidthUnselected self.TabBarBorderSize = ImTrunc(self.TabBarBorderSize * scale_factor) self.TabBarOverlineSize = ImTrunc(self.TabBarOverlineSize * scale_factor) self.TreeLinesSize = ImTrunc(self.TreeLinesSize * scale_factor) self.TreeLinesRounding = ImTrunc(self.TreeLinesRounding * scale_factor) self.MenuItemRounding = ImTrunc(self.MenuItemRounding * scale_factor) self.SelectableRounding = ImTrunc(self.SelectableRounding * scale_factor) self.DragDropTargetRounding = ImTrunc(self.DragDropTargetRounding * scale_factor) self.DragDropTargetBorderSize = ImTrunc(self.DragDropTargetBorderSize * scale_factor) self.DragDropTargetPadding = ImTrunc(self.DragDropTargetPadding * scale_factor) self.ColorMarkerSize = ImTrunc(self.ColorMarkerSize * scale_factor) self.InputTextCursorSize = ImTrunc(self.InputTextCursorSize * scale_factor) self.SeparatorSize = ImTrunc(self.SeparatorSize * scale_factor) self.SeparatorTextBorderSize = ImTrunc(self.SeparatorTextBorderSize * scale_factor) self.SeparatorTextPadding = ImTrunc(self.SeparatorTextPadding * scale_factor) self.DockingSeparatorSize = ImTrunc(self.DockingSeparatorSize * scale_factor) self.DisplayWindowPadding = ImTrunc(self.DisplayWindowPadding * scale_factor) self.DisplaySafeAreaPadding = ImTrunc(self.DisplaySafeAreaPadding * scale_factor) self.MouseCursorScale = ImTrunc(self.MouseCursorScale * scale_factor) end local ImGuiResizeGripDef = { {CornerPosN = ImVec2(1, 1), InnerDir = ImVec2(-1, -1), AngleMin12 = 0, AngleMax12 = 3}, -- Bottom right grip {CornerPosN = ImVec2(0, 1), InnerDir = ImVec2( 1, -1), AngleMin12 = 3, AngleMax12 = 6} -- Bottom left } --- [1] Left, [2] Right, [3] Up, [4] Down local ImGuiResizeBorderDef = { {InnerDir = ImVec2( 1, 0), SegmentN1 = ImVec2( 0, 1), SegmentN2 = ImVec2( 0, 0), OuterAngle = IM_PI * 1.00}, {InnerDir = ImVec2(-1, 0), SegmentN1 = ImVec2( 1, 0), SegmentN2 = ImVec2( 1, 1), OuterAngle = IM_PI * 0.00}, {InnerDir = ImVec2( 0, 1), SegmentN1 = ImVec2( 0, 0), SegmentN2 = ImVec2( 1, 0), OuterAngle = IM_PI * 1.50}, {InnerDir = ImVec2( 0, -1), SegmentN1 = ImVec2( 1, 1), SegmentN2 = ImVec2( 0, 1), OuterAngle = IM_PI * 0.50} } --- @param window ImGuiWindow --- @param border_n int --- @param perp_padding float --- @param thickness float --- @return ImRect --- @nodiscard local function GetResizeBorderRect(window, border_n, perp_padding, thickness) local rect = window:Rect() if thickness == 0.0 then ImVec2_CopyV(rect.Max, rect.Max.x - 1, rect.Max.y - 1) end if border_n == ImGuiDir.Left then return ImRect(rect.Min.x - thickness, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding) end if border_n == ImGuiDir.Right then return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x + thickness, rect.Max.y - perp_padding) end if border_n == ImGuiDir.Up then return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness, rect.Max.x - perp_padding, rect.Min.y + thickness) end if border_n == ImGuiDir.Down then return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y + thickness) end IM_ASSERT(false) return ImRect() end --- @param data table|number --- @param size int? # size = -1 to indicate that `data` is a single number --- @param seed int? --- @return int function ImHashData(data, size, seed) seed = seed or 0 local FNV_OFFSET_BASIS = 0x811C9DC5 local FNV_PRIME = 0x01000193 local hash = bit32.bxor(FNV_OFFSET_BASIS, seed) if size == -1 then --- @cast data number hash = bit32.bxor(hash, data) hash = bit32.band(hash * FNV_PRIME, 0xFFFFFFFF) else size = size or #data for i = 1, size do hash = bit32.bxor(hash, data[i]) hash = bit32.band(hash * FNV_PRIME, 0xFFFFFFFF) end end return hash end -- Use FNV1a, as one ImGui FIXME suggested --- @param str string --- @param size int? --- @param seed int? --- @return int -- [Luau] memo for whole-string hashes: labels are re-hashed with the same seed every frame. -- ponytail: whole-memo reset at HASH_MEMO_MAX entries, LRU if ID churn makes it thrash local hash_memo, hash_memo_n, HASH_MEMO_MAX = {}, 0, 16384 local ImHashStrRaw function ImHashStr(str, size, seed) if seed == nil then seed = 0 end if size == nil or size == #str then local m = hash_memo[seed] if m == nil then m = {}; hash_memo[seed] = m end local h = m[str] if h == nil then if hash_memo_n >= HASH_MEMO_MAX then hash_memo = { [seed] = m }; table.clear(m); hash_memo_n = 0 end h = ImHashStrRaw(str, #str, seed) m[str] = h hash_memo_n += 1 end return h end return ImHashStrRaw(str, size, seed) end @native function ImHashStrRaw(str, size, seed) if str == "" or size == 0 then return seed -- need to match cpp code edge case behavior while using FNV ourselves end local FNV_OFFSET_BASIS = 0x811C9DC5 local FNV_PRIME = 0x01000193 local hash = bit32.bxor(FNV_OFFSET_BASIS, seed) local i = 1 local c while i <= size do c = string.byte(str, i) -- `###` to reset back to initial hash value if c == 35 and string.byte(str, i + 1) == 35 and string.byte(str, i + 2) == 35 then hash = bit32.bxor(FNV_OFFSET_BASIS, seed) i = i + 2 else hash = bit32.bxor(hash, c) hash = bit32.band(hash * FNV_PRIME, 0xFFFFFFFF) end i = i + 1 end return hash end do --[[CharFromUtf8]] local lengths = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0} local masks = {0x00, 0x7f, 0x1f, 0x0f, 0x07} local mins = {0x400000, 0, 0x80, 0x800, 0x10000} local shiftc = {0, 18, 12, 6, 0} local shifte = {0, 6, 4, 2, 0} local s = {0, 0, 0, 0} --- @param in_text ImString --- @param pos int --- @param in_text_end int --- @return int wanted --- @return unsigned_int out_char function ImStd.ImTextCharFromUtf8(in_text, pos, in_text_end) local len = lengths[bit32.rshift(ImStrByte(in_text, pos), 3) + 1] local wanted = len > 0 and len or 1 if in_text_end == nil then in_text_end = pos + wanted end s[1] = (pos < in_text_end) and ImStrByte(in_text, pos) or 0 s[2] = (pos + 1 < in_text_end) and ImStrByte(in_text, pos + 1) or 0 s[3] = (pos + 2 < in_text_end) and ImStrByte(in_text, pos + 2) or 0 s[4] = (pos + 3 < in_text_end) and ImStrByte(in_text, pos + 3) or 0 local out_char out_char = bit32.lshift(bit32.band(s[1], masks[len + 1]), 18) out_char = bit32.bor(out_char, bit32.lshift(bit32.band(s[2], 0x3f), 12)) out_char = bit32.bor(out_char, bit32.lshift(bit32.band(s[3], 0x3f), 6)) out_char = bit32.bor(out_char, bit32.lshift(bit32.band(s[4], 0x3f), 0)) out_char = bit32.rshift(out_char, shiftc[len + 1]) local e = 0 e = bit32.lshift((out_char < mins[len + 1]) and 1 or 0, 6) e = bit32.bor(e, bit32.lshift((bit32.rshift(out_char, 11) == 0x1b) and 1 or 0, 7)) e = bit32.bor(e, bit32.lshift((out_char > IM_UNICODE_CODEPOINT_MAX) and 1 or 0, 8)) e = bit32.bor(e, bit32.rshift(bit32.band(s[2], 0xc0), 2)) e = bit32.bor(e, bit32.rshift(bit32.band(s[3], 0xc0), 4)) e = bit32.bor(e, bit32.rshift(s[4], 6)) e = bit32.bxor(e, 0x2a) e = bit32.rshift(e, shifte[len + 1]) if e ~= 0 then wanted = ImMin(wanted, (s[1] ~= 0 and 1 or 0) + (s[2] ~= 0 and 1 or 0) + (s[3] ~= 0 and 1 or 0) + (s[4] ~= 0 and 1 or 0)) out_char = IM_UNICODE_CODEPOINT_INVALID end return wanted, out_char end end --- @param buf char[] --- @param buf_size int --- @param c unsigned_int local function ImTextCharToUtf8_inline(buf, buf_size, c) if c < 0x80 then buf[1] = c return 1 end if c < 0x800 then if buf_size < 2 then return 0 end buf[1] = (0xc0 + bit32.rshift(c, 6)) buf[2] = (0x80 + bit32.band(c, 0x3f)) return 2 end if c < 0x10000 then if buf_size < 3 then return 0 end buf[1] = (0xe0 + bit32.rshift(c, 12)) buf[2] = (0x80 + bit32.band(bit32.rshift(c, 6), 0x3f)) buf[3] = (0x80 + bit32.band(c, 0x3f)) return 3 end if c <= 0x10FFFF then if buf_size < 4 then return 0 end buf[1] = (0xf0 + bit32.rshift(c, 18)) buf[2] = (0x80 + bit32.band(bit32.rshift(c, 12), 0x3f)) buf[3] = (0x80 + bit32.band(bit32.rshift(c, 6), 0x3f)) buf[4] = (0x80 + bit32.band(c, 0x3f)) return 4 end -- Invalid code point, the max unicode is 0x10FFFF return 0 end --- @param out_buf [char, char, char, char, char] --- @param c unsigned_int function ImStd.ImTextCharToUtf8(out_buf, c) local count = ImTextCharToUtf8_inline(out_buf, 5, c) out_buf[count + 1] = 0 return count end --- @param in_text ImString --- @param pos int --- @param in_text_end int --- @return int function ImStd.ImTextCountUtf8BytesFromChar(in_text, pos, in_text_end) local bytes, unused = ImStd.ImTextCharFromUtf8(in_text, pos, in_text_end) return bytes end --- @param text ImString --- @param in_text_start int --- @param in_p int function ImStd.ImTextFindPreviousUtf8Codepoint(text, in_text_start, in_p) while in_p > in_text_start do in_p = in_p - 1 if bit32.band(ImStrByte(text, in_p), 0xC0) ~= 0x80 then return in_p end end return in_text_start end --- @param text ImString --- @param in_text_start int --- @param in_text_end int --- @param in_p int --- @return int function ImStd.ImTextFindValidUtf8CodepointEnd(text, in_text_start, in_text_end, in_p) if in_text_start == in_p then return in_text_start end local prev = ImStd.ImTextFindPreviousUtf8Codepoint(text, in_text_start, in_p) local prev_c_len, prev_c = ImStd.ImTextCharFromUtf8(text, prev, in_text_end) -- Check if the previous character is valid and fits within the range if prev_c ~= IM_UNICODE_CODEPOINT_INVALID and prev_c_len <= (in_p - prev) then return in_p end return prev end --- @param a ImVec2 --- @param b ImVec2 --- @param p ImVec2 --- @return ImVec2 --- @nodiscard function ImStd.ImLineClosestPoint(a, b, p) local ap = p - a local ab_dir = b - a local dot = ap.x * ab_dir.x + ap.y * ab_dir.y if dot < 0.0 then return a end local ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y if dot > ab_len_sqr then return b end return a + ab_dir * (dot / ab_len_sqr) end --- @param a ImVec2 --- @param b ImVec2 --- @param c ImVec2 --- @param p ImVec2 function ImStd.ImTriangleContainsPoint(a, b, c, p) local b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0 local b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0 local b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0 return (b1 == b2) and (b2 == b3) end --- @param a ImVec2 --- @param b ImVec2 --- @param c ImVec2 --- @param p ImVec2 --- @return float, float, float function ImStd.ImTriangleBarycentricCoords(a, b, c, p) local v0 = b - a local v1 = c - a local v2 = p - a local denom = v0.x * v1.y - v1.x * v0.y local out_v = (v2.x * v1.y - v1.x * v2.y) / denom local out_w = (v0.x * v2.y - v2.x * v0.y) / denom local out_u = 1.0 - out_v - out_w return out_u, out_v, out_w end --- @param a ImVec2 --- @param b ImVec2 --- @param c ImVec2 --- @param p ImVec2 --- @return ImVec2 --- @nodiscard function ImStd.ImTriangleClosestPoint(a, b, c, p) local proj_ab = ImStd.ImLineClosestPoint(a, b, p) local proj_bc = ImStd.ImLineClosestPoint(b, c, p) local proj_ca = ImStd.ImLineClosestPoint(c, a, p) local dist2_ab = ImLengthSqr(p - proj_ab) local dist2_bc = ImLengthSqr(p - proj_bc) local dist2_ca = ImLengthSqr(p - proj_ca) local m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca)) if m == dist2_ab then return proj_ab elseif m == dist2_bc then return proj_bc else return proj_ca end end --- @param dst char[] --- @param dst_pos int --- @param src char[] --- @param src_pos int --- @param count size_t function ImStd.ImStrncpy(dst, dst_pos, src, src_pos, count) if count < 1 then return end if count > 1 then ImStd.memmove(dst, dst_pos, src, src_pos, count - 1) end dst[dst_pos + (count - 1)] = 0 end --- @param str char[] --- @param mid_line int --- @param begin int function ImStd.ImStrbol(str, mid_line, begin) IM_ASSERT_PARANOID(mid_line >= begin and mid_line <= #str) while mid_line > begin and str[mid_line - 1] ~= 10 do mid_line = mid_line - 1 end return mid_line end --- @param buf char[] function ImStd.ImStrTrimBlanks(buf) local p = 1 -- buf_begin while buf[p] == 32 or buf[p] == 9 do p = p + 1 end local p_start = p while buf[p] ~= 0 do p = p + 1 end while p > p_start and (buf[p - 1] == 32 or buf[p - 1] == 9) do p = p - 1 end if p_start > 1 then ImStd.memmove(buf, 1, buf, p_start, p - p_start) end buf[p - p_start + 1] = 0 end function ImGui.UpdateCurrentFontSize(restore_font_size_after_scaling) local g = GImGui local window = g.CurrentWindow g.Style.FontSizeBase = g.FontSizeBase -- if (window ~= nil and window.SkipItems) then -- local table = g.CurrentTable -- if (table == nil or (table.CurrentColumn ~= -1 and table.Columns[table.CurrentColumn].IsSkipItems == false)) then -- return -- end -- end local final_size = (restore_font_size_after_scaling > 0.0) and restore_font_size_after_scaling or 0.0 if final_size == 0.0 then final_size = g.FontSizeBase final_size = final_size * g.Style.FontScaleMain final_size = final_size * g.Style.FontScaleDpi if window ~= nil then final_size = final_size * window.FontWindowScale end end final_size = ImGui.GetRoundedFontSize(final_size) final_size = ImClamp(final_size, 4.0, IMGUI_FONT_SIZE_MAX) if (g.Font ~= nil and bit32.band(g.IO.BackendFlags, ImGuiBackendFlags.RendererHasTextures) ~= 0) then g.Font.CurrentRasterizerDensity = g.FontRasterizerDensity end g.FontSize = final_size g.FontBaked = (g.Font ~= nil and window ~= nil) and g.Font:GetFontBaked(final_size) or nil g.FontBakedScale = (g.FontBaked ~= nil) and (g.FontSize / g.FontBaked.Size) or 0.0 g.DrawListSharedData.FontSize = g.FontSize g.DrawListSharedData.FontScale = g.FontBakedScale end function ImGui.SetCurrentFont(font, font_size_before_scaling, font_size_after_scaling) local g = GImGui g.Font = font g.FontSizeBase = font_size_before_scaling ImGui.UpdateCurrentFontSize(font_size_after_scaling) if font ~= nil then IM_ASSERT(font and font:IsLoaded()) local atlas = font.OwnerAtlas g.DrawListSharedData.FontAtlas = atlas g.DrawListSharedData.Font = font ImFontAtlasUpdateDrawListsSharedData(atlas) if (g.CurrentWindow ~= nil) then g.CurrentWindow.DrawList:_SetTexture(atlas.TexRef) end end end function ImGui.PushFont(font, font_size_base) local g = GImGui if font == nil then font = g.Font end IM_ASSERT(font ~= nil) IM_ASSERT(font_size_base >= 0.0) g.FontStack:push_back(ImFontStackData(font, g.FontSizeBase, g.FontSize)) if font_size_base == 0.0 then font_size_base = g.FontSizeBase end ImGui.SetCurrentFont(font, font_size_base, 0.0) end function ImGui.PopFont() local g = GImGui if (g.FontStack.Size <= 0) then IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!") return end local font_stack_data = g.FontStack:back() ImGui.SetCurrentFont(font_stack_data.Font, font_stack_data.FontSizeBeforeScaling, font_stack_data.FontSizeAfterScaling) g.FontStack:pop_back() end function ImGui.UpdateTexturesNewFrame() local g = GImGui local has_textures = bit32.band(g.IO.BackendFlags, ImGuiBackendFlags.RendererHasTextures) ~= 0 for _, atlas in g.FontAtlases:iter() do if (atlas.OwnerContext == g) then ImFontAtlasUpdateNewFrame(atlas, g.FrameCount, has_textures) else IM_ASSERT(atlas.Builder ~= nil and atlas.Builder.FrameCount ~= -1) IM_ASSERT(atlas.RendererHasTextures == has_textures) end end for _, tex in g.UserTextures:iter() do ImTextureDataUpdateNewFrame(tex) end end function ImGui.UpdateTexturesEndFrame() local g = GImGui g.PlatformIO.Textures:resize(0) for _, atlas in g.FontAtlases:iter() do for _, tex in atlas.TexList:iter() do tex.RefCount = atlas.RefCount g.PlatformIO.Textures:push_back(tex) end end for _, tex in g.UserTextures:iter() do g.PlatformIO.Textures:push_back(tex) end end function ImGui.UpdateFontsNewFrame() local g = GImGui if (bit32.band(g.IO.BackendFlags, ImGuiBackendFlags.RendererHasTextures) == 0) then for _, atlas in g.FontAtlases:iter() do atlas.Locked = true end end if (g.Style._NextFrameFontSizeBase ~= 0.0) then g.Style.FontSizeBase = g.Style._NextFrameFontSizeBase g.Style._NextFrameFontSizeBase = 0.0 end local font = ImGui.GetDefaultFont() if g.Style.FontSizeBase <= 0.0 then g.Style.FontSizeBase = ((font.LegacySize > 0.0) and font.LegacySize or FONT_DEFAULT_SIZE_BASE) end g.Font = font g.FontSizeBase = g.Style.FontSizeBase g.FontSize = 0.0 local font_stack_data = ImFontStackData(font, g.Style.FontSizeBase, g.Style.FontSizeBase) ImGui.SetCurrentFont(font_stack_data.Font, font_stack_data.FontSizeBeforeScaling, 0.0) g.FontStack:push_back(font_stack_data) IM_ASSERT(g.Font:IsLoaded()) end function ImGui.UpdateFontsEndFrame() ImGui.PopFont() end --- @return ImFont function ImGui.GetDefaultFont() local g = GImGui local atlas = g.IO.Fonts if (atlas.Builder == nil or atlas.Fonts.Size == 0) then ImFontAtlasBuildMain(atlas) end return g.IO.FontDefault and g.IO.FontDefault or atlas.Fonts[1] end --- @param atlas ImFontAtlas function ImGui.RegisterFontAtlas(atlas) local g = GImGui if (g.FontAtlases.Size == 0) then IM_ASSERT(atlas == g.IO.Fonts) end atlas.RefCount = atlas.RefCount + 1 g.FontAtlases:push_back(atlas) ImFontAtlasAddDrawListSharedData(atlas, g.DrawListSharedData) for _, tex in atlas.TexList:iter() do tex.RefCount = atlas.RefCount end end --- @param atlas ImFontAtlas function ImGui.UnregisterFontAtlas(atlas) local g = GImGui IM_ASSERT(atlas.RefCount > 0) ImFontAtlasRemoveDrawListSharedData(atlas, g.DrawListSharedData) g.FontAtlases:find_erase(atlas) atlas.RefCount = atlas.RefCount - 1 for _, tex in atlas.TexList:iter() do tex.RefCount = atlas.RefCount end end function ImGui.GetCurrentContext() return GImGui end --- @param ctx ImGuiContext? function ImGui.SetCurrentContext(ctx) GImGui = ctx ImGui._SetCurrentContext_Internal(ctx) ImGui._SetCurrentContext_Widgets(ctx) ImGui._SetCurrentContext_Tables(ctx) end --- @param alloc_func ImGuiMemAllocFunc --- @param free_func ImGuiMemFreeFunc --- @param user_data any function ImGui.SetAllocatorFunctions(alloc_func, free_func, user_data) GImAllocatorAllocFunc = alloc_func GImAllocatorFreeFunc = free_func GImAllocatorUserData = user_data end --- @param key ImGuiLocKey --- @param text string --- @return ImGuiLocEntry --- @nodiscard --- @package local function ImGuiLocEntry(key, text) return { Key = key, Text = text } end local GLocalizationEntriesEnUS = { ImGuiLocEntry(ImGuiLocKey.VersionStr, "Dear ImGui WIP"), ImGuiLocEntry(ImGuiLocKey.TableSizeOne, "Size column to fit###SizeOne"), ImGuiLocEntry(ImGuiLocKey.TableSizeAllFit, "Size all columns to fit###SizeAll"), ImGuiLocEntry(ImGuiLocKey.TableSizeAllDefault, "Size all columns to default###SizeAll"), ImGuiLocEntry(ImGuiLocKey.TableReset, "Reset"), ImGuiLocEntry(ImGuiLocKey.TableResetOrder, "Reset order###ResetOrder"), ImGuiLocEntry(ImGuiLocKey.TableResetVisibility, "Reset visibility###ResetVisibility"), ImGuiLocEntry(ImGuiLocKey.WindowingMainMenuBar, "(Main menu bar)"), ImGuiLocEntry(ImGuiLocKey.WindowingPopup, "(Popup)"), ImGuiLocEntry(ImGuiLocKey.WindowingUntitled, "(Untitled)"), ImGuiLocEntry(ImGuiLocKey.OpenLink_s, "Open '%s'"), ImGuiLocEntry(ImGuiLocKey.CopyLink, "Copy Link###CopyLink"), ImGuiLocEntry(ImGuiLocKey.DockingHideTabBar, "Hide tab bar###HideTabBar"), ImGuiLocEntry(ImGuiLocKey.DockingHoldShiftToDock, "Hold SHIFT to enable Docking window."), ImGuiLocEntry(ImGuiLocKey.DockingDragToUndockOrMoveNode, "Click and drag to move or undock whole node."), } function ImGui.Initialize() local g = GImGui IM_ASSERT(not g.Initialized and not g.SettingsLoaded) ImGui.WindowSettingsAddSettingsHandler() if ImGui.TableSettingsAddSettingsHandler then ImGui.TableSettingsAddSettingsHandler() end ImGui.DockContextInitialize(g) ImGui.LocalizeRegisterEntries(GLocalizationEntriesEnUS, #GLocalizationEntriesEnUS) local viewport = ImGuiViewportP() viewport.ID = IMGUI_VIEWPORT_DEFAULT_ID viewport.Idx = 1 viewport.PlatformWindowCreated = true viewport.Flags = ImGuiViewportFlags.OwnedByApp g.Viewports:push_back(viewport) g.ViewportCreatedCount = g.ViewportCreatedCount + 1 g.PlatformIO.Viewports:push_back(g.Viewports.Data[1]) for key = ImGuiKey.NamedKey_BEGIN, ImGuiKey.NamedKey_END - 1 do if (key >= ImGuiKey.K0 and key <= ImGuiKey.K9) or (key >= ImGuiKey.A and key <= ImGuiKey.Z) or (key >= ImGuiKey.Keypad0 and key <= ImGuiKey.Keypad9) or key == ImGuiKey.Tab or key == ImGuiKey.Space or key == ImGuiKey.Apostrophe or key == ImGuiKey.Comma or key == ImGuiKey.Minus or key == ImGuiKey.Period or key == ImGuiKey.Slash or key == ImGuiKey.Semicolon or key == ImGuiKey.Equal or key == ImGuiKey.LeftBracket or key == ImGuiKey.RightBracket or key == ImGuiKey.GraveAccent or key == ImGuiKey.KeypadDecimal or key == ImGuiKey.KeypadDivide or key == ImGuiKey.KeypadMultiply or key == ImGuiKey.KeypadSubtract or key == ImGuiKey.KeypadAdd or key == ImGuiKey.KeypadEqual then g.KeysMayBeCharInput:SetBit(key + 1) end end local atlas = g.IO.Fonts g.DrawListSharedData.Context = g ImGui.RegisterFontAtlas(atlas) g.Initialized = true end --- @param shared_font_atlas? ImFontAtlas function ImGui.CreateContext(shared_font_atlas) local prev_ctx = ImGui.GetCurrentContext() local ctx = ImGuiContext(shared_font_atlas) ImGui.SetCurrentContext(ctx) ImGui.Initialize() if prev_ctx ~= nil then ImGui.SetCurrentContext(prev_ctx) -- Restore previous context if any, else keep new one end return ctx end --- @param ctx? ImGuiContext function ImGui.DestroyContext(ctx) local prev_ctx = ImGui.GetCurrentContext() if ctx == nil then ctx = prev_ctx end ImGui.SetCurrentContext(ctx) ImGui.Shutdown() ImGui.SetCurrentContext((prev_ctx ~= ctx) and prev_ctx or nil) end --- @param window ImGuiWindow --- @return any function ImGui.FindWindowSettingsByWindow(window) local g = GImGui if window.SettingsOffset ~= -1 then return g.SettingsWindows[window.SettingsOffset + 1] end return ImGui.FindWindowSettingsByID(window.ID) end --- @param id ImGuiID --- @return any function ImGui.FindWindowSettingsByID(id) local g = GImGui for _, settings in g.SettingsWindows:iter() do if settings.ID == id and not settings.WantDelete then return settings end end return nil end --- @param window ImGuiWindow --- @return ImGuiWindow? function ImGui.FindFrontMostVisibleChildWindow(window) for n = window.DC.ChildWindows.Size, 1, -1 do if ImGui.IsWindowActiveAndVisible(window.DC.ChildWindows.Data[n]) then return ImGui.FindFrontMostVisibleChildWindow(window.DC.ChildWindows.Data[n]) end end return window end --- @param window ImGuiWindow --- @param cond ImGuiCond --- @param allow bool local function SetWindowConditionAllowFlags(window, cond, allow) if allow then window.SetWindowPosAllowFlags = bit32.bor(window.SetWindowPosAllowFlags, cond) window.SetWindowSizeAllowFlags = bit32.bor(window.SetWindowSizeAllowFlags, cond) window.SetWindowCollapsedAllowFlags = bit32.bor(window.SetWindowCollapsedAllowFlags, cond) else window.SetWindowPosAllowFlags = bit32.band(window.SetWindowPosAllowFlags, bit32.bnot(cond)) window.SetWindowSizeAllowFlags = bit32.band(window.SetWindowSizeAllowFlags, bit32.bnot(cond)) window.SetWindowCollapsedAllowFlags = bit32.band(window.SetWindowCollapsedAllowFlags, bit32.bnot(cond)) end end --- @param window ImGuiWindow --- @param settings ImGuiWindowSettings local function ApplyWindowSettings(window, settings) if settings ~= nil then local main_viewport = ImGui.GetMainViewport() ImVec2_Copy(window.ViewportPos, main_viewport.Pos) if (settings.ViewportId ~= 0) then window.ViewportId = settings.ViewportId ImVec2_Copy(window.ViewportPos, settings.ViewportPos) end window.Pos = ImTrunc(ImVec2(settings.Pos.x + window.ViewportPos.x, settings.Pos.y + window.ViewportPos.y)) if settings.Size.x > 0 and settings.Size.y > 0 then local size = ImVec2(ImTrunc(settings.Size.x), ImTrunc(settings.Size.y)) ImVec2_Copy(window.Size, size) ImVec2_Copy(window.SizeFull, size) window.AutoFitFramesX = 0; window.AutoFitFramesY = 0 end window.Collapsed = settings.Collapsed window.DockId = settings.DockId or 0 window.DockOrder = settings.DockOrder or -1 SetWindowConditionAllowFlags(window, ImGuiCond.FirstUseEver, false) end -- So first call to CalcWindowContentSizes() doesn't return crazy values ImVec2_Copy(window.DC.CursorStartPos, window.Pos) ImVec2_Copy(window.DC.CursorMaxPos, window.Pos) ImVec2_Copy(window.DC.IdealMaxPos, window.Pos) if (bit32.band(window.Flags, ImGuiWindowFlags.AlwaysAutoResize) ~= 0) then window.AutoFitFramesX = 2; window.AutoFitFramesY = 2 window.AutoFitOnlyGrows = false else window.AutoFitFramesX = (window.Size.x <= 0.0) and 2 or 0 window.AutoFitFramesY = (window.Size.y <= 0.0) and 2 or 0 window.AutoFitOnlyGrows = (window.AutoFitFramesX > 0) or (window.AutoFitFramesY > 0) end end --- @param window ImGuiWindow --- @param settings ImGuiWindowSettings local function InitOrLoadWindowSettings(window, settings) -- Initial window state with e.g. default/arbitrary window position -- Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. local main_viewport = ImGui.GetMainViewport() ImVec2_Copy(window.Pos, main_viewport.Pos + ImVec2(60, 60)) ImVec2_Copy(window.Size, ImVec2(0, 0)) ImVec2_Copy(window.SizeFull, ImVec2(0, 0)) ImVec2_Copy(window.ViewportPos, main_viewport.Pos) window.SetWindowPosAllowFlags = bit32.bor(ImGuiCond.Always, ImGuiCond.Once, ImGuiCond.FirstUseEver, ImGuiCond.Appearing) window.SetWindowSizeAllowFlags = window.SetWindowPosAllowFlags window.SetWindowCollapsedAllowFlags = window.SetWindowPosAllowFlags window.SetWindowDockAllowFlags = window.SetWindowPosAllowFlags ApplyWindowSettings(window, settings) if settings ~= nil then -- TODO: settings.LastUsedDate = g.SessionDate end end --- @param name string --- @param flags ImGuiWindowFlags --- @return ImGuiWindow local function CreateNewWindow(name, flags) local g = GImGui local window_id = ImHashStr(name) local window = ImGuiWindow(g, name) window.ID = window_id window.Flags = flags g.WindowsById[window_id] = window local settings = nil if bit32.band(window.Flags, ImGuiWindowFlags.NoSavedSettings) == 0 then settings = ImGui.FindWindowSettingsByWindow(window) if settings ~= nil then window.SettingsOffset = g.SettingsWindows:index_from_ptr(settings) end end InitOrLoadWindowSettings(window, settings) if bit32.band(flags, ImGuiWindowFlags.NoBringToFrontOnFocus) ~= 0 then g.Windows:push_front(window) else g.Windows:push_back(window) end return window end --------------------------------------------------------------------------------------- -- [SECTION] SETTINGS (in-memory .ini; Roblox has no disk) --------------------------------------------------------------------------------------- function ImGui.UpdateSettings() local g = GImGui if not g.SettingsLoaded then if g.IO.IniFilename then ImGui.LoadIniSettingsFromDisk(g.IO.IniFilename) end g.SettingsLoaded = true end if g.SettingsDirtyTimer > 0.0 then g.SettingsDirtyTimer = g.SettingsDirtyTimer - g.IO.DeltaTime if g.SettingsDirtyTimer <= 0.0 then -- No disk: always let the user know they can call SaveIniSettingsToMemory() g.IO.WantSaveIniSettings = true g.SettingsDirtyTimer = 0.0 end end end --- @param window? ImGuiWindow function ImGui.MarkIniSettingsDirty(window) local g = GImGui if window ~= nil and bit32.band(window.Flags, ImGuiWindowFlags.NoSavedSettings) ~= 0 then return end if g.SettingsDirtyTimer <= 0.0 then g.SettingsDirtyTimer = g.IO.IniSavingRate end end --- @param handler ImGuiSettingsHandler function ImGui.AddSettingsHandler(handler) local g = GImGui IM_ASSERT(ImGui.FindSettingsHandler(handler.TypeName) == nil) if handler.TypeHash == 0 or handler.TypeHash == nil then handler.TypeHash = ImHashStr(handler.TypeName) end g.SettingsHandlers:push_back(handler) end function ImGui.RemoveSettingsHandler(type_name) local g = GImGui local handler = ImGui.FindSettingsHandler(type_name) if handler then g.SettingsHandlers:find_erase(handler) end end function ImGui.FindSettingsHandler(type_name) local g = GImGui local type_hash = ImHashStr(type_name) for _, handler in g.SettingsHandlers:iter() do if handler.TypeHash == type_hash then return handler end end return nil end function ImGui.ClearIniSettings() local g = GImGui g.SettingsIniData = "" for _, handler in g.SettingsHandlers:iter() do if handler.ClearAllFn then handler.ClearAllFn(g, handler) end end end function ImGui.CleanupIniSettings(args) local g = GImGui if args.DiscardAll then for _, handler in g.SettingsHandlers:iter() do if (args.TypeHashFilter or 0) == 0 or handler.TypeHash == args.TypeHashFilter then if handler.ClearAllFn then handler.ClearAllFn(g, handler) end end end end end function ImGui.LoadIniSettingsFromDisk(ini_filename) local data = ImStd.ImFileLoadToMemory and ImStd.ImFileLoadToMemory(ini_filename, "rb") if not data or #data == 0 then return end local parts = {} for i = 1, #data do parts[i] = string.char(data[i]) end ImGui.LoadIniSettingsFromMemory(table.concat(parts)) end --- Zero-tolerance, cheap .ini parsing. `ini_size` ignored (Lua strings know their length). function ImGui.LoadIniSettingsFromMemory(ini_data, ini_size) local g = GImGui IM_ASSERT(g.Initialized) if ini_size and ini_size > 0 then ini_data = ini_data:sub(1, ini_size) end g.SettingsIniData = ini_data for _, handler in g.SettingsHandlers:iter() do if handler.ReadInitFn then handler.ReadInitFn(g, handler) end end local entry_data, entry_handler = nil, nil for line in ini_data:gmatch("[^\r\n]+") do local c = line:sub(1, 1) if c == ";" then -- comment elseif c == "[" and line:sub(-1) == "]" then -- "[Type][Name]" (Name may contain [] characters) local type_name, name = line:match("^%[([^%]]*)%]%[(.*)%]$") if type_name then entry_handler = ImGui.FindSettingsHandler(type_name) entry_data = entry_handler and entry_handler.ReadOpenFn(g, entry_handler, name) or nil else entry_handler, entry_data = nil, nil end elseif entry_handler ~= nil and entry_data ~= nil then entry_handler.ReadLineFn(g, entry_handler, entry_data, line) end end g.SettingsLoaded = true for _, handler in g.SettingsHandlers:iter() do if handler.ApplyAllFn then handler.ApplyAllFn(g, handler) end end end function ImGui.SaveIniSettingsToDisk(ini_filename) local g = GImGui g.SettingsDirtyTimer = 0.0 -- Roblox: no disk. Call SaveIniSettingsToMemory() and persist the string yourself (e.g. DataStore). end --- @return string ini_data, int size function ImGui.SaveIniSettingsToMemory() local g = GImGui g.SettingsDirtyTimer = 0.0 local buf = ImGuiTextBuffer() for _, handler in g.SettingsHandlers:iter() do handler.WriteAllFn(g, handler, buf) end g.SettingsIniData = buf:c_str() return g.SettingsIniData, #g.SettingsIniData end function ImGui.CreateNewWindowSettings(name) local g = GImGui if not g.IO.ConfigDebugIniSettings then -- ImHashSkipUncontributingPrefix: skip everything up to the last "###" local p = name:match("^.*()###") if p then name = name:sub(p) end end local settings = ImGuiWindowSettings() settings.ID = ImHashStr(name) settings.Name = name g.SettingsWindows:push_back(settings) return settings end function ImGui.ClearWindowSettings(name) local window = ImGui.FindWindowByName(name) if window ~= nil then window.Flags = bit32.bor(window.Flags, ImGuiWindowFlags.NoSavedSettings) InitOrLoadWindowSettings(window, nil) if window.DockId and window.DockId ~= 0 and ImGui.DockContextProcessUndockWindow then ImGui.DockContextProcessUndockWindow(GImGui, window, true) end end local settings = window and ImGui.FindWindowSettingsByWindow(window) or ImGui.FindWindowSettingsByID(ImHashStr(name)) if settings then settings.WantDelete = true end end local function WindowSettingsHandler_ClearAll(ctx, handler) local g = ctx for _, window in g.Windows:iter() do window.SettingsOffset = -1 end g.SettingsWindows:clear() end local function WindowSettingsHandler_ReadOpen(ctx, handler, name) local id = ImHashStr(name) local settings = ImGui.FindWindowSettingsByID(id) if settings then local fresh = ImGuiWindowSettings() for k, v in pairs(fresh) do settings[k] = v end settings.Name = name else settings = ImGui.CreateNewWindowSettings(name) end settings.ID = id settings.WantApply = true return settings end local function WindowSettingsHandler_ReadLine(ctx, handler, settings, line) local x, y = line:match("^Pos=(%-?%d+),(%-?%d+)") if x then ImVec2_CopyV(settings.Pos, tonumber(x), tonumber(y)) return end x, y = line:match("^Size=(%-?%d+),(%-?%d+)") if x then ImVec2_CopyV(settings.Size, tonumber(x), tonumber(y)) return end x = line:match("^ViewportId=0x(%x+)") if x then settings.ViewportId = tonumber(x, 16) return end x, y = line:match("^ViewportPos=(%-?%d+),(%-?%d+)") if x then ImVec2_CopyV(settings.ViewportPos, tonumber(x), tonumber(y)) return end x = line:match("^Collapsed=(%d+)") if x then settings.Collapsed = tonumber(x) ~= 0 return end x = line:match("^IsChild=(%d+)") if x then settings.IsChild = tonumber(x) ~= 0 return end x = line:match("^LastUsed=(%d+)") if x then settings.LastUsedDate = tonumber(x) return end x, y = line:match("^DockId=0x(%x+),(%-?%d+)") if x then settings.DockId = tonumber(x, 16); settings.DockOrder = tonumber(y) return end x = line:match("^DockId=0x(%x+)") if x then settings.DockId = tonumber(x, 16); settings.DockOrder = -1 return end x = line:match("^ClassId=0x(%x+)") if x then settings.ClassId = tonumber(x, 16) return end end local function WindowSettingsHandler_ApplyAll(ctx, handler) local g = ctx for _, settings in g.SettingsWindows:iter() do if settings.WantApply then local window = ImGui.FindWindowByID(settings.ID) if window then ApplyWindowSettings(window, settings) end settings.WantApply = false end end end local function WindowSettingsHandler_WriteAll(ctx, handler, buf) local g = ctx for _, window in g.Windows:iter() do if bit32.band(window.Flags, ImGuiWindowFlags.NoSavedSettings) == 0 then local settings = ImGui.FindWindowSettingsByWindow(window) if not settings then settings = ImGui.CreateNewWindowSettings(window.Name) window.SettingsOffset = g.SettingsWindows:index_from_ptr(settings) end IM_ASSERT(settings.ID == window.ID) ImVec2_CopyV(settings.Pos, ImTrunc(window.Pos.x - window.ViewportPos.x), ImTrunc(window.Pos.y - window.ViewportPos.y)) ImVec2_CopyV(settings.Size, ImTrunc(window.SizeFull.x), ImTrunc(window.SizeFull.y)) settings.ViewportId = window.ViewportId or 0 ImVec2_CopyV(settings.ViewportPos, ImTrunc(window.ViewportPos.x), ImTrunc(window.ViewportPos.y)) settings.DockId = window.DockId or 0 settings.ClassId = window.WindowClass and window.WindowClass.ClassId or 0 settings.DockOrder = window.DockOrder or -1 settings.Collapsed = window.Collapsed settings.IsChild = (window.RootWindow ~= window) settings.WantDelete = false end end for _, settings in g.SettingsWindows:iter() do if not settings.WantDelete then buf:appendf("[%s][%s]\n", handler.TypeName, settings.Name) if settings.IsChild then buf:appendf("IsChild=1\n") buf:appendf("Size=%d,%d\n", settings.Size.x, settings.Size.y) else if settings.ViewportId ~= 0 and settings.ViewportId ~= IMGUI_VIEWPORT_DEFAULT_ID then buf:appendf("ViewportPos=%d,%d\n", settings.ViewportPos.x, settings.ViewportPos.y) buf:appendf("ViewportId=0x%08X\n", settings.ViewportId) end if settings.Pos.x ~= 0 or settings.Pos.y ~= 0 or settings.ViewportId == IMGUI_VIEWPORT_DEFAULT_ID then buf:appendf("Pos=%d,%d\n", settings.Pos.x, settings.Pos.y) end if settings.Size.x ~= 0 or settings.Size.y ~= 0 then buf:appendf("Size=%d,%d\n", settings.Size.x, settings.Size.y) end buf:appendf("Collapsed=%d\n", settings.Collapsed and 1 or 0) if settings.DockId ~= 0 then if settings.DockOrder == -1 then buf:appendf("DockId=0x%08X\n", settings.DockId) else buf:appendf("DockId=0x%08X,%d\n", settings.DockId, settings.DockOrder) end if settings.ClassId ~= 0 then buf:appendf("ClassId=0x%08X\n", settings.ClassId) end end end buf:append("\n") end end end function ImGui.WindowSettingsAddSettingsHandler() local ini_handler = ImGuiSettingsHandler() ini_handler.TypeName = "Window" ini_handler.TypeHash = ImHashStr("Window") ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll ini_handler.WriteAllFn = WindowSettingsHandler_WriteAll ImGui.AddSettingsHandler(ini_handler) end --------------------------------------------------------------------------------------- -- [SECTION] LOGGING/CAPTURING --------------------------------------------------------------------------------------- ImGuiLogFlags = ImGuiLogFlags or { None = 0, OutputTTY = 1, OutputFile = 2, OutputBuffer = 4, OutputClipboard = 8, OutputMask_ = 15, } local function LogTextV_Internal(g, fmt, ...) local s = (select("#", ...) > 0) and string.format(fmt, ...) or fmt if g.LogFile then print(s) -- TTY (the only "file" available on Roblox) else g.LogBuffer:append(s) end end function ImGui.LogText(fmt, ...) local g = GImGui if not g.LogEnabled then return end LogTextV_Internal(g, fmt, ...) end ImGui.LogTextV = ImGui.LogText --- Internal version that takes a position to decide on newline placement and pad items according to their depth. --- `text` is a Lua string, `text_begin/text_end` optional 1-based byte range (text_end exclusive). function ImGui.LogRenderedText(ref_pos, text, text_end, text_begin) local g = GImGui local window = g.CurrentWindow local prefix, suffix = g.LogNextPrefix, g.LogNextSuffix g.LogNextPrefix = nil; g.LogNextSuffix = nil text_begin = text_begin or 1 if not text_end then text_end = ImGui.FindRenderedTextEnd(text, text_end) end local log_new_line = ref_pos and (ref_pos.y > g.LogLinePosY + ImMax(g.Style.FramePadding.y, g.Style.ItemSpacing.y) + 1) if ref_pos then g.LogLinePosY = ref_pos.y end if log_new_line then ImGui.LogText("\n") g.LogLineFirstItem = true end if prefix then ImGui.LogRenderedText(ref_pos, prefix, #prefix + 1) end if g.LogDepthRef > window.DC.TreeDepth then g.LogDepthRef = window.DC.TreeDepth end local tree_depth = window.DC.TreeDepth - g.LogDepthRef local text_remaining = text_begin while true do local line_start = text_remaining local nl = string.find(text, "\n", line_start, true) local line_end = (nl and nl < text_end) and nl or text_end local is_last_line = (line_end == text_end) if line_start ~= line_end or not is_last_line then local indentation = g.LogLineFirstItem and tree_depth * 4 or 1 ImGui.LogText(string.rep(" ", indentation) .. string.sub(text, line_start, line_end - 1)) g.LogLineFirstItem = false if string.byte(text, line_end) == 10 then ImGui.LogText("\n") g.LogLineFirstItem = true end end if is_last_line then break end text_remaining = line_end + 1 end if suffix then ImGui.LogRenderedText(ref_pos, suffix, #suffix + 1) end end function ImGui.LogBegin(flags, auto_open_depth) local g = GImGui local window = g.CurrentWindow IM_ASSERT(g.LogEnabled == false) g.LogEnabled = true; g.ItemUnclipByLog = true g.LogFlags = flags g.LogWindow = window g.LogNextPrefix = nil; g.LogNextSuffix = nil g.LogDepthRef = window.DC.TreeDepth g.LogDepthToExpand = ((auto_open_depth or -1) >= 0) and auto_open_depth or g.LogDepthToExpandDefault g.LogLinePosY = FLT_MAX g.LogLineFirstItem = true g.LogBuffer = ImGuiTextBuffer() end function ImGui.LogSetNextTextDecoration(prefix, suffix) local g = GImGui g.LogNextPrefix = prefix g.LogNextSuffix = suffix end function ImGui.LogToTTY(auto_open_depth) local g = GImGui if g.LogEnabled then return end ImGui.LogBegin(ImGuiLogFlags.OutputTTY, auto_open_depth or -1) g.LogFile = "stdout" end function ImGui.LogToFile(auto_open_depth, filename) -- Roblox: no file access. Falls back to TTY (output window). ImGui.LogToTTY(auto_open_depth) end function ImGui.LogToClipboard(auto_open_depth) local g = GImGui if g.LogEnabled then return end ImGui.LogBegin(ImGuiLogFlags.OutputClipboard, auto_open_depth or -1) end function ImGui.LogToBuffer(auto_open_depth) local g = GImGui if g.LogEnabled then return end ImGui.LogBegin(ImGuiLogFlags.OutputBuffer, auto_open_depth or -1) end function ImGui.LogFinish() local g = GImGui if not g.LogEnabled then return end ImGui.LogText("\n") local out = bit32.band(g.LogFlags, ImGuiLogFlags.OutputMask_) if out == ImGuiLogFlags.OutputClipboard then if not g.LogBuffer:empty() then ImGui.SetClipboardText(g.LogBuffer:c_str()) end end g.LogEnabled = false; g.ItemUnclipByLog = false g.LogFlags = ImGuiLogFlags.None g.LogFile = nil g.LogBuffer = ImGuiTextBuffer() end function ImGui.LogButtons() local g = GImGui ImGui.PushID("LogButtons") local log_to_tty = ImGui.Button("Log To TTY"); ImGui.SameLine() local log_to_file = ImGui.Button("Log To File"); ImGui.SameLine() local log_to_clipboard = ImGui.Button("Log To Clipboard"); ImGui.SameLine() ImGui.PushItemFlag(ImGuiItemFlags.NoTabStop, true) ImGui.SetNextItemWidth(ImGui.CalcTextSize("999").x) g.LogDepthToExpandDefault = ImGui.SliderInt("Default Depth", g.LogDepthToExpandDefault, 0, 9, nil) ImGui.PopItemFlag() ImGui.PopID() if log_to_tty then ImGui.LogToTTY() end if log_to_file then ImGui.LogToFile() end if log_to_clipboard then ImGui.LogToClipboard() end end --- @param window ImGuiWindow --- @return ImGuiWindow? local function GetWindowForTitleDisplay(window) if window.DockNodeAsHost then return window.DockNodeAsHost.VisibleWindow else return window end end --- @param id ImGuiID @native function ImGui.KeepAliveID(id) local g = GImGui if g.ActiveId == id then g.ActiveIdIsAlive = id end if g.DeactivatedItemData.ID == id then g.DeactivatedItemData.IsAlive = true end end --- @param r_min ImVec2 --- @param r_max ImVec2 --- @param clip? bool @native function ImGui.IsMouseHoveringRect(r_min, r_max, clip) if clip == nil then clip = true end local g = GImGui -- [Luau] scalar version of: rect_clipped = ImRect(r_min, r_max); ClipWith(window.ClipRect); ContainsWithPad(); viewport Overlaps() local x0, y0, x1, y1 = r_min.x, r_min.y, r_max.x, r_max.y if clip then local cr = g.CurrentWindow.ClipRect local cmin, cmax = cr.Min, cr.Max x0 = math.max(x0, cmin.x); y0 = math.max(y0, cmin.y) x1 = math.min(x1, cmax.x); y1 = math.min(y1, cmax.y) end local p, pad = g.IO.MousePos, g.Style.TouchExtraPadding if not (p.x >= x0 - pad.x and p.y >= y0 - pad.y and p.x < x1 + pad.x and p.y < y1 + pad.y) then return false end local vp = g.MouseViewport local vx0, vy0 = vp.Pos.x, vp.Pos.y local vx1, vy1 = vx0 + vp.Size.x, vy0 + vp.Size.y if not (vx0 <= x1 and vx1 >= x0 and vy0 <= y1 and vy1 >= y0) then return false end return true end -- Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame. -- [Internal] This doesn't test if the button is pressed --- @param button ImGuiMouseButton --- @param lock_threshold? float function ImGui.IsMouseDragPastThreshold(button, lock_threshold) if lock_threshold == nil then lock_threshold = -1.0 end local g = GImGui IM_ASSERT(button >= 0 and button < 3) -- IM_COUNTOF(g.IO.MouseDown) if lock_threshold < 0.0 then lock_threshold = g.IO.MouseDragThreshold end return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold end --- @param button ImGuiMouseButton --- @param lock_threshold? float function ImGui.IsMouseDragging(button, lock_threshold) if lock_threshold == nil then lock_threshold = -1.0 end local g = GImGui IM_ASSERT(button >= 0 and button < 3) -- IM_COUNTOF(g.IO.MouseDown) if not g.IO.MouseDown[button] then return false end return ImGui.IsMouseDragPastThreshold(button, lock_threshold) end --- @param pos ImVec2 function ImGui.TeleportMousePos(pos) local g = GImGui ImVec2_Copy(g.IO.MousePos, pos) ImVec2_Copy(g.IO.MousePosPrev, pos) ImVec2_CopyV(g.IO.MouseDelta, 0.0, 0.0) g.IO.WantSetMousePos = true -- IMGUI_DEBUG_LOG_IO("TeleportMousePos: (%.1f,%.1f)", io.MousePos.x, io.MousePos.y) end --- @param id ImGuiID function ImGui.ItemHandleShortcut(id) local g = GImGui local flags = g.NextItemData.ShortcutFlags IM_ASSERT(bit32.band(flags, bit32.bnot(ImGuiInputFlags.SupportedBySetNextItemShortcut)) == 0) if bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.Disabled) ~= 0 then return end if bit32.band(flags, ImGuiInputFlags.Tooltip) ~= 0 then g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HasShortcut) g.LastItemData.Shortcut = g.NextItemData.Shortcut end if not ImGui.Shortcut(g.NextItemData.Shortcut, bit32.band(flags, ImGuiInputFlags.SupportedByShortcut), id) or g.NavActivateId ~= 0 then return end g.NavActivateId = id g.NavActivateFlags = bit32.bor(ImGuiActivateFlags.PreferInput, ImGuiActivateFlags.FromShortcut) g.NavActivateDownId = id g.NavActivatePressedId = id ImGui.NavHighlightActivated(id) end --- @param bb ImRect --- @param id ImGuiID --- @param nav_bb_arg? ImRect --- @param extra_flags? ImGuiItemFlags @native function ImGui.ItemAdd(bb, id, nav_bb_arg, extra_flags) if extra_flags == nil then extra_flags = 0 end local g = GImGui local window = g.CurrentWindow g.LastItemData.ID = id ImRect_Copy(g.LastItemData.Rect, bb) ImRect_Copy(g.LastItemData.NavRect, nav_bb_arg and nav_bb_arg or bb) g.LastItemData.ItemFlags = bit32.bor(g.CurrentItemFlags, g.NextItemData.ItemFlagsSet, extra_flags) g.LastItemData.StatusFlags = ImGuiItemStatusFlags.None if id ~= 0 then ImGui.KeepAliveID(id) if bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.NoNav) == 0 then window.DC.NavLayersActiveMaskNext = bit32.bor(window.DC.NavLayersActiveMaskNext, bit32.lshift(1, window.DC.NavLayerCurrent)) if g.NavId == id or g.NavAnyRequest then if g.NavWindow.RootWindowForNav == window.RootWindowForNav then if window == g.NavWindow or bit32.band(bit32.bor(window.ChildFlags, g.NavWindow.ChildFlags), ImGuiChildFlags.NavFlattened) ~= 0 then ImGui.NavProcessItem() end end end end if bit32.band(g.NextItemData.HasFlags, ImGuiNextItemDataFlags.HasShortcut) ~= 0 then ImGui.ItemHandleShortcut(id) end end g.NextItemData.HasFlags = ImGuiNextItemDataFlags.None g.NextItemData.ItemFlagsSet = ImGuiItemFlags.None local bmin, bmax, cr = bb.Min, bb.Max, window.ClipRect local is_rect_visible = bmin.x <= cr.Max.x and bmax.x >= cr.Min.x and bmin.y <= cr.Max.y and bmax.y >= cr.Min.y if not is_rect_visible then if id == 0 or not (id == g.ActiveId or id == g.ActiveIdPreviousFrame or id == g.NavId or id == g.NavActivateId or g.ItemUnclipByLog) then return false end end if id ~= 0 and g.DeactivatedItemData.ID == id then g.DeactivatedItemData.ElapseFrame = g.FrameCount end if is_rect_visible then g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.Visible) end if ImGui.IsMouseHoveringRect(bb.Min, bb.Max) then g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredRect) end return true end --- @param size_or_bb ImVec2|ImRect --- @param text_baseline_y? float @native function ImGui.ItemSize(size_or_bb, text_baseline_y) if text_baseline_y == nil then text_baseline_y = -1.0 end local size_x, size_y local bb_min = size_or_bb.Min if bb_min then --- @cast size_or_bb ImRect size_x = size_or_bb.Max.x - bb_min.x; size_y = size_or_bb.Max.y - bb_min.y else --- @cast size_or_bb ImVec2 size_x = size_or_bb.x; size_y = size_or_bb.y end local g = GImGui local window = g.CurrentWindow if window.SkipItems then return end local dc = window.DC local mathMax = math.max local offset_to_match_baseline_y if text_baseline_y >= 0 then offset_to_match_baseline_y = mathMax(0, dc.CurrLineTextBaseOffset - text_baseline_y) else offset_to_match_baseline_y = 0 end local cursor, prev_line, max_pos = dc.CursorPos, dc.CursorPosPrevLine, dc.CursorMaxPos local line_y1 if dc.IsSameLine then line_y1 = prev_line.y else line_y1 = cursor.y end local line_height = mathMax(dc.CurrLineSize.y, cursor.y - line_y1 + size_y + offset_to_match_baseline_y) local spacing_y = g.Style.ItemSpacing.y prev_line.x = cursor.x + size_x prev_line.y = line_y1 cursor.x = math.floor(window.Pos.x + dc.Indent.x + dc.ColumnsOffset.x) cursor.y = math.floor(line_y1 + line_height + spacing_y) max_pos.x = mathMax(max_pos.x, prev_line.x) max_pos.y = mathMax(max_pos.y, cursor.y - spacing_y) dc.PrevLineSize.y = line_height dc.CurrLineSize.y = 0 dc.PrevLineTextBaseOffset = mathMax(dc.CurrLineTextBaseOffset, text_baseline_y) dc.CurrLineTextBaseOffset = 0 dc.IsSetPos = false dc.IsSameLine = false --- Horizontal layout mode if (dc.LayoutType == ImGuiLayoutType.Horizontal) then ImGui.SameLine() end end --- @param offset_from_start_x float? --- @param spacing_w float? @native function ImGui.SameLine(offset_from_start_x, spacing_w) if offset_from_start_x == nil then offset_from_start_x = 0.0 end if spacing_w == nil then spacing_w = -1.0 end local g = GImGui local window = g.CurrentWindow if window.SkipItems then return end if offset_from_start_x ~= 0.0 then if spacing_w < 0.0 then spacing_w = 0.0 end window.DC.CursorPos.x = window.Pos.x - window.Scroll.x + offset_from_start_x + spacing_w + window.DC.GroupOffset.x + window.DC.ColumnsOffset.x window.DC.CursorPos.y = window.DC.CursorPosPrevLine.y else if spacing_w < 0.0 then spacing_w = g.Style.ItemSpacing.x end window.DC.CursorPos.x = window.DC.CursorPosPrevLine.x + spacing_w window.DC.CursorPos.y = window.DC.CursorPosPrevLine.y end ImVec2_Copy(window.DC.CurrLineSize, window.DC.PrevLineSize) window.DC.CurrLineTextBaseOffset = window.DC.PrevLineTextBaseOffset window.DC.IsSameLine = true end --- @param indent_w? float function ImGui.Indent(indent_w) if indent_w == nil then indent_w = 0.0 end local g = GImGui local window = ImGui.GetCurrentWindow() if indent_w ~= 0.0 then window.DC.Indent.x = window.DC.Indent.x + indent_w else window.DC.Indent.x = window.DC.Indent.x + g.Style.IndentSpacing end window.DC.CursorPos.x = window.Pos.x + window.DC.Indent.x + window.DC.ColumnsOffset.x end --- @param indent_w? float function ImGui.Unindent(indent_w) if indent_w == nil then indent_w = 0.0 end local g = GImGui local window = ImGui.GetCurrentWindow() if indent_w ~= 0.0 then window.DC.Indent.x = window.DC.Indent.x - indent_w else window.DC.Indent.x = window.DC.Indent.x - g.Style.IndentSpacing end window.DC.CursorPos.x = window.Pos.x + window.DC.Indent.x + window.DC.ColumnsOffset.x end -- Affect large frame+labels widgets only --- @param item_width float function ImGui.SetNextItemWidth(item_width) local g = GImGui g.NextItemData.HasFlags = bit32.bor(g.NextItemData.HasFlags, ImGuiNextItemDataFlags.HasWidth) g.NextItemData.Width = item_width end --- @param item_width float function ImGui.PushItemWidth(item_width) local g = GImGui local window = g.CurrentWindow window.DC.ItemWidthStack:push_back(window.DC.ItemWidth) window.DC.ItemWidth = (item_width == 0.0) and window.DC.ItemWidthDefault or item_width g.NextItemData.HasFlags = bit32.band(g.NextItemData.HasFlags, bit32.bnot(ImGuiNextItemDataFlags.HasWidth)) end function ImGui.PopItemWidth() local g = GImGui local window = g.CurrentWindow if window.DC.ItemWidthStack.Size <= 0 then IM_ASSERT_USER_ERROR(false, "Calling PopItemWidth() too many times!") return end window.DC.ItemWidth = window.DC.ItemWidthStack:back() window.DC.ItemWidthStack:pop_back() end --- @param components int --- @param w_full float function ImGui.PushMultiItemsWidths(components, w_full) local g = GImGui local window = g.CurrentWindow IM_ASSERT(components > 0) local style = g.Style window.DC.ItemWidthStack:push_back(window.DC.ItemWidth) local w_items = w_full - style.ItemInnerSpacing.x * (components - 1) local prev_split = w_items for i = components - 1, 1, -1 do local next_split = IM_TRUNC(w_items * i / components) window.DC.ItemWidthStack:push_back(ImMax(prev_split - next_split, 1.0)) prev_split = next_split end window.DC.ItemWidth = ImMax(prev_split, 1.0) g.NextItemData.HasFlags = bit32.band(g.NextItemData.HasFlags, bit32.bnot(ImGuiNextItemDataFlags.HasWidth)) end --- @return float function ImGui.GetFrameHeight() local g = GImGui return g.FontSize + g.Style.FramePadding.y * 2.0 end --- @return ImVec2 --- @nodiscard @native function ImGui.GetContentRegionAvail() local g = GImGui local window = g.CurrentWindow local mx if window.DC.CurrentColumns or g.CurrentTable then mx = window.WorkRect.Max else mx = window.ContentRegionRect.Max end return ImVec2(mx.x - window.DC.CursorPos.x, mx.y - window.DC.CursorPos.y) end --- @return ImVec2 --- @nodiscard function ImGui.GetCursorScreenPos() local window = ImGui.GetCurrentWindowRead() local ret = ImVec2() ImVec2_Copy(ret, window.DC.CursorPos) return ret end --- @param pos ImVec2 function ImGui.SetCursorScreenPos(pos) local window = ImGui.GetCurrentWindow() ImVec2_Copy(window.DC.CursorPos, pos) window.DC.IsSetPos = true end -- [window-local] cursor position, in window coordinates (relative to window position, including scrolling) --- @return ImVec2 function ImGui.GetCursorPos() local window = ImGui.GetCurrentWindowRead() return ImVec2(window.DC.CursorPos.x - window.Pos.x + window.Scroll.x, window.DC.CursorPos.y - window.Pos.y + window.Scroll.y) end function ImGui.GetCursorPosX() local window = ImGui.GetCurrentWindowRead() return window.DC.CursorPos.x - window.Pos.x + window.Scroll.x end function ImGui.GetCursorPosY() local window = ImGui.GetCurrentWindowRead() return window.DC.CursorPos.y - window.Pos.y + window.Scroll.y end --- @param local_pos ImVec2 function ImGui.SetCursorPos(local_pos) local window = ImGui.GetCurrentWindow() ImVec2_CopyV(window.DC.CursorPos, window.Pos.x - window.Scroll.x + local_pos.x, window.Pos.y - window.Scroll.y + local_pos.y) window.DC.IsSetPos = true end --- @param x float function ImGui.SetCursorPosX(x) local window = ImGui.GetCurrentWindow() window.DC.CursorPos.x = window.Pos.x - window.Scroll.x + x window.DC.IsSetPos = true end --- @param y float function ImGui.SetCursorPosY(y) local window = ImGui.GetCurrentWindow() window.DC.CursorPos.y = window.Pos.y - window.Scroll.y + y window.DC.IsSetPos = true end --- @return ImVec2 function ImGui.GetCursorStartPos() local window = ImGui.GetCurrentWindowRead() return window.DC.CursorStartPos - window.Pos end function ImGui.GetFrameHeightWithSpacing() local g = GImGui return g.FontSize + g.Style.FramePadding.y * 2.0 + g.Style.ItemSpacing.y end function ImGui.GetTextLineHeightWithSpacing() local g = GImGui return g.FontSize + g.Style.ItemSpacing.y end --- @return ImVec2 function ImGui.GetContentRegionMax() local window = GImGui.CurrentWindow return ImGui.GetContentRegionAvail() + window.DC.CursorPos - window.Pos end --- @return ImVec2 function ImGui.GetWindowContentRegionMin() local window = GImGui.CurrentWindow return window.ContentRegionRect.Min - window.Pos end --- @return ImVec2 function ImGui.GetWindowContentRegionMax() local window = GImGui.CurrentWindow return window.ContentRegionRect.Max - window.Pos end -- Overloads: IsRectVisible(size) / IsRectVisible(rect_min, rect_max) function ImGui.IsRectVisible(a, b) local window = GImGui.CurrentWindow if b == nil then return window.ClipRect:Overlaps(ImRect(window.DC.CursorPos, window.DC.CursorPos + a)) end return window.ClipRect:Overlaps(ImRect(a, b)) end -- Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.) -- Groups are currently a mishmash of functionalities which should perhaps be clarified and separated. -- FIXME-OPT: Could we safely early out on ->SkipItems? @native function ImGui.BeginGroup() local g = GImGui local window = g.CurrentWindow local group_data = ImGuiGroupData() group_data.WindowID = window.ID ImVec2_Copy(group_data.BackupCursorPos, window.DC.CursorPos) ImVec2_Copy(group_data.BackupCursorPosPrevLine, window.DC.CursorPosPrevLine) ImVec2_Copy(group_data.BackupCursorMaxPos, window.DC.CursorMaxPos) ImVec1_Copy(group_data.BackupIndent, window.DC.Indent) ImVec1_Copy(group_data.BackupGroupOffset, window.DC.GroupOffset) ImVec2_Copy(group_data.BackupCurrLineSize, window.DC.CurrLineSize) group_data.BackupCurrLineTextBaseOffset = window.DC.CurrLineTextBaseOffset group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive group_data.BackupHoveredIdIsAlive = (g.HoveredId ~= 0) group_data.BackupIsSameLine = window.DC.IsSameLine group_data.BackupAnyIdHasBeenEditedThisFrame = g.AnyIdHasBeenEditedThisFrame group_data.BackupDeactivatedIdIsAlive = g.DeactivatedItemData.IsAlive group_data.EmitItem = true g.GroupStack:push_back(group_data) window.DC.GroupOffset.x = window.DC.CursorPos.x - window.Pos.x - window.DC.ColumnsOffset.x ImVec1_Copy(window.DC.Indent, window.DC.GroupOffset) ImVec2_Copy(window.DC.CursorMaxPos, window.DC.CursorPos) ImVec2_CopyV(window.DC.CurrLineSize, 0.0, 0.0) if (g.LogEnabled) then g.LogLinePosY = -FLT_MAX end end @native function ImGui.EndGroup() local g = GImGui local window = g.CurrentWindow IM_ASSERT(g.GroupStack.Size > 0) -- Mismatched BeginGroup()/EndGroup() calls local group_data = g.GroupStack:back() IM_ASSERT(group_data.WindowID == window.ID) -- EndGroup() in wrong window? if (window.DC.IsSetPos) then -- TODO: ImGui.ErrorCheckUsingSetCursorPosToExtendParentBoundaries() end local group_bb = ImRect(group_data.BackupCursorPos, ImMax(ImMax(window.DC.CursorMaxPos, g.LastItemData.Rect.Max), group_data.BackupCursorPos)) ImVec2_Copy(window.DC.CursorPos, group_data.BackupCursorPos) ImVec2_Copy(window.DC.CursorPosPrevLine, group_data.BackupCursorPosPrevLine) ImVec2_Copy(window.DC.CursorMaxPos, ImMax(group_data.BackupCursorMaxPos, group_bb.Max)) ImVec1_Copy(window.DC.Indent, group_data.BackupIndent) ImVec1_Copy(window.DC.GroupOffset, group_data.BackupGroupOffset) ImVec2_Copy(window.DC.CurrLineSize, group_data.BackupCurrLineSize) window.DC.CurrLineTextBaseOffset = group_data.BackupCurrLineTextBaseOffset window.DC.IsSameLine = group_data.BackupIsSameLine if (g.LogEnabled) then g.LogLinePosY = -FLT_MAX -- To enforce a carriage return end if (not group_data.EmitItem) then g.GroupStack:pop_back() return end window.DC.CurrLineTextBaseOffset = ImMax(window.DC.PrevLineTextBaseOffset, group_data.BackupCurrLineTextBaseOffset) -- FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now ImGui.ItemSize(group_bb:GetSize()) ImGui.ItemAdd(group_bb, 0, nil, ImGuiItemFlags.NoTabStop) -- If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group. -- It would be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets. -- Also if you grep for LastItemId you'll notice it is only used in that context. -- (The two tests not the same because ActiveIdIsAlive is an ID itself, in order to be able to handle ActiveId being overwritten during the frame.) local group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive ~= g.ActiveId) and (g.ActiveIdIsAlive == g.ActiveId) and g.ActiveId local group_contains_deactivated_id = (group_data.BackupDeactivatedIdIsAlive == false) and (g.DeactivatedItemData.IsAlive == true) if group_contains_curr_active_id then g.LastItemData.ID = g.ActiveId elseif group_contains_deactivated_id then g.LastItemData.ID = g.DeactivatedItemData.ID end ImRect_Copy(g.LastItemData.Rect, group_bb) -- Forward Hovered flag local group_contains_curr_hovered_id = (group_data.BackupHoveredIdIsAlive == false) and g.HoveredId ~= 0 if group_contains_curr_hovered_id then g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredWindow) end -- Forward Edited flag if g.AnyIdHasBeenEditedThisFrame and not group_data.BackupAnyIdHasBeenEditedThisFrame then g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.Edited) end -- Forward Deactivated flag g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HasDeactivated) if group_contains_deactivated_id then g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.Deactivated) end g.GroupStack:pop_back() if g.DebugShowGroupRects then window.DrawList:AddRect(group_bb.Min, group_bb.Max, IM_COL32(255, 0, 255, 255)) -- [Debug] end end @native function ImGui.CalcItemWidth() local g = GImGui local window = g.CurrentWindow local w if bit32.band(g.NextItemData.HasFlags, ImGuiNextItemDataFlags.HasWidth) ~= 0 then w = g.NextItemData.Width else w = window.DC.ItemWidth end if w < 0.0 then local region_avail_x = ImGui.GetContentRegionAvail().x w = ImMax(1.0, region_avail_x + w) end w = IM_TRUNC(w) return w end --- @param size ImVec2 --- @param default_w float --- @param default_h float --- @return ImVec2 function ImGui.CalcItemSize(size, default_w, default_h) local avail if size.x < 0.0 or size.y < 0.0 then avail = ImGui.GetContentRegionAvail() end if size.x == 0.0 then size.x = default_w elseif size.x < 0.0 then size.x = ImMax(4.0, avail.x + size.x) -- size.x is negative here so we are subtracting end if size.y == 0.0 then size.y = default_h elseif size.y < 0.0 then size.y = ImMax(4.0, avail.y + size.y) -- size.y is negative here so we are subtracting end return size end function ImGui.GetTextLineHeight() local g = GImGui return g.FontSize end function ImGui.IsItemActive() local g = GImGui if g.ActiveId ~= 0 then return g.ActiveId == g.LastItemData.ID end return false end function ImGui.IsItemFocused() local g = GImGui if g.NavId ~= g.LastItemData.ID or g.NavId == 0 then return false end local window = g.CurrentWindow if g.LastItemData.ID == window.ID and window.WriteAccessed then return false end return true end function ImGui.IsItemDeactivated() local g = GImGui if bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HasDeactivated) ~= 0 then return bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.Deactivated) ~= 0 end return g.DeactivatedItemData.ID == g.LastItemData.ID and g.LastItemData.ID ~= 0 and g.DeactivatedItemData.ElapseFrame >= g.FrameCount end function ImGui.IsItemDeactivatedAfterEdit() local g = GImGui return ImGui.IsItemDeactivated() and g.DeactivatedItemData.HasBeenEditedBefore end function ImGui.IsItemActivated() local g = GImGui if g.ActiveId ~= 0 then if g.ActiveId == g.LastItemData.ID and g.ActiveIdPreviousFrame ~= g.LastItemData.ID then return true end end return false end -- Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()! --- @param mouse_button? ImGuiMouseButton function ImGui.IsItemClicked(mouse_button) if mouse_button == nil then mouse_button = 0 end return ImGui.IsMouseClicked(mouse_button) and ImGui.IsItemHovered(ImGuiHoveredFlags.None) end function ImGui.IsItemToggledOpen() return bit32.band(GImGui.LastItemData.StatusFlags, ImGuiItemStatusFlags.ToggledOpen) ~= 0 end function ImGui.IsItemToggledSelection() local g = GImGui IM_ASSERT(g.CurrentMultiSelect ~= nil) -- Can only be used inside a BeginMultiSelect()/EndMultiSelect() return bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.ToggledSelection) ~= 0 end function ImGui.IsAnyItemHovered() local g = GImGui return g.HoveredId ~= 0 or g.HoveredIdPreviousFrame ~= 0 end function ImGui.IsAnyItemActive() return GImGui.ActiveId ~= 0 end function ImGui.IsAnyItemFocused() local g = GImGui return g.NavId ~= 0 and g.NavCursorVisible end function ImGui.IsItemVisible() return bit32.band(GImGui.LastItemData.StatusFlags, ImGuiItemStatusFlags.Visible) ~= 0 end function ImGui.IsItemEdited() return bit32.band(GImGui.LastItemData.StatusFlags, ImGuiItemStatusFlags.Edited) ~= 0 end -- Allow next item to be overlapped by subsequent items. function ImGui.SetNextItemAllowOverlap() local g = GImGui g.NextItemData.ItemFlagsSet = bit32.bor(g.NextItemData.ItemFlagsSet, ImGuiItemFlags.AllowOverlap) end function ImGui.GetItemID() return GImGui.LastItemData.ID end function ImGui.GetItemFlags() return GImGui.LastItemData.ItemFlags end --- @return ImVec2 function ImGui.GetItemRectSize() return GImGui.LastItemData.Rect:GetSize() end function ImGui.GetHoveredID() local g = GImGui return (g.HoveredId ~= 0) and g.HoveredId or g.HoveredIdPreviousFrame end --------------------------------------------------------------------------------------- -- [SECTION] WINDOW FOCUS --------------------------------------------------------------------------------------- --- @param window? ImGuiWindow --- @param flags? ImGuiFocusRequestFlags function ImGui.FocusWindow(window, flags) if flags == nil then flags = 0 end local g = GImGui if bit32.band(flags, ImGuiFocusRequestFlags.UnlessBelowModal) ~= 0 and g.NavWindow ~= window then local blocking_modal = ImGui.FindBlockingModal(window) if blocking_modal then -- IMGUI_DEBUG_LOG_FOCUS("[focus] FocusWindow(\"%s\", UnlessBelowModal): prevented by \"%s\".", window ? window->Name : "", blocking_modal->Name) if window and window == window.RootWindow and bit32.band(window.Flags, ImGuiWindowFlags.NoBringToFrontOnFocus) == 0 then ImGui.BringWindowToDisplayBehind(window, blocking_modal) end ImGui.ClosePopupsOverWindow(ImGui.GetTopMostPopupModal(), false) return end end if bit32.band(flags, ImGuiFocusRequestFlags.RestoreFocusedChild) ~= 0 and window ~= nil then window = ImGui.NavRestoreLastChildNavWindow(window) end if g.NavWindow ~= window then ImGui.SetNavWindow(window) if window and g.NavHighlightItemUnderNav then g.NavMousePosDirty = true end g.NavId = window and window.NavLastIds[0] or 0 g.NavLayer = ImGuiNavLayer.Main ImGui.SetNavFocusScope(window and window.NavRootFocusScopeId or 0) g.NavIdIsAlive = false g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid -- Close popups if any ImGui.ClosePopupsOverWindow(window, false) end IM_ASSERT(window == nil or window.RootWindowDockTree ~= nil) local focus_front_window = window and window.RootWindow or nil local display_front_window = window and window.RootWindowDockTree or nil local dock_node = window and window.DockNode or nil local active_id_window_is_dock_node_host = g.ActiveIdWindow and dock_node and dock_node.HostWindow == g.ActiveIdWindow if g.ActiveId ~= 0 and g.ActiveIdWindow and g.ActiveIdWindow.RootWindow ~= focus_front_window then if not g.ActiveIdNoClearOnFocusLoss and not active_id_window_is_dock_node_host then ImGui.ClearActiveID() end end if not window then return end window.LastFrameJustFocused = g.FrameCount ImGui.BringWindowToFocusFront(focus_front_window) if bit32.band(bit32.bor(window.Flags, focus_front_window.Flags, display_front_window.Flags), ImGuiWindowFlags.NoBringToFrontOnFocus) == 0 then ImGui.BringWindowToDisplayFront(display_front_window) end end --- @param window ImGuiWindow --- @return int function ImGui.FindWindowFocusIndex(window) local g = GImGui -- IM_UNUSED(g) local order = window.FocusOrder IM_ASSERT(window.RootWindow == window) -- No child window (not testing _ChildWindow because of docking) IM_ASSERT(g.WindowsFocusOrder.Data[order] == window) return order end --- @param window ImGuiWindow --- @return bool function ImGui.IsWindowNavFocusable(window) return window.WasActive and window == window.RootWindow and bit32.band(window.Flags, ImGuiWindowFlags.NoNavFocus) == 0 end --- @param i_start int --- @param i_stop int --- @param dir int --- @return ImGuiWindow? function ImGui.FindWindowNavFocusable(i_start, i_stop, dir) local g = GImGui local i = i_start while i >= 1 and i <= g.WindowsFocusOrder.Size and i ~= i_stop do local window = g.WindowsFocusOrder.Data[i] if ImGui.IsWindowNavFocusable(window) then return window end i = i + dir end return nil end function ImGui.UpdateWindowInFocusOrderList(window, just_created, new_flags) local g = GImGui local new_is_explicit_child = (bit32.band(new_flags, ImGuiWindowFlags.ChildWindow) ~= 0) and ((bit32.band(new_flags, ImGuiWindowFlags.Popup) == 0) or (bit32.band(new_flags, ImGuiWindowFlags.ChildMenu) ~= 0)) local child_flag_changed = (new_is_explicit_child ~= window.IsExplicitChild) if (just_created or child_flag_changed) and not new_is_explicit_child then IM_ASSERT(not g.WindowsFocusOrder:contains(window)) g.WindowsFocusOrder:push_back(window) window.FocusOrder = g.WindowsFocusOrder.Size elseif not just_created and child_flag_changed and new_is_explicit_child then IM_ASSERT(g.WindowsFocusOrder.Data[window.FocusOrder] == window) for n = window.FocusOrder + 1, g.WindowsFocusOrder.Size do g.WindowsFocusOrder.Data[n].FocusOrder = g.WindowsFocusOrder.Data[n].FocusOrder - 1 end g.WindowsFocusOrder:erase(window.FocusOrder) window.FocusOrder = -1 end window.IsExplicitChild = new_is_explicit_child end --- @param window? ImGuiWindow function ImGui.BringWindowToFocusFront(window) local g = GImGui IM_ASSERT(window == window.RootWindow) local cur_order = window.FocusOrder IM_ASSERT(g.WindowsFocusOrder.Data[cur_order] == window) if g.WindowsFocusOrder:back() == window then return end local new_order = g.WindowsFocusOrder.Size for n = cur_order, new_order - 1 do g.WindowsFocusOrder.Data[n] = g.WindowsFocusOrder.Data[n + 1] g.WindowsFocusOrder.Data[n].FocusOrder = g.WindowsFocusOrder.Data[n].FocusOrder - 1 IM_ASSERT(g.WindowsFocusOrder.Data[n].FocusOrder == n) end g.WindowsFocusOrder.Data[new_order] = window window.FocusOrder = new_order end --- @param window? ImGuiWindow function ImGui.BringWindowToDisplayFront(window) local g = GImGui local current_front_window = g.Windows:back() if current_front_window == window or current_front_window.RootWindowDockTree == window then return end for i, this_window in g.Windows:iter() do if this_window == window then g.Windows:erase(i) break end end g.Windows:push_back(window) end --- @param window ImGuiWindow --- @param behind_window ImGuiWindow function ImGui.BringWindowToDisplayBehind(window, behind_window) IM_ASSERT(window ~= nil and behind_window ~= nil) local g = GImGui window = window.RootWindow behind_window = behind_window.RootWindow -- TODO: end --- @param under_this_window? ImGuiWindow --- @param ignore_window? ImGuiWindow --- @param filter_viewport? ImGuiViewport --- @param flags ImGuiFocusRequestFlags function ImGui.FocusTopMostWindowUnderOne(under_this_window, ignore_window, filter_viewport, flags) local g = GImGui local start_idx = g.WindowsFocusOrder.Size if under_this_window ~= nil then -- Aim at root window behind us, if we are in a child window that's our own root (see #4640) local offset = -1 while bit32.band(under_this_window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 do under_this_window = under_this_window.ParentWindow offset = 0 end --- @cast under_this_window ImGuiWindow start_idx = ImGui.FindWindowFocusIndex(under_this_window) + offset end for i = start_idx, 1, -1 do repeat local window = g.WindowsFocusOrder.Data[i] if window == ignore_window or not window.WasActive then break end if filter_viewport ~= nil and window.Viewport ~= filter_viewport then break end if bit32.band(window.Flags, bit32.bor(ImGuiWindowFlags.NoMouseInputs, ImGuiWindowFlags.NoNavInputs)) ~= bit32.bor(ImGuiWindowFlags.NoMouseInputs, ImGuiWindowFlags.NoNavInputs) then ImGui.FocusWindow(window, flags) return end until true end ImGui.FocusWindow(nil, flags) end --- @param id ImGuiID --- @param window ImGuiWindow function ImGui.SetFocusID(id, window) local g = GImGui IM_ASSERT(id ~= 0) if g.NavWindow ~= window then ImGui.SetNavWindow(window) end local nav_layer = window.DC.NavLayerCurrent g.NavId = id g.NavLayer = nav_layer ImGui.SetNavFocusScope(g.CurrentFocusScopeId) window.NavLastIds[nav_layer] = id if g.LastItemData.ID == id then ImRect_Copy(window.NavRectRel[nav_layer], ImGui.WindowRectAbsToRel(window, g.LastItemData.NavRect)) end g.NavIdItemFlags = (g.LastItemData.ID == id) and g.LastItemData.ItemFlags or ImGuiItemFlags.None if id == g.ActiveIdIsAlive then g.NavIdIsAlive = true end if g.ActiveIdSource == ImGuiInputSource.Keyboard or g.ActiveIdSource == ImGuiInputSource.Gamepad then g.NavHighlightItemUnderNav = true elseif g.IO.ConfigNavCursorVisibleAuto then g.NavCursorVisible = false end ImGui.NavClearPreferredPosForAxis(ImGuiAxis.X) ImGui.NavClearPreferredPosForAxis(ImGuiAxis.Y) end function ImGui.StopMouseMovingWindow() local g = GImGui local window = g.MovingWindow if window and window.Viewport then if bit32.band(g.ConfigFlagsCurrFrame, ImGuiConfigFlags.ViewportsEnable) ~= 0 then ImGui.UpdateTryMergeWindowIntoHostViewport(window.RootWindowDockTree, g.MouseViewport) end if (not ImGui.IsDragDropPayloadBeingAccepted()) then g.MouseViewport = window.Viewport end local window_can_use_inputs = bit32.band(window.Flags, ImGuiWindowFlags.NoMouseInputs) == 0 or bit32.band(window.Flags, ImGuiWindowFlags.NoNavInputs) == 0 if window_can_use_inputs then window.Viewport.Flags = bit32.band(window.Viewport.Flags, bit32.bnot(ImGuiViewportFlags.NoInputs)) end end g.MovingWindow = nil end --- @param id ImGuiID --- @param window? ImGuiWindow function ImGui.SetActiveID(id, window) local g = GImGui if g.ActiveId ~= 0 then local deactivated_data = g.DeactivatedItemData deactivated_data.ID = g.ActiveId deactivated_data.ElapseFrame = (g.LastItemData.ID == g.ActiveId) and g.FrameCount or g.FrameCount + 1 -- FIXME: OK to use LastItemData? deactivated_data.HasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore deactivated_data.IsAlive = (g.ActiveIdIsAlive == g.ActiveId) if g.InputTextState.ID == g.ActiveId then ImGui.InputTextDeactivateHook(g.ActiveId) end if g.MovingWindow and (g.ActiveId == g.MovingWindow.MoveId) then print("SetActiveID() cancel MovingWindow") ImGui.StopMouseMovingWindow() end end g.ActiveIdIsJustActivated = (g.ActiveId ~= id) if (g.ActiveIdIsJustActivated) then -- IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() 0x%08X in \"%s\"%*s(previously 0x%08X in \"%s\")\n", id, window ? window->Name : "", -- ImMax(0, 20 - (int)(window ? strlen(window->Name) : 0)), "", g.ActiveId, g.ActiveIdWindow ? g.ActiveIdWindow->Name : ""); g.ActiveIdTimer = 0.0 g.ActiveIdHasBeenPressedBefore = false g.ActiveIdHasBeenEditedBefore = false g.ActiveIdMouseButton = -1 if id ~= 0 then g.LastActiveId = id g.LastActiveIdTimer = 0.0 end end g.ActiveId = id g.ActiveIdAllowOverlap = false g.ActiveIdNoClearOnFocusLoss = false g.ActiveIdWindow = window g.ActiveIdHasBeenEditedThisFrame = false g.ActiveIdFromShortcut = false g.ActiveIdDisabledId = 0 if id ~= 0 then g.ActiveIdIsAlive = id if g.NavActivateId == id or g.NavJustMovedToId == id then g.ActiveIdSource = g.NavInputSource else g.ActiveIdSource = ImGuiInputSource.Mouse end IM_ASSERT(g.ActiveIdSource ~= ImGuiInputSource.None) end g.ActiveIdUsingNavDirMask = 0x00 g.ActiveIdUsingAllKeyboardKeys = false end function ImGui.ClearActiveID() ImGui.SetActiveID(0, nil) end --- @param str_id string|int @native function ImGui.PushID(str_id) local g = GImGui local window = g.CurrentWindow local id = window:GetID(str_id) window.IDStack:push_back(id) end --- @param id ImGuiID function ImGui.PushOverrideID(id) local g = GImGui local window = g.CurrentWindow if g.DebugHookIdInfoId == id and id ~= 0 then ImGui.DebugHookIdInfo(id, ImGuiDataType.ID, nil) end window.IDStack:push_back(id) end @native function ImGui.PopID() local window = GImGui.CurrentWindow IM_ASSERT_USER_ERROR_RET(window.IDStack.Size > 1, "Calling PopID() too many times!") window.IDStack:pop_back() end --- @param id string|int --- @return ImGuiID @native function MT.ImGuiWindow:GetID(id) local seed = self.IDStack:back() local h if type(id) == "string" then h = ImHashStr(id, nil, seed) else --- @cast id int h = ImHashData(id, -1, seed) end local g = GImGui if g.DebugHookIdInfoId == h and h ~= 0 then ImGui.DebugHookIdInfo(h, (type(id) == "string") and ImGuiDataType.String or ImGuiDataType.S32, id) end return h end -- This is only used in rare/specific situations to manufacture an ID out of nowhere -- FIXME: Consider instead storing last non-zero ID + count of successive zero-ID, and combine those? --- @param p_abs ImVec2 --- @return ImGuiID function MT.ImGuiWindow:GetIDFromPos(p_abs) local seed = self.IDStack:back() local p_rel = ImGui.WindowPosAbsToRel(self, p_abs) return ImHashData(p_rel, 2, seed) end -- " --- @param r_abs ImRect --- @return ImGuiID function MT.ImGuiWindow:GetIDFromRectangle(r_abs) local seed = self.IDStack:back() local r_rel = ImGui.WindowRectAbsToRel(self, r_abs) return ImHashData({ math.floor(r_rel.Min.x), math.floor(r_rel.Min.y), math.floor(r_rel.Max.x), math.floor(r_rel.Max.y) }, 4, seed) end -- Overloads: GetID(str_id) / GetID(str_id_begin, str_id_end) / GetID(int_id) --- @param str_or_int string|int --- @param str_end? int # exclusive end (1-based byte position) --- @return ImGuiID function ImGui.GetID(str_or_int, str_end) local window = GImGui.CurrentWindow if str_end ~= nil and type(str_or_int) == "string" then return ImHashStr(str_or_int, str_end - 1, window.IDStack:back()) end return window:GetID(str_or_int) end -- Overloads: GetIDWithSeed(str, str_end, seed) / GetIDWithSeed(n, seed) function ImGui.GetIDWithSeed(a, b, c) local id if type(a) == "number" then id = ImHashData(a, -1, b) else id = ImHashStr(a, b and (b - 1) or nil, c) end if GImGui.DebugHookIdInfoId == id and id ~= 0 then ImGui.DebugHookIdInfo(id, (type(a) == "number") and ImGuiDataType.S32 or ImGuiDataType.String, (b and type(a) == "string") and string.sub(a, 1, b - 1) or a) end return id end --- @param id ImGuiID @native function ImGui.SetHoveredID(id) local g = GImGui g.HoveredId = id g.HoveredIdAllowOverlap = false if id ~= 0 and g.HoveredIdPreviousFrame ~= id then g.HoveredIdTimer = 0.0 g.HoveredIdNotActiveTimer = 0.0 end end --- @param flags ImGuiHoveredFlags --- @return float local function CalcDelayFromHoveredFlags(flags) local g = GImGui if bit32.band(flags, ImGuiHoveredFlags.DelayNormal) ~= 0 then return g.Style.HoverDelayNormal end if bit32.band(flags, ImGuiHoveredFlags.DelayShort) ~= 0 then return g.Style.HoverDelayShort end return 0.0 end --- @param user_flags ImGuiHoveredFlags --- @param shared_flags ImGuiHoveredFlags local function ApplyHoverFlagsForTooltip(user_flags, shared_flags) if bit32.band(user_flags, bit32.bor(ImGuiHoveredFlags.DelayNone, ImGuiHoveredFlags.DelayShort, ImGuiHoveredFlags.DelayNormal)) ~= 0 then shared_flags = bit32.band(shared_flags, bit32.bnot(bit32.bor(ImGuiHoveredFlags.DelayNone, ImGuiHoveredFlags.DelayShort, ImGuiHoveredFlags.DelayNormal))) end return bit32.bor(user_flags, shared_flags) end --- @param id ImGuiID function ImGui.MarkItemEdited(id) -- This marking is to be able to provide info for IsItemDeactivatedAfterEdit(). -- ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data. local g = GImGui g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.EditedInternal) if bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.NoMarkEdited) ~= 0 then return end g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.Edited) if g.ActiveId == id or g.ActiveId == 0 then -- FIXME: Can't we fully rely on LastItemData yet? g.AnyIdHasBeenEditedThisFrame = true g.ActiveIdHasBeenEditedThisFrame = true g.ActiveIdHasBeenEditedBefore = true end if g.DeactivatedItemData.ID == id then g.DeactivatedItemData.HasBeenEditedBefore = true end -- We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343) -- We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714) -- FIXME: This assert is getting a bit meaningless over time. It helped detect some unusual use cases but eventually it is becoming an unnecessary restriction. IM_ASSERT(g.DragDropActive or g.ActiveId == id or g.ActiveId == 0 or g.DeactivatedItemData.ID == id or g.ActiveIdPreviousFrame == id or g.NavJustMovedToId or (g.CurrentMultiSelect ~= nil and g.BoxSelectState.IsActive)) -- IM_ASSERT(g.CurrentWindow.DC.LastItemId == id) g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.Edited) end --- @param window ImGuiWindow --- @param flags ImGuiHoveredFlags function ImGui.IsWindowContentHoverable(window, flags) -- An active popup disable hovering on other windows (apart from its own children) -- FIXME-OPT: This could be cached/stored within the window. local g = GImGui if g.NavWindow then local focused_root_window = g.NavWindow.RootWindowDockTree if focused_root_window.WasActive and focused_root_window ~= window.RootWindowDockTree then -- For the purpose of those flags we differentiate "standard popup" from "modal popup" -- NB: The 'else' is important because Modal windows are also Popups. local want_inhibit = false if bit32.band(focused_root_window.Flags, ImGuiWindowFlags.Modal) ~= 0 then want_inhibit = true elseif (bit32.band(focused_root_window.Flags, ImGuiWindowFlags.Popup) ~= 0) and (bit32.band(flags, ImGuiHoveredFlags.AllowWhenBlockedByPopup) == 0) then want_inhibit = true end -- Inhibit hover unless the window is within the stack of our modal/popup if want_inhibit then if not ImGui.IsWindowWithinBeginStackOf(window.RootWindow, focused_root_window) then return false end end end end -- Filter by viewport if (window.Viewport ~= g.MouseViewport) then if (g.MovingWindow == nil or window.RootWindowDockTree ~= g.MovingWindow.RootWindowDockTree) then return false end end return true end --- @param flags? ImGuiHoveredFlags @native function ImGui.IsItemHovered(flags) if flags == nil then flags = 0 end local g = GImGui local window = g.CurrentWindow IM_ASSERT_USER_ERROR(bit32.band(flags, bit32.bnot(ImGuiHoveredFlags.AllowedMaskForIsItemHovered)) == 0, "Invalid flags for IsItemHovered()!") if g.NavHighlightItemUnderNav and g.NavCursorVisible and bit32.band(flags, ImGuiHoveredFlags.NoNavOverride) == 0 then if not ImGui.IsItemFocused() then return false end if bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.Disabled) ~= 0 and bit32.band(flags, ImGuiHoveredFlags.AllowWhenDisabled) == 0 then return false end if bit32.band(flags, ImGuiHoveredFlags.ForTooltip) ~= 0 then flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipNav) end else local status_flags = g.LastItemData.StatusFlags if bit32.band(status_flags, ImGuiItemStatusFlags.HoveredRect) == 0 then return false end if bit32.band(flags, ImGuiHoveredFlags.ForTooltip) ~= 0 then flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipMouse) end if g.HoveredWindow ~= window and bit32.band(status_flags, ImGuiItemStatusFlags.HoveredWindow) == 0 then if bit32.band(flags, ImGuiHoveredFlags.AllowWhenOverlappedByWindow) == 0 then return false end end local id = g.LastItemData.ID if bit32.band(flags, ImGuiHoveredFlags.AllowWhenBlockedByActiveItem) == 0 then if g.ActiveId ~= 0 and g.ActiveId ~= id and not g.ActiveIdAllowOverlap and not g.ActiveIdFromShortcut then local cancel_is_hovered = true if g.ActiveId == window.MoveId and (id == 0 or g.ActiveIdDisabledId == id) then cancel_is_hovered = false end if g.ActiveId == window.TabId then cancel_is_hovered = false end if cancel_is_hovered then return false end end end if not ImGui.IsWindowContentHoverable(window, flags) and bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.NoWindowHoverableCheck) == 0 then return false end if bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.Disabled) ~= 0 and bit32.band(flags, ImGuiHoveredFlags.AllowWhenDisabled) == 0 then return false end if id == window.MoveId and window.WriteAccessed then return false end if bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.AllowOverlap) ~= 0 and id ~= 0 then if bit32.band(flags, ImGuiHoveredFlags.AllowWhenOverlappedByItem) == 0 then if g.HoveredIdPreviousFrame ~= g.LastItemData.ID then return false end end end end local delay = CalcDelayFromHoveredFlags(flags) if delay > 0.0 or bit32.band(flags, ImGuiHoveredFlags.Stationary) ~= 0 then local hover_delay_id if g.LastItemData.ID ~= 0 then hover_delay_id = g.LastItemData.ID else hover_delay_id = window:GetIDFromPos(g.LastItemData.Rect.Min) end if bit32.band(flags, ImGuiHoveredFlags.NoSharedDelay) ~= 0 and g.HoverItemDelayIdPreviousFrame ~= hover_delay_id then g.HoverItemDelayTimer = 0.0 end g.HoverItemDelayId = hover_delay_id if bit32.band(flags, ImGuiHoveredFlags.Stationary) ~= 0 and g.HoverItemUnlockedStationaryId ~= hover_delay_id then return false end if g.HoverItemDelayTimer < delay then return false end end return true end --- @param bb ImRect --- @param id ImGuiID --- @param item_flags ImGuiItemFlags @native function ImGui.ItemHoverable(bb, id, item_flags) local g = GImGui local window = g.CurrentWindow if not IMGUI_DISABLE_DEBUG_TOOLS then if id ~= 0 and g.HoveredIdPreviousFrame == id and bit32.band(item_flags, ImGuiItemFlags.AllowDuplicateId) == 0 then g.HoveredIdPreviousFrameItemCount = g.HoveredIdPreviousFrameItemCount + 1 if g.DebugDrawIdConflictsId == id then window.DrawList:AddRect(bb.Min - ImVec2(1, 1), bb.Max + ImVec2(1, 1), IM_COL32(255, 0, 0, 255), 0.0, 2.0, ImDrawFlags.None) end end end if g.HoveredWindow ~= window then return false end if not ImGui.IsMouseHoveringRect(bb.Min, bb.Max) then return false end if g.HoveredId ~= 0 and g.HoveredId ~= id and not g.HoveredIdAllowOverlap then return false end if g.ActiveId ~= 0 and g.ActiveId ~= id and not g.ActiveIdAllowOverlap then if not g.ActiveIdFromShortcut then return false end end if (bit32.band(item_flags, ImGuiItemFlags.NoWindowHoverableCheck) == 0) and not ImGui.IsWindowContentHoverable(window, ImGuiHoveredFlags.None) then g.HoveredIdIsDisabled = true return false end if id ~= 0 then if g.DragDropActive and g.DragDropPayload.SourceId == id and (bit32.band(g.DragDropSourceFlags, ImGuiDragDropFlags.SourceNoDisableHover) == 0) then return false end ImGui.SetHoveredID(id) if bit32.band(item_flags, ImGuiItemFlags.AllowOverlap) ~= 0 then g.HoveredIdAllowOverlap = true if g.HoveredIdPreviousFrame ~= id then return false end end if id == g.LastItemData.ID and (bit32.band(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HasShortcut) ~= 0) and g.ActiveId ~= id then if ImGui.IsItemHovered(bit32.bor(ImGuiHoveredFlags.ForTooltip, ImGuiHoveredFlags.DelayNormal)) then ImGui.SetTooltip("%s", ImGui.GetKeyChordName(g.LastItemData.Shortcut)) end end end if bit32.band(item_flags, ImGuiItemFlags.Disabled) ~= 0 then -- Release active id if turning disabled if g.ActiveId == id and id ~= 0 then ImGui.ClearActiveID() end g.HoveredIdIsDisabled = true return false end if g.NavHighlightItemUnderNav and (bit32.band(item_flags, ImGuiItemFlags.NoNavDisableMouseHover) == 0) then return false end return true end --- @param bb ImRect --- @param id ImGuiID --- @return bool function ImGui.IsClippedEx(bb, id) local g = GImGui local window = g.CurrentWindow if not bb:Overlaps(window.ClipRect) then if id == 0 or (id ~= g.ActiveId and id ~= g.ActiveIdPreviousFrame and id ~= g.NavId and id ~= g.NavActivateId) then if not g.ItemUnclipByLog then return true end end end return false end --- @param item_id ImGuiID --- @param item_flags ImGuiItemFlags --- @param status_flags ImGuiItemStatusFlags --- @param item_rect ImRect function ImGui.SetLastItemData(item_id, item_flags, status_flags, item_rect) local g = GImGui g.LastItemData.ID = item_id g.LastItemData.ItemFlags = item_flags g.LastItemData.StatusFlags = status_flags ImRect_Copy(g.LastItemData.Rect, item_rect) ImRect_Copy(g.LastItemData.NavRect, item_rect) end --- @param window ImGuiWindow --- @param rect ImRect function ImGui.SetLastItemDataForWindow(window, rect) local g = GImGui if window.DockIsActive then ImGui.SetLastItemData(window.MoveId, g.CurrentItemFlags, window.DC.DockTabItemStatusFlags, window.DC.DockTabItemRect) else ImGui.SetLastItemData(window.MoveId, g.CurrentItemFlags, window.DC.WindowItemStatusFlags, rect) end end --- @param c unsigned_int function MT.ImGuiIO:AddInputCharacter(c) IM_ASSERT(self.Ctx ~= nil) local g = self.Ctx if c == 0 or not self.AppAcceptingEvents then return end local e = ImGuiInputEvent() e.Type = ImGuiInputEventType.Text e.Source = ImGuiInputSource.Keyboard e.EventId = g.InputEventsNextEventId g.InputEventsNextEventId = g.InputEventsNextEventId + 1 e.Text = ImGuiInputEventText() e.Text.Char = c g.InputEventsQueue:push_back(e) end --- @param c ImWchar16 function MT.ImGuiIO:AddInputCharacterUTF16(c) if (c == 0 and self.InputQueueSurrogate == 0) or not self.AppAcceptingEvents then return end if bit32.band(c, 0xFC00) == 0xD800 then -- High surrogate, must save if self.InputQueueSurrogate ~= 0 then self:AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID) end self.InputQueueSurrogate = c return end local cp = c if self.InputQueueSurrogate ~= 0 then if bit32.band(c, 0xFC00) ~= 0xDC00 then -- Invalid low surrogate self:AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID) else -- #if IM_UNICODE_CODEPOINT_MAX == 0xFFFF cp = IM_UNICODE_CODEPOINT_INVALID -- #endif end self.InputQueueSurrogate = 0 end self:AddInputCharacter(cp) end function MT.ImGuiIO:ClearEventsQueue() IM_ASSERT(self.Ctx ~= nil) local g = GImGui g.InputEventsQueue:clear() end function MT.ImGuiIO:ClearInputKeys() local g = self.Ctx for key = ImGuiKey.NamedKey_BEGIN, ImGuiKey.NamedKey_END - 1 do if not ImGui.IsMouseKey(key) then local key_data = g.IO.KeysData[key - ImGuiKey.NamedKey_BEGIN] key_data.Down = false key_data.DownDuration = -1.0 key_data.DownDurationPrev = -1.0 end end self.KeyCtrl = false self.KeyShift = false self.KeyAlt = false self.KeySuper = false self.KeyMods = ImGuiMod_None self.InputQueueCharacters:resize(0) end function MT.ImGuiIO:ClearInputMouse() for key = ImGuiKey_Mouse_BEGIN, ImGuiKey_Mouse_END - 1 do local key_data = self.KeysData[key - ImGuiKey.NamedKey_BEGIN] if key_data then key_data.Down = false key_data.DownDuration = -1.0 key_data.DownDurationPrev = -1.0 end end self.MousePos = ImVec2(-FLT_MAX, -FLT_MAX) for n = 0, 2 do -- IM_COUNTOF(MouseDown) self.MouseDown[n] = false self.MouseDownDuration[n] = -1.0 self.MouseDownDurationPrev[n] = -1.0 end self.MouseWheel = 0 self.MouseWheelH = 0 end --- @param ctx ImGuiContext --- @param type ImGuiInputEventType --- @param arg? int --- @return ImGuiInputEvent? function MT.ImGuiIO:FindLatestInputEvent(ctx, type, arg) if arg == nil then arg = -1 end local g = ctx for n = g.InputEventsQueue.Size, 1, -1 do local e = g.InputEventsQueue.Data[n] if e.Type ~= type then elseif type == ImGuiInputEventType.Key and e.Key.Key ~= arg then elseif type == ImGuiInputEventType.MouseButton and e.MouseButton.Button ~= arg then else return e end end return nil end --- @param key ImGuiKey --- @param down bool --- @param analog_value float function MT.ImGuiIO:AddKeyAnalogEvent(key, down, analog_value) IM_ASSERT(self.Ctx ~= nil) if key == ImGuiKey.None or not self.AppAcceptingEvents then return end local g = self.Ctx IM_ASSERT(ImGui.IsNamedKeyOrMod(key)) IM_ASSERT(ImGui.IsAliasKey(key) == false) -- MacOS: swap Cmd(Super) and Ctrl if (g.IO.ConfigMacOSXBehaviors) then if (key == ImGuiMod_Super) then key = ImGuiMod_Ctrl elseif (key == ImGuiMod_Ctrl) then key = ImGuiMod_Super elseif (key == ImGuiKey.LeftSuper) then key = ImGuiKey.LeftCtrl elseif (key == ImGuiKey.RightSuper) then key = ImGuiKey.RightCtrl elseif (key == ImGuiKey.LeftCtrl) then key = ImGuiKey.LeftSuper elseif (key == ImGuiKey.RightCtrl) then key = ImGuiKey.RightSuper end end local latest_event = self:FindLatestInputEvent(g, ImGuiInputEventType.Key, key) local key_data = ImGui.GetKeyData(g, key) local latest_key_down = latest_event and latest_event.Key.Down or key_data.Down local latest_key_analog = latest_event and latest_event.Key.AnalogValue or key_data.AnalogValue if latest_key_down == down and latest_key_analog == analog_value then return end local e = ImGuiInputEvent() e.Type = ImGuiInputEventType.Key e.Source = ImGui.IsGamepadKey(key) and ImGuiInputSource.Gamepad or ImGuiInputSource.Keyboard e.EventId = g.InputEventsNextEventId g.InputEventsNextEventId = g.InputEventsNextEventId + 1 e.Key = ImGuiInputEventKey() e.Key.Key = key e.Key.Down = down e.Key.AnalogValue = analog_value g.InputEventsQueue:push_back(e) end --- @param key ImGuiKey --- @param down bool function MT.ImGuiIO:AddKeyEvent(key, down) if not self.AppAcceptingEvents then return end self:AddKeyAnalogEvent(key, down, (down and 1.0 or 0.0)) end --- @param accepting_events bool function MT.ImGuiIO:SetAppAcceptingEvents(accepting_events) self.AppAcceptingEvents = accepting_events end --- @param source ImGuiMouseSource function MT.ImGuiIO:AddMouseSourceEvent(source) IM_ASSERT(self.Ctx ~= nil) local g = self.Ctx g.InputEventsNextMouseSource = source end --- @param viewport_id ImGuiID function MT.ImGuiIO:AddMouseViewportEvent(viewport_id) IM_ASSERT(self.Ctx ~= nil) local g = self.Ctx -- IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags.HasMouseHoveredViewport); if (not self.AppAcceptingEvents) then return end -- Filter duplicate local latest_event = self:FindLatestInputEvent(g, ImGuiInputEventType.MouseViewport) local latest_viewport_id if latest_event then latest_viewport_id = latest_event.MouseViewport.HoveredViewportID else latest_viewport_id = g.IO.MouseHoveredViewport end if (latest_viewport_id == viewport_id) then return end local e = ImGuiInputEvent() e.Type = ImGuiInputEventType.MouseViewport e.Source = ImGuiInputSource.Mouse e.MouseViewport = ImGuiInputEventMouseViewport() e.MouseViewport.HoveredViewportID = viewport_id g.InputEventsQueue:push_back(e) end --- @param focused bool function MT.ImGuiIO:AddFocusEvent(focused) IM_ASSERT(self.Ctx ~= nil) local g = self.Ctx local latest_event = self:FindLatestInputEvent(g, ImGuiInputEventType.Focus) local latest_focused = latest_event and latest_event.AppFocused.Focused or (not g.IO.AppFocusLost) if latest_focused == focused or (self.ConfigDebugIgnoreFocusLoss and not focused) then return end local e = ImGuiInputEvent() e.Type = ImGuiInputEventType.Focus e.EventId = g.InputEventsNextEventId g.InputEventsNextEventId = g.InputEventsNextEventId + 1 e.AppFocused = ImGuiInputEventAppFocused() e.AppFocused.Focused = focused g.InputEventsQueue:push_back(e) end --- @param x float --- @param y float function MT.ImGuiIO:AddMousePosEvent(x, y) IM_ASSERT(self.Ctx ~= nil) if not self.AppAcceptingEvents then return end local g = self.Ctx local x_val = x if x > -FLT_MAX then x_val = ImFloor(x) end local y_val = y if y > -FLT_MAX then y_val = ImFloor(y) end local latest_event = self:FindLatestInputEvent(g, ImGuiInputEventType.MousePos) local latest_pos = latest_event and ImVec2(latest_event.MousePos.PosX, latest_event.MousePos.PosY) or self.MousePos if latest_pos.x == x_val and latest_pos.y == y_val then return end local e = ImGuiInputEvent() e.Type = ImGuiInputEventType.MousePos e.Source = ImGuiInputSource.Mouse e.EventId = g.InputEventsNextEventId g.InputEventsNextEventId = g.InputEventsNextEventId + 1 e.MousePos = ImGuiInputEventMousePos() e.MousePos.PosX = x_val e.MousePos.PosY = y_val e.MousePos.MouseSource = g.InputEventsNextMouseSource g.InputEventsQueue:push_back(e) end --- @param mouse_button ImGuiMouseButton --- @param down bool function MT.ImGuiIO:AddMouseButtonEvent(mouse_button, down) IM_ASSERT(self.Ctx ~= nil) local g = self.Ctx IM_ASSERT(mouse_button >= 0 and mouse_button < ImGuiMouseButton.COUNT) if not self.AppAcceptingEvents then return end -- On MacOS X: Convert Ctrl(Super)+Left click into Right-click: handle held button. if self.ConfigMacOSXBehaviors and mouse_button == 0 and g.IO.MouseCtrlLeftAsRightClick then -- Order of both statements matters: this event will still release mouse button 1 mouse_button = 1 if not down then self.MouseCtrlLeftAsRightClick = false end end local latest_event = self:FindLatestInputEvent(g, ImGuiInputEventType.MouseButton, mouse_button) local latest_button_down = latest_event and latest_event.MouseButton.Down or self.MouseDown[mouse_button] if latest_button_down == down then return end -- On MacOS X: Convert Ctrl(Super)+Left click into Right-click. -- - Note that this is actual physical Ctrl which is ImGuiMod_Super for us. -- - At this point we want from !down to down, so this is handling the initial press. if self.ConfigMacOSXBehaviors and mouse_button == 0 and down then local latest_super_event = self:FindLatestInputEvent(g, ImGuiInputEventType.Key, ImGuiMod_Super) if latest_super_event and latest_super_event.Key.Down or self.KeySuper then -- IMGUI_DEBUG_LOG_IO("[io] Super+Left Click aliased into Right Click\n") self.MouseCtrlLeftAsRightClick = true self:AddMouseButtonEvent(1, true) -- This is just quicker to write that passing through, as we need to filter duplicate again. return end end local e = ImGuiInputEvent() e.Type = ImGuiInputEventType.MouseButton e.Source = ImGuiInputSource.Mouse e.EventId = g.InputEventsNextEventId g.InputEventsNextEventId = g.InputEventsNextEventId + 1 e.MouseButton = ImGuiInputEventMouseButton() e.MouseButton.Button = mouse_button e.MouseButton.Down = down e.MouseButton.MouseSource = g.InputEventsNextMouseSource g.InputEventsQueue:push_back(e) end --- @param wheel_x float --- @param wheel_y float function MT.ImGuiIO:AddMouseWheelEvent(wheel_x, wheel_y) IM_ASSERT(self.Ctx ~= nil) local g = self.Ctx if not self.AppAcceptingEvents or (wheel_x == 0 and wheel_y == 0) then return end local e = ImGuiInputEvent() e.Type = ImGuiInputEventType.MouseWheel e.Source = ImGuiInputSource.Mouse e.EventId = g.InputEventsNextEventId g.InputEventsNextEventId = g.InputEventsNextEventId + 1 e.MouseWheel = ImGuiInputEventMouseWheel() e.MouseWheel.WheelX = wheel_x e.MouseWheel.WheelY = wheel_y e.MouseWheel.MouseSource = g.InputEventsNextMouseSource g.InputEventsQueue:push_back(e) end --- @param key ImGuiKey local function GetModForLRModKey(key) if key == ImGuiKey.LeftCtrl or key == ImGuiKey.RightCtrl then return ImGuiMod_Ctrl end if key == ImGuiKey.LeftShift or key == ImGuiKey.RightShift then return ImGuiMod_Shift end if key == ImGuiKey.LeftAlt or key == ImGuiKey.RightAlt then return ImGuiMod_Alt end if key == ImGuiKey.LeftSuper or key == ImGuiKey.RightSuper then return ImGuiMod_Super end return ImGuiMod_None end --- @param key_chord ImGuiKeyChord function ImGui.FixupKeyChord(key_chord) local key = bit32.band(key_chord, bit32.bnot(ImGuiMod_Mask_)) if ImGui.IsLRModKey(key) then key_chord = bit32.bor(key_chord, GetModForLRModKey(key)) end return key_chord end --- @param ctx? ImGuiContext --- @param key ImGuiKey --- @return ImGuiKeyData, int function ImGui.GetKeyData(ctx, key) if ctx == nil then ctx = GImGui end if bit32.band(key, ImGuiMod_Mask_) ~= 0 then key = ImGui.ConvertSingleModFlagToKey(key) end IM_ASSERT(ImGui.IsNamedKey(key), "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code.") local idx = key - ImGuiKey.NamedKey_BEGIN return ctx.IO.KeysData[idx], idx end --- @param key_left ImGuiKey --- @param key_right ImGuiKey --- @param key_up ImGuiKey --- @param key_down ImGuiKey function ImGui.GetKeyMagnitude2d(key_left, key_right, key_up, key_down) return ImVec2( ImGui.GetKeyData(nil, key_right).AnalogValue - ImGui.GetKeyData(nil, key_left).AnalogValue, ImGui.GetKeyData(nil, key_down).AnalogValue - ImGui.GetKeyData(nil, key_up).AnalogValue ) end --- @param key ImGuiKey --- @param owner_id ImGuiID function ImGui.TestKeyOwner(key, owner_id) if not ImGui.IsNamedKeyOrMod(key) then return true end local g = GImGui --- @cast g ImGuiContext if g.ActiveIdUsingAllKeyboardKeys and owner_id ~= g.ActiveId and owner_id ~= ImGuiKeyOwner_Any then if key >= ImGuiKey_Keyboard_BEGIN and key < ImGuiKey_Keyboard_END then return false end end local owner_data = ImGui.GetKeyOwnerData(g, key) if owner_id == ImGuiKeyOwner_Any then return not owner_data.LockThisFrame end if owner_data.OwnerCurr ~= owner_id then if owner_data.LockThisFrame then return false end if owner_data.OwnerCurr ~= ImGuiKeyOwner_NoOwner then return false end end return true end --- @param key ImGuiKey --- @param owner_id? ImGuiID function ImGui.IsKeyDown(key, owner_id) if owner_id == nil then owner_id = ImGuiKeyOwner_Any end local key_data = ImGui.GetKeyData(nil, key) if not key_data.Down then return false end if not ImGui.TestKeyOwner(key, owner_id) then return false end return true end --- @param key ImGuiKey --- @param is_repeat? bool function ImGui.IsKeyPressed(key, is_repeat) if is_repeat == nil then is_repeat = true end return ImGui.IsKeyPressedEx(key, is_repeat and ImGuiInputFlags.Repeat or ImGuiInputFlags.None, ImGuiKeyOwner_Any) end --- @param key ImGuiKey --- @param owner_id? ImGuiID --- @return bool function ImGui.IsKeyReleased(key, owner_id) if owner_id == nil then owner_id = ImGuiKeyOwner_Any end local key_data = ImGui.GetKeyData(nil, key) if key_data.DownDurationPrev < 0.0 or key_data.Down then return false end if not ImGui.TestKeyOwner(key, owner_id) then return false end return true end --- @param key ImGuiKey --- @param flags ImGuiInputFlags --- @param owner_id? ImGuiID function ImGui.IsKeyPressedEx(key, flags, owner_id) if owner_id == nil then owner_id = 0 end local g = GImGui local key_data = ImGui.GetKeyData(g, key) -- In theory this should already be encoded as (DownDuration < 0.0), but testing this facilitates eating mechanism (until we finish work on key ownership) if not key_data.Down then return false end local t = key_data.DownDuration if t < 0.0 then return false end IM_ASSERT(bit32.band(flags, bit32.bnot(ImGuiInputFlags.SupportedByIsKeyPressed)) == 0) -- Passing flags not supported by this function! if bit32.band(flags, bit32.bor(ImGuiInputFlags.RepeatRateMask_, ImGuiInputFlags.RepeatUntilMask_)) ~= 0 then -- Setting any _RepeatXXX option enables _Repeat flags = bit32.bor(flags, ImGuiInputFlags.Repeat) end local pressed = (t == 0.0) if not pressed and bit32.band(flags, ImGuiInputFlags.Repeat) ~= 0 then local repeat_delay, repeat_rate = ImGui.GetTypematicRepeatRate(flags) pressed = (t > repeat_delay) and (ImGui.GetKeyPressedAmount(key, repeat_delay, repeat_rate) > 0) if pressed and bit32.band(flags, ImGuiInputFlags.RepeatUntilMask_) ~= 0 then -- Slightly bias 'key_pressed_time' as DownDuration is an accumulation of DeltaTime which we compare to an absolute time value. -- Ideally we'd replace DownDuration with KeyPressedTime but it would break user's code. local key_pressed_time = g.Time - t + 0.00001 if bit32.band(flags, ImGuiInputFlags.RepeatUntilKeyModsChange) ~= 0 and g.LastKeyModsChangeTime > key_pressed_time then pressed = false end if bit32.band(flags, ImGuiInputFlags.RepeatUntilKeyModsChangeFromNone) ~= 0 and g.LastKeyModsChangeFromNoneTime > key_pressed_time then pressed = false end if bit32.band(flags, ImGuiInputFlags.RepeatUntilOtherKeyPress) ~= 0 and g.LastKeyboardKeyPressTime > key_pressed_time then pressed = false end end end if not pressed then return false end if not ImGui.TestKeyOwner(key, owner_id) then return false end return true end -- TODO: function ImGui.GetKeyChordName(key_chord) local key = bit32.band(key_chord, bit32.bnot(ImGuiMod_Mask_)) if ImGui.IsLRModKey(key) then key_chord = bit32.band(key_chord, bit32.bnot(GetModForLRModKey(key))) -- Return "Ctrl+LeftShift" instead of "Ctrl+Shift+LeftShift" end local s = ((bit32.band(key_chord, ImGuiMod_Ctrl) ~= 0) and "Ctrl+" or "") .. ((bit32.band(key_chord, ImGuiMod_Shift) ~= 0) and "Shift+" or "") .. ((bit32.band(key_chord, ImGuiMod_Alt) ~= 0) and "Alt+" or "") .. ((bit32.band(key_chord, ImGuiMod_Super) ~= 0) and "Super+" or "") .. ((key ~= ImGuiKey.None or key_chord == ImGuiKey.None) and ImGui.GetKeyName(key) or "") if key == ImGuiKey.None and key_chord ~= 0 and #s > 0 then s = string.sub(s, 1, #s - 1) -- Remove trailing '+' end return s end local GKeyNames = { "Tab", "LeftArrow", "RightArrow", "UpArrow", "DownArrow", "PageUp", "PageDown", "Home", "End", "Insert", "Delete", "Backspace", "Space", "Enter", "Escape", "LeftCtrl", "LeftShift", "LeftAlt", "LeftSuper", "RightCtrl", "RightShift", "RightAlt", "RightSuper", "Menu", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", "Apostrophe", "Comma", "Minus", "Period", "Slash", "Semicolon", "Equal", "LeftBracket", "Backslash", "RightBracket", "GraveAccent", "CapsLock", "ScrollLock", "NumLock", "PrintScreen", "Pause", "Keypad0", "Keypad1", "Keypad2", "Keypad3", "Keypad4", "Keypad5", "Keypad6", "Keypad7", "Keypad8", "Keypad9", "KeypadDecimal", "KeypadDivide", "KeypadMultiply", "KeypadSubtract", "KeypadAdd", "KeypadEnter", "KeypadEqual", "AppBack", "AppForward", "Oem102", "GamepadStart", "GamepadBack", "GamepadFaceLeft", "GamepadFaceRight", "GamepadFaceUp", "GamepadFaceDown", "GamepadDpadLeft", "GamepadDpadRight", "GamepadDpadUp", "GamepadDpadDown", "GamepadL1", "GamepadR1", "GamepadL2", "GamepadR2", "GamepadL3", "GamepadR3", "GamepadLStickLeft", "GamepadLStickRight", "GamepadLStickUp", "GamepadLStickDown", "GamepadRStickLeft", "GamepadRStickRight", "GamepadRStickUp", "GamepadRStickDown", "MouseLeft", "MouseRight", "MouseMiddle", "MouseX1", "MouseX2", "MouseWheelX", "MouseWheelY", "ModCtrl", "ModShift", "ModAlt", "ModSuper", -- ReservedForModXXX are showing the ModXXX names. } --- @param key ImGuiKey --- @return string function ImGui.GetKeyName(key) if key == ImGuiKey.None then return "None" end IM_ASSERT(ImGui.IsNamedKeyOrMod(key), "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code.") if bit32.band(key, ImGuiMod_Mask_) ~= 0 then key = ImGui.ConvertSingleModFlagToKey(key) end if not ImGui.IsNamedKey(key) then return "Unknown" end return GKeyNames[key - ImGuiKey.NamedKey_BEGIN + 1] or "Unknown" end --- @param key ImGuiKey --- @return ImGuiID function ImGui.GetKeyOwner(key) if not ImGui.IsNamedKeyOrMod(key) then return ImGuiKeyOwner_NoOwner end local g = GImGui local owner_data = ImGui.GetKeyOwnerData(g, key) local owner_id = owner_data.OwnerCurr if g.ActiveIdUsingAllKeyboardKeys and owner_id ~= g.ActiveId and owner_id ~= ImGuiKeyOwner_Any then if key >= ImGuiKey_Keyboard_BEGIN and key < ImGuiKey_Keyboard_END then return ImGuiKeyOwner_NoOwner end end return owner_id end --- @param t0 float --- @param t1 float --- @param repeat_delay float --- @param repeat_rate float function ImGui.CalcTypematicRepeatAmount(t0, t1, repeat_delay, repeat_rate) if t1 == 0.0 then return 1 end if t0 >= t1 then return 0 end if repeat_rate <= 0.0 then if t0 < repeat_delay and t1 >= repeat_delay then return 1 else return 0 end end local count_t0 if t0 < repeat_delay then count_t0 = -1 else count_t0 = math.floor((t0 - repeat_delay) / repeat_rate) end local count_t1 if t1 < repeat_delay then count_t1 = -1 else count_t1 = math.floor((t1 - repeat_delay) / repeat_rate) end return count_t1 - count_t0 end --- @param flags ImGuiInputFlags --- @return float repeat_delay --- @return float repeat_rate function ImGui.GetTypematicRepeatRate(flags) local g = GImGui flags = bit32.band(flags, ImGuiInputFlags.RepeatRateMask_) if flags == ImGuiInputFlags.RepeatRateNavMove then repeat_delay = g.IO.KeyRepeatDelay * 0.72; repeat_rate = g.IO.KeyRepeatRate * 0.80; return repeat_delay, repeat_rate elseif flags == ImGuiInputFlags.RepeatRateNavTweak then repeat_delay = g.IO.KeyRepeatDelay * 0.72; repeat_rate = g.IO.KeyRepeatRate * 0.30; return repeat_delay, repeat_rate else repeat_delay = g.IO.KeyRepeatDelay * 1.00; repeat_rate = g.IO.KeyRepeatRate * 1.00; return repeat_delay, repeat_rate end end --- @param key ImGuiKey --- @param repeat_delay float --- @param repeat_rate float function ImGui.GetKeyPressedAmount(key, repeat_delay, repeat_rate) local g = GImGui local key_data = ImGui.GetKeyData(g, key) if not key_data.Down then -- In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership) return 0 end local t = key_data.DownDuration return ImGui.CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, repeat_delay, repeat_rate) end --- @param rt ImGuiKeyRoutingTable function ImGui.UpdateKeyRoutingTable(rt) local g = GImGui --[[@as ImGuiContext]] rt.EntriesNext:resize(0) for key = ImGuiKey.NamedKey_BEGIN, ImGuiKey.NamedKey_END - 1 do local new_routing_start_idx = rt.EntriesNext.Size + 1 local routing_entry local old_routing_idx = rt.Index[key - ImGuiKey.NamedKey_BEGIN + 1] while old_routing_idx ~= -1 do repeat routing_entry = rt.Entries[old_routing_idx] routing_entry.RoutingCurrScore = routing_entry.RoutingNextScore routing_entry.RoutingCurr = routing_entry.RoutingNext routing_entry.RoutingNext = ImGuiKeyOwner_NoOwner routing_entry.RoutingNextScore = 0 if routing_entry.RoutingCurr == ImGuiKeyOwner_NoOwner then break end rt.EntriesNext:push_back(routing_entry) if routing_entry.Mods == g.IO.KeyMods then local owner_data = ImGui.GetKeyOwnerData(g, key) if owner_data.OwnerCurr == ImGuiKeyOwner_NoOwner then owner_data.OwnerCurr = routing_entry.RoutingCurr end end until true old_routing_idx = routing_entry.NextEntryIndex end rt.Index[key - ImGuiKey.NamedKey_BEGIN + 1] = (new_routing_start_idx <= rt.EntriesNext.Size and new_routing_start_idx or -1) for n = new_routing_start_idx, rt.EntriesNext.Size do rt.EntriesNext[n].NextEntryIndex = (n + 1 <= rt.EntriesNext.Size) and (n + 1) or -1 end end rt.Entries:swap(rt.EntriesNext) end -- owner_id may be None/Any, but routing_id needs to be always be set, so we default to GetCurrentFocusScope() --- @param owner_id ImGuiID local function GetRoutingIdFromOwnerId(owner_id) local g = GImGui return (owner_id ~= ImGuiKeyOwner_NoOwner and owner_id ~= ImGuiKeyOwner_Any) and owner_id or g.CurrentFocusScopeId end --- @param key_chord ImGuiKeyChord --- @return ImGuiKeyRoutingData function ImGui.GetShortcutRoutingData(key_chord) local g = GImGui local rt = g.KeysRoutingTable local key = bit32.band(key_chord, bit32.bnot(ImGuiMod_Mask_)) local mods = bit32.band(key_chord, ImGuiMod_Mask_) if key == ImGuiKey.None then key = ImGui.ConvertSingleModFlagToKey(mods) end IM_ASSERT(ImGui.IsNamedKey(key)) local routing_data local idx = rt.Index[key - ImGuiKey.NamedKey_BEGIN + 1] while idx ~= -1 do routing_data = rt.Entries.Data[idx] if routing_data.Mods == mods then return routing_data end idx = routing_data.NextEntryIndex end local routing_data_idx = rt.Entries.Size + 1 rt.Entries:push_back(ImGuiKeyRoutingData()) routing_data = rt.Entries[routing_data_idx] routing_data.Mods = mods routing_data.NextEntryIndex = rt.Index[key - ImGuiKey.NamedKey_BEGIN + 1] rt.Index[key - ImGuiKey.NamedKey_BEGIN + 1] = routing_data_idx return routing_data end --- @param focus_scope_id ImGuiID --- @param owner_id ImGuiID --- @param flags ImGuiInputFlags local function CalcRoutingScore(focus_scope_id, owner_id, flags) local g = GImGui if bit32.band(flags, ImGuiInputFlags.RouteFocused) ~= 0 then if owner_id ~= 0 and g.ActiveId == owner_id then return 300 end if focus_scope_id == 0 then return 0 end for index_in_focus_path = 1, g.NavFocusRoute.Size do if g.NavFocusRoute.Data[index_in_focus_path].ID == focus_scope_id then if bit32.band(flags, ImGuiInputFlags.RouteOverActive) ~= 0 then return 599 - (index_in_focus_path - 1) else return 199 - (index_in_focus_path - 1) end end end return 0 elseif bit32.band(flags, ImGuiInputFlags.RouteActive) ~= 0 then if owner_id ~= 0 and g.ActiveId == owner_id then return 300 end return 0 elseif bit32.band(flags, ImGuiInputFlags.RouteGlobal) ~= 0 then if bit32.band(flags, ImGuiInputFlags.RouteOverActive) ~= 0 then return 400 end if owner_id ~= 0 and g.ActiveId == owner_id then return 300 end if bit32.band(flags, ImGuiInputFlags.RouteOverFocused) ~= 0 then return 200 end return 1 end IM_ASSERT(false) return 0 end --- @param key_chord ImGuiKeyChord local function IsKeyChordPotentiallyCharInput(key_chord) local g = GImGui local mods = bit32.band(key_chord, ImGuiMod_Mask_) local ignore_char_inputs = ((bit32.band(mods, ImGuiMod_Ctrl) ~= 0) and (bit32.band(mods, ImGuiMod_Alt) == 0)) or (g.IO.ConfigMacOSXBehaviors and (bit32.band(mods, ImGuiMod_Ctrl) ~= 0)) if ignore_char_inputs then return false end local key = bit32.band(key_chord, bit32.bnot(ImGuiMod_Mask_)) if key == ImGuiKey.None then return false end return g.KeysMayBeCharInput:TestBit(key + 1) -- TestBit requires 1-based index end --- @param key_chord ImGuiKeyChord --- @param flags ImGuiInputFlags --- @param owner_id ImGuiID function ImGui.SetShortcutRouting(key_chord, flags, owner_id) local g = GImGui if bit32.band(flags, ImGuiInputFlags.RouteTypeMask_) == 0 then flags = bit32.bor(flags, ImGuiInputFlags.RouteGlobal, ImGuiInputFlags.RouteOverFocused, ImGuiInputFlags.RouteOverActive) -- IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut() else IM_ASSERT(ImIsPowerOfTwo(bit32.band(flags, ImGuiInputFlags.RouteTypeMask_))) -- Check that only 1 routing flag is used end IM_ASSERT(owner_id ~= ImGuiKeyOwner_Any and owner_id ~= ImGuiKeyOwner_NoOwner) if bit32.band(flags, bit32.bor(ImGuiInputFlags.RouteOverFocused, ImGuiInputFlags.RouteUnlessBgFocused)) ~= 0 then IM_ASSERT(bit32.band(flags, ImGuiInputFlags.RouteGlobal) ~= 0) end if bit32.band(flags, ImGuiInputFlags.RouteOverActive) ~= 0 then IM_ASSERT(bit32.band(flags, bit32.bor(ImGuiInputFlags.RouteGlobal, ImGuiInputFlags.RouteFocused)) ~= 0) end key_chord = ImGui.FixupKeyChord(key_chord) if g.DebugBreakInShortcutRouting == key_chord then -- IM_DEBUG_BREAK() end if bit32.band(flags, ImGuiInputFlags.RouteUnlessBgFocused) ~= 0 then if g.NavWindow == nil then return false end end if bit32.band(flags, ImGuiInputFlags.RouteAlways) ~= 0 then -- IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> always, no register", ImGui.GetKeyChordName(key_chord), flags, owner_id) return true end if g.ActiveId ~= 0 and g.ActiveId ~= owner_id then if bit32.band(flags, ImGuiInputFlags.RouteActive) ~= 0 then return false end if g.IO.WantTextInput and IsKeyChordPotentiallyCharInput(key_chord) then -- IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> filtered as potential char input", ImGui.GetKeyChordName(key_chord), flags, owner_id) return false end if bit32.band(flags, ImGuiInputFlags.RouteOverActive) == 0 and g.ActiveIdUsingAllKeyboardKeys then local key = bit32.band(key_chord, bit32.bnot(ImGuiMod_Mask_)) if key == ImGuiKey.None then key = ImGui.ConvertSingleModFlagToKey(bit32.band(key_chord, ImGuiMod_Mask_)) end if key >= ImGuiKey_Keyboard_BEGIN and key < ImGuiKey_Keyboard_END then return false end end end local focus_scope_id = g.CurrentFocusScopeId if bit32.band(flags, ImGuiInputFlags.RouteFromRootWindow) ~= 0 then focus_scope_id = g.CurrentWindow.RootWindow.ID end local score = CalcRoutingScore(focus_scope_id, owner_id, flags) -- IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> score %d", ImGui.GetKeyChordName(key_chord), flags, owner_id, score) if score == 0 then return false end local routing_data = ImGui.GetShortcutRoutingData(key_chord) if score > routing_data.RoutingNextScore then routing_data.RoutingNext = owner_id routing_data.RoutingNextScore = score end if routing_data.RoutingCurr == owner_id then -- IMGUI_DEBUG_LOG_INPUTROUTING("--> granting current route") end return routing_data.RoutingCurr == owner_id end --- @param mouse_pos? ImVec2 function ImGui.IsMousePosValid(mouse_pos) local MOUSE_INVALID = -256000.0 local p if mouse_pos then p = mouse_pos else p = GImGui.IO.MousePos end return p.x >= MOUSE_INVALID and p.y >= MOUSE_INVALID end --- @return bool function ImGui.IsAnyMouseDown() local g = GImGui for n = 0, 2 do -- IM_COUNTOF(g.IO.MouseDown) - 1 if g.IO.MouseDown[n] then return true end end return false end --- @param button ImGuiMouseButton --- @param owner_id? ImGuiID function ImGui.IsMouseDown(button, owner_id) if owner_id == nil then owner_id = ImGuiKeyOwner_Any end local g = GImGui IM_ASSERT(button >= 0 and button < 3) -- IM_COUNTOF(g.IO.MouseDown) return g.IO.MouseDown[button] and ImGui.TestKeyOwner(ImGui.MouseButtonToKey(button), owner_id) end --- @param button ImGuiMouseButton --- @param is_repeat? bool function ImGui.IsMouseClicked(button, is_repeat) if is_repeat == nil then is_repeat = false end return ImGui.IsMouseClickedEx(button, is_repeat and ImGuiInputFlags.Repeat or ImGuiInputFlags.None, ImGuiKeyOwner_Any) end --- @param button ImGuiMouseButton --- @param flags ImGuiInputFlags --- @param owner_id? ImGuiID @native function ImGui.IsMouseClickedEx(button, flags, owner_id) if owner_id == nil then owner_id = ImGuiKeyOwner_Any end local g = GImGui IM_ASSERT(button >= 0 and button < 3) -- IM_COUNTOF(g.IO.MouseDown) if not g.IO.MouseDown[button] then return false end local t = g.IO.MouseDownDuration[button] if t < 0.0 then return false end IM_ASSERT(bit32.band(flags, bit32.bnot(ImGuiInputFlags.SupportedByIsMouseClicked)) == 0) local is_repeat = (bit32.band(flags, ImGuiInputFlags.Repeat) ~= 0) local pressed = (t == 0.0) or (is_repeat and t > g.IO.KeyRepeatDelay and ImGui.CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0) if not pressed then return false end if not ImGui.TestKeyOwner(ImGui.MouseButtonToKey(button), owner_id) then return false end return true end --- @param button ImGuiMouseButton --- @param owner_id? ImGuiID function ImGui.IsMouseReleased(button, owner_id) if owner_id == nil then owner_id = ImGuiKeyOwner_Any end local g = GImGui IM_ASSERT(button >= 0 and button < 3) -- IM_COUNTOF(g.IO.MouseDown) return g.IO.MouseReleased[button] and ImGui.TestKeyOwner(ImGui.MouseButtonToKey(button), owner_id) end -- Use if you absolutely need to distinguish single-click from double-click by introducing a delay. --- @param button ImGuiMouseButton --- @param delay? float function ImGui.IsMouseReleasedWithDelay(button, delay) if delay == nil then delay = -1.0 end local g = GImGui IM_ASSERT(button >= 0 and button < 3) if ImGui.IsMouseDown(button) then return false end if delay < 0.0 then delay = g.IO.MouseSingleClickDelay or 0.50 end local time_since_release = g.Time - g.IO.MouseReleasedTime[button] return (time_since_release - g.IO.DeltaTime < delay) and (time_since_release >= delay) end --- @param button ImGuiMouseButton --- @param owner_id? ImGuiID function ImGui.IsMouseDoubleClicked(button, owner_id) if owner_id == nil then owner_id = ImGuiKeyOwner_Any end local g = GImGui IM_ASSERT(button >= 0 and button < 3) return g.IO.MouseClickedCount[button] == 2 and ImGui.TestKeyOwner(ImGui.MouseButtonToKey(button), owner_id) end --- @param button ImGuiMouseButton function ImGui.GetMouseClickedCount(button) local g = GImGui IM_ASSERT(button >= 0 and button < 3) return g.IO.MouseClickedCount[button] end -- FIXME: This is close to what BeginDragDropSource() is doing, maybe rework. --- @param mouse_button ImGuiMouseButton local function LastItemOverlayButtonForNullId(mouse_button) local g = GImGui IM_ASSERT(g.LastItemData.ID == 0) local window = g.CurrentWindow local id = window:GetIDFromRectangle(g.LastItemData.Rect) if g.IO.MouseClicked[mouse_button] and ImGui.ItemHoverable(g.LastItemData.Rect, id, g.LastItemData.ItemFlags) then ImGui.SetActiveID(id, window) ImGui.FocusWindow(window) elseif g.ActiveId == id then ImGui.KeepAliveID(id) if not g.IO.MouseDown[mouse_button] then ImGui.ClearActiveID() end end return id end --- @param mouse_button? ImGuiMouseButton --- @param delay? float --- @return int function ImGui.GetItemClickedCountWithSingleClickDelay(mouse_button, delay) if mouse_button == nil then mouse_button = 0 end if delay == nil then delay = -1.0 end -- Action: double-click and subsequent clicks local g = GImGui if g.IO.MouseClickedCount[mouse_button] >= 2 and ImGui.IsItemClicked(mouse_button) then return g.IO.MouseClickedCount[mouse_button] end -- Action: second click, delayed local id = g.LastItemData.ID if id == 0 then id = LastItemOverlayButtonForNullId(mouse_button) end if g.LastActiveId == id then if delay >= 0.0 then delay = ImMax(delay, g.IO.MouseDoubleClickTime + 0.01) end if ImGui.IsMouseReleasedWithDelay(mouse_button, delay) and g.IO.MouseClickedLastCount[mouse_button] == 1 then return 1 end end return 0 end --- @return ImVec2 # Ref function ImGui.GetMousePos() return GImGui.IO.MousePos end --- @return ImVec2 function ImGui.GetMousePosOnOpeningCurrentPopup() local g = GImGui if g.BeginPopupStack.Size > 0 then return g.BeginPopupStack.Data[g.BeginPopupStack.Size].OpenMousePos end return g.IO.MousePos end -- Return the delta from the initial clicking position while the mouse button is clicked or was just released. --- @param button? ImGuiMouseButton --- @param lock_threshold? float --- @return ImVec2 function ImGui.GetMouseDragDelta(button, lock_threshold) if button == nil then button = 0 end if lock_threshold == nil then lock_threshold = -1.0 end local g = GImGui IM_ASSERT(button >= 0 and button < 3) if lock_threshold < 0.0 then lock_threshold = g.IO.MouseDragThreshold end if g.IO.MouseDown[button] or g.IO.MouseReleased[button] then if g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold then if ImGui.IsMousePosValid(g.IO.MousePos) and ImGui.IsMousePosValid(g.IO.MouseClickedPos[button]) then return g.IO.MousePos - g.IO.MouseClickedPos[button] end end end return ImVec2(0.0, 0.0) end --- @param button? ImGuiMouseButton function ImGui.ResetMouseDragDelta(button) if button == nil then button = 0 end local g = GImGui IM_ASSERT(button >= 0 and button < 3) -- NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr ImVec2_Copy(g.IO.MouseClickedPos[button], g.IO.MousePos) end do local key_changed_mask = ImBitArray(ImGuiKey.NamedKey_COUNT) --- @param trickle_fast_inputs bool function ImGui.UpdateInputEvents(trickle_fast_inputs) local g = GImGui local io = g.IO local trickle_interleaved_nonchar_keys_and_text = trickle_fast_inputs and g.WantTextInputNextFrame == 1 local mouse_moved = false local mouse_wheeled = false local key_changed = false local key_changed_nonchar = false local text_inputted = false local mouse_button_changed = 0x00 key_changed_mask:ClearAllBits() local event_n = 1 local stop_3231 = false while event_n <= g.InputEventsQueue.Size do repeat local e = g.InputEventsQueue.Data[event_n] if e.Type == ImGuiInputEventType.MousePos then if g.IO.WantSetMousePos then break end if trickle_fast_inputs and (mouse_button_changed ~= 0 or mouse_wheeled or key_changed or text_inputted) then stop_3231 = true; break end local event_pos = ImVec2(e.MousePos.PosX, e.MousePos.PosY) io.MousePos = event_pos io.MouseSource = e.MousePos.MouseSource mouse_moved = true elseif e.Type == ImGuiInputEventType.MouseButton then local button = e.MouseButton.Button IM_ASSERT(button >= 0 and button < ImGuiMouseButton.COUNT) if trickle_fast_inputs and ((bit32.band(mouse_button_changed, bit32.lshift(1, button)) ~= 0) or mouse_wheeled) then stop_3231 = true; break end if trickle_fast_inputs and e.MouseButton.MouseSource == ImGuiMouseSource.TouchScreen and mouse_moved then stop_3231 = true; break end io.MouseDown[button] = e.MouseButton.Down io.MouseSource = e.MouseButton.MouseSource mouse_button_changed = bit32.bor(mouse_button_changed, bit32.lshift(1, button)) elseif e.Type == ImGuiInputEventType.MouseWheel then if trickle_fast_inputs and (mouse_moved or mouse_button_changed ~= 0) then stop_3231 = true; break end io.MouseWheelH = io.MouseWheelH + e.MouseWheel.WheelX io.MouseWheel = io.MouseWheel + e.MouseWheel.WheelY io.MouseSource = e.MouseWheel.MouseSource mouse_wheeled = true elseif e.Type == ImGuiInputEventType.MouseViewport then io.MouseHoveredViewport = e.MouseViewport.HoveredViewportID elseif e.Type == ImGuiInputEventType.Key then -- Trickling Rule: Stop processing queued events if we got multiple action on the same button if bit32.band(io.ConfigFlags, ImGuiConfigFlags.NoKeyboard) ~= 0 then break end local key = e.Key.Key IM_ASSERT(key ~= ImGuiKey.None) -- TODO: Also gets 0-based index here, consider making key_data storage 1-based local key_data, key_data_index = ImGui.GetKeyData(g, key) if trickle_fast_inputs and key_data.Down ~= e.Key.Down and (key_changed_mask:TestBit(key_data_index + 1) or mouse_button_changed ~= 0) then stop_3231 = true; break end local key_is_potentially_for_char_input = IsKeyChordPotentiallyCharInput(bit32.bor(ImGui.GetMergedModsFromKeys(), key)) if trickle_interleaved_nonchar_keys_and_text and (text_inputted and not key_is_potentially_for_char_input) then stop_3231 = true; break end if key_data.Down ~= e.Key.Down then key_changed = true key_changed_mask:SetBit(key_data_index + 1) if trickle_interleaved_nonchar_keys_and_text and not key_is_potentially_for_char_input then key_changed_nonchar = true end end key_data.Down = e.Key.Down key_data.AnalogValue = e.Key.AnalogValue elseif e.Type == ImGuiInputEventType.Text then if bit32.band(io.ConfigFlags, ImGuiConfigFlags.NoKeyboard) ~= 0 then break end if trickle_fast_inputs and (mouse_button_changed ~= 0 or mouse_moved or mouse_wheeled) then stop_3231 = true; break end if trickle_interleaved_nonchar_keys_and_text and key_changed_nonchar then stop_3231 = true; break end local c = e.Text.Char io.InputQueueCharacters:push_back((c <= IM_UNICODE_CODEPOINT_MAX) and c or IM_UNICODE_CODEPOINT_INVALID) if trickle_interleaved_nonchar_keys_and_text then text_inputted = true end elseif e.Type == ImGuiInputEventType.Focus then local focus_lost = not e.AppFocused.Focused io.AppFocusLost = focus_lost else IM_ASSERT(false, "Unknown event!") end until true if stop_3231 then break end event_n = event_n + 1 end if event_n == g.InputEventsQueue.Size + 1 then g.InputEventsQueue:resize(0) else for i = 1, event_n - 1 do g.InputEventsQueue:erase(1) end end if g.IO.AppFocusLost then g.IO:ClearInputKeys() g.IO:ClearInputMouse() end end end --- @param key ImGuiKey --- @param owner_id ImGuiID --- @param flags? ImGuiInputFlags function ImGui.SetKeyOwner(key, owner_id, flags) if flags == nil then flags = 0 end local g = GImGui --- @cast g ImGuiContext IM_ASSERT(ImGui.IsNamedKeyOrMod(key) and (owner_id ~= ImGuiKeyOwner_Any or (bit32.band(flags, bit32.bor(ImGuiInputFlags.LockThisFrame, ImGuiInputFlags.LockUntilRelease)) ~= 0)), "Can only use _Any with _LockXXX flags (to eat a key away without an ID to retrieve it)") IM_ASSERT(bit32.band(flags, bit32.bnot(ImGuiInputFlags.SupportedBySetKeyOwner)) == 0, "Passing flags not supported by this function!") local owner_data = ImGui.GetKeyOwnerData(g, key) owner_data.OwnerCurr = owner_id owner_data.OwnerNext = owner_id owner_data.LockUntilRelease = (bit32.band(flags, ImGuiInputFlags.LockUntilRelease) ~= 0) owner_data.LockThisFrame = (bit32.band(flags, ImGuiInputFlags.LockThisFrame) ~= 0) or owner_data.LockUntilRelease end -- Rarely used helper --- @param key_chord ImGuiKeyChord --- @param owner_id ImGuiID --- @param flags? ImGuiInputFlags function ImGui.SetKeyOwnersForKeyChord(key_chord, owner_id, flags) if flags == nil then flags = 0 end if bit32.band(key_chord, ImGuiMod_Ctrl) ~= 0 then ImGui.SetKeyOwner(ImGuiMod_Ctrl, owner_id, flags) end if bit32.band(key_chord, ImGuiMod_Shift) ~= 0 then ImGui.SetKeyOwner(ImGuiMod_Shift, owner_id, flags) end if bit32.band(key_chord, ImGuiMod_Alt) ~= 0 then ImGui.SetKeyOwner(ImGuiMod_Alt, owner_id, flags) end if bit32.band(key_chord, ImGuiMod_Super) ~= 0 then ImGui.SetKeyOwner(ImGuiMod_Super, owner_id, flags) end if bit32.band(key_chord, bit32.bnot(ImGuiMod_Mask_)) ~= 0 then ImGui.SetKeyOwner(bit32.band(key_chord, bit32.bnot(ImGuiMod_Mask_)), owner_id, flags) end end --- @param key ImGuiKey --- @param flags? ImGuiInputFlags function ImGui.SetItemKeyOwner(key, flags) if flags == nil then flags = ImGuiInputFlags.None end local g = GImGui local id = g.LastItemData.ID if id == 0 or (g.HoveredId ~= id and g.ActiveId ~= id) then return false end if bit32.band(flags, ImGuiInputFlags.CondMask_) == 0 then flags = bit32.bor(flags, ImGuiInputFlags.CondDefault_) end if (g.HoveredId == id and bit32.band(flags, ImGuiInputFlags.CondHovered) ~= 0) or (g.ActiveId == id and bit32.band(flags, ImGuiInputFlags.CondActive) ~= 0) then IM_ASSERT(bit32.band(flags, bit32.bnot(ImGuiInputFlags.SupportedBySetItemKeyOwner)) == 0) if not ImGui.TestKeyOwner(key, id) then return false end ImGui.SetKeyOwner(key, id, bit32.band(flags, bit32.bnot(ImGuiInputFlags.CondMask_))) return true end return false end --- @param key_chord ImGuiKeyChord --- @param flags ImGuiInputFlags --- @param owner_id ImGuiID function ImGui.IsKeyChordPressed(key_chord, flags, owner_id) local g = GImGui key_chord = ImGui.FixupKeyChord(key_chord) local mods = bit32.band(key_chord, ImGuiMod_Mask_) if g.IO.KeyMods ~= mods then return false end -- Special storage location for mods local key = bit32.band(key_chord, bit32.bnot(ImGuiMod_Mask_)) if key == ImGuiKey.None then key = ImGui.ConvertSingleModFlagToKey(mods) end if not ImGui.IsKeyPressedEx(key, bit32.band(flags, ImGuiInputFlags.RepeatMask_), owner_id) then return false end return true end --- @param key_chord ImGuiKeyChord --- @param flags? ImGuiInputFlags function ImGui.SetNextItemShortcut(key_chord, flags) if flags == nil then flags = 0 end local g = GImGui g.NextItemData.HasFlags = bit32.bor(g.NextItemData.HasFlags, ImGuiNextItemDataFlags.HasShortcut) g.NextItemData.Shortcut = key_chord g.NextItemData.ShortcutFlags = flags end --- @param key_chord ImGuiKeyChord --- @param flags ImGuiInputFlags --- @param owner_id ImGuiID function ImGui.Shortcut(key_chord, flags, owner_id) local g = GImGui -- IMGUI_DEBUG_LOG("Shortcut(%s, flags=%X, owner_id=0x%08X)", ImGui.GetKeyChordName(key_chord, g.TempBuffer.Data, g.TempBuffer.Size), flags, owner_id) -- When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any. if bit32.band(flags, ImGuiInputFlags.RouteTypeMask_) == 0 then flags = bit32.bor(flags, ImGuiInputFlags.RouteFocused) end -- Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default) -- Effectively makes Shortcut() always input-owner aware. if owner_id == ImGuiKeyOwner_Any or owner_id == ImGuiKeyOwner_NoOwner then owner_id = GetRoutingIdFromOwnerId(owner_id) end if bit32.band(g.CurrentItemFlags, ImGuiItemFlags.Disabled) ~= 0 then return false end -- Submit route if not ImGui.SetShortcutRouting(key_chord, flags, owner_id) then return false end -- Default repeat behavior for Shortcut() -- So e.g. pressing Ctrl+W and releasing Ctrl while holding W will not trigger the W shortcut. if bit32.band(flags, ImGuiInputFlags.Repeat) ~= 0 and bit32.band(flags, ImGuiInputFlags.RepeatUntilMask_) == 0 then flags = bit32.bor(flags, ImGuiInputFlags.RepeatUntilKeyModsChange) end if not ImGui.IsKeyChordPressed(key_chord, flags, owner_id) then return false end -- Claim mods during the press ImGui.SetKeyOwnersForKeyChord(bit32.band(key_chord, ImGuiMod_Mask_), owner_id) IM_ASSERT(bit32.band(flags, bit32.bnot(ImGuiInputFlags.SupportedByShortcut)) == 0) -- Passing flags not supported by this function! return true end --- @param window ImGuiWindow --- @return bool function ImGui.IsWindowActiveAndVisible(window) return window.Active and not window.Hidden end --- @param window ImGuiWindow --- @nodiscard local function CalcWindowMinSize(window) local g = GImGui local size_min = ImVec2() if (bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0) and (bit32.band(window.Flags, ImGuiWindowFlags.Popup) == 0) then if bit32.band(window.ChildFlags, ImGuiChildFlags.ResizeX) ~= 0 then size_min.x = g.Style.WindowMinSize.x else size_min.x = IMGUI_WINDOW_HARD_MIN_SIZE end if bit32.band(window.ChildFlags, ImGuiChildFlags.ResizeY) ~= 0 then size_min.y = g.Style.WindowMinSize.y else size_min.y = IMGUI_WINDOW_HARD_MIN_SIZE end else if bit32.band(window.Flags, ImGuiWindowFlags.AlwaysAutoResize) == 0 then size_min.x = g.Style.WindowMinSize.x else size_min.x = IMGUI_WINDOW_HARD_MIN_SIZE end if bit32.band(window.Flags, ImGuiWindowFlags.AlwaysAutoResize) == 0 then size_min.y = g.Style.WindowMinSize.y else size_min.y = IMGUI_WINDOW_HARD_MIN_SIZE end end local window_for_height = window size_min.y = ImMax(size_min.y, window_for_height.TitleBarHeight + window_for_height.MenuBarHeight + ImMax(0, g.Style.WindowRounding - 1)) return size_min end --- @param window ImGuiWindow --- @param size_desired ImVec2 --- @return ImVec2 --- @nodiscard local function CalcWindowSizeAfterConstraint(window, size_desired) local g = GImGui local new_size = ImVec2() ImVec2_Copy(new_size, size_desired) if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasSizeConstraint) ~= 0 then local cr = ImRect() ImRect_Copy(cr, g.NextWindowData.SizeConstraintRect) new_size.x = (cr.Min.x >= 0 and cr.Max.x >= 0) and ImClamp(new_size.x, cr.Min.x, cr.Max.x) or window.SizeFull.x new_size.y = (cr.Min.y >= 0 and cr.Max.y >= 0) and ImClamp(new_size.y, cr.Min.y, cr.Max.y) or window.SizeFull.y if g.NextWindowData.SizeCallback then local data = {} -- TODO: ImGuiSizeCallbackData data.UserData = g.NextWindowData.SizeCallbackUserData data.Pos = ImVec2(window.Pos.x, window.Pos.y) data.CurrentSize = ImVec2(window.SizeFull.x, window.SizeFull.y) data.DesiredSize = ImVec2(new_size.x, new_size.y) g.NextWindowData.SizeCallback(data) new_size.x = data.DesiredSize.x new_size.y = data.DesiredSize.y end new_size.x = IM_TRUNC(new_size.x) new_size.y = IM_TRUNC(new_size.y) end local size_min = CalcWindowMinSize(window) return ImMax(new_size, size_min) end --- @param window ImGuiWindow --- @param content_size_current ImVec2 --- @param content_size_ideal ImVec2 local function CalcWindowContentSizes(window, content_size_current, content_size_ideal) local preserve_old_content_sizes = false if (window.Collapsed and window.AutoFitFramesX <= 0 and window.AutoFitFramesY <= 0) then preserve_old_content_sizes = true elseif (window.Hidden and window.HiddenFramesCannotSkipItems == 0 and window.HiddenFramesCanSkipItems > 0) then preserve_old_content_sizes = true end if preserve_old_content_sizes then content_size_current.x = window.ContentSize.x content_size_current.y = window.ContentSize.y content_size_ideal.x = window.ContentSizeIdeal.x content_size_ideal.y = window.ContentSizeIdeal.y return end if (window.ContentSizeExplicit.x ~= 0.0) then content_size_current.x = window.ContentSizeExplicit.x else content_size_current.x = ImTrunc64(window.DC.CursorMaxPos.x - window.DC.CursorStartPos.x) end if (window.ContentSizeExplicit.y ~= 0.0) then content_size_current.y = window.ContentSizeExplicit.y else content_size_current.y = ImTrunc64(window.DC.CursorMaxPos.y - window.DC.CursorStartPos.y) end if (window.ContentSizeExplicit.x ~= 0.0) then content_size_ideal.x = window.ContentSizeExplicit.x else content_size_ideal.x = ImTrunc64(ImMax(window.DC.CursorMaxPos.x, window.DC.IdealMaxPos.x) - window.DC.CursorStartPos.x) end if (window.ContentSizeExplicit.y ~= 0.0) then content_size_ideal.y = window.ContentSizeExplicit.y else content_size_ideal.y = ImTrunc64(ImMax(window.DC.CursorMaxPos.y, window.DC.IdealMaxPos.y) - window.DC.CursorStartPos.y) end end --- @param window ImGuiWindow # The window to calculate auto-fit size for --- @param size_contents ImVec2 # The content size --- @param axis_mask int # The axis mask to determine which axes to auto-fit --- @return ImVec2 # The auto-fit size local function CalcWindowAutoFitSize(window, size_contents, axis_mask) local g = GImGui local style = g.Style local decoration_w_without_scrollbars = window.DecoOuterSizeX1 + window.DecoOuterSizeX2 - window.ScrollbarSizes.x local decoration_h_without_scrollbars = window.DecoOuterSizeY1 + window.DecoOuterSizeY2 - window.ScrollbarSizes.y local size_pad = ImVec2(window.WindowPadding.x * 2, window.WindowPadding.y * 2) local size_desired = ImVec2() size_desired.x = (bit32.band(axis_mask, 1) ~= 0) and (size_contents.x + size_pad.x + decoration_w_without_scrollbars) or window.Size.x size_desired.y = (bit32.band(axis_mask, 2) ~= 0) and (size_contents.y + size_pad.y + decoration_h_without_scrollbars) or window.Size.y local size_max = ImVec2(FLT_MAX, FLT_MAX) if bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) == 0 or bit32.band(window.Flags, ImGuiWindowFlags.Popup) ~= 0 then if not window.ViewportOwned then size_max = ImGui.GetMainViewport().WorkSize - style.DisplaySafeAreaPadding * 2.0 end local monitor_idx = window.ViewportAllowPlatformMonitorExtend if monitor_idx >= 1 and monitor_idx <= g.PlatformIO.Monitors.Size then size_max = g.PlatformIO.Monitors.Data[monitor_idx].WorkSize - style.DisplaySafeAreaPadding * 2.0 end end if bit32.band(window.Flags, ImGuiWindowFlags.Tooltip) ~= 0 then return ImMin(size_desired, size_max) else local size_min = CalcWindowMinSize(window) local size_auto_fit = ImClamp(size_desired, ImMin(size_min, size_max), size_max) local size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit) local size_contents_for_scrollbar_x = (bit32.band(axis_mask, 1) ~= 0) and size_contents.x or window.ContentSize.x local size_contents_for_scrollbar_y = (bit32.band(axis_mask, 2) ~= 0) and size_contents.y or window.ContentSize.y local will_have_scrollbar_x = (size_auto_fit_after_constraint.x < size_contents_for_scrollbar_x + size_pad.x + decoration_w_without_scrollbars and bit32.band(window.Flags, ImGuiWindowFlags.NoScrollbar) == 0 and bit32.band(window.Flags, ImGuiWindowFlags.HorizontalScrollbar) ~= 0) or bit32.band(window.Flags, ImGuiWindowFlags.AlwaysHorizontalScrollbar) ~= 0 local will_have_scrollbar_y = (size_auto_fit_after_constraint.y < size_contents_for_scrollbar_y + size_pad.y + decoration_h_without_scrollbars and bit32.band(window.Flags, ImGuiWindowFlags.NoScrollbar) == 0) or bit32.band(window.Flags, ImGuiWindowFlags.AlwaysVerticalScrollbar) ~= 0 if will_have_scrollbar_x then size_auto_fit.y = size_auto_fit.y + style.ScrollbarSize end if will_have_scrollbar_y then size_auto_fit.x = size_auto_fit.x + style.ScrollbarSize end return size_auto_fit end end --- @param window ImGuiWindow --- @return ImVec2 function ImGui.CalcWindowNextAutoFitSize(window) local size_contents_current = ImVec2() local size_contents_ideal = ImVec2() CalcWindowContentSizes(window, size_contents_current, size_contents_ideal) local size_auto_fit = CalcWindowAutoFitSize(window, size_contents_ideal, -1) local size_final = CalcWindowSizeAfterConstraint(window, size_auto_fit) return size_final end --- @param window ImGuiWindow --- @return ImGuiCol local function GetWindowBgColorIdx(window) if bit32.band(window.Flags, bit32.bor(ImGuiWindowFlags.Tooltip, ImGuiWindowFlags.Popup)) ~= 0 then return ImGuiCol.PopupBg elseif bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 and not window.DockIsActive then return ImGuiCol.ChildBg else return ImGuiCol.WindowBg end end --- @param window ImGuiWindow --- @param corner_target_arg ImVec2 --- @param corner_norm ImVec2 --- @return ImVec2, ImVec2 --- @nodiscard local function CalcResizePosSizeFromAnyCorner(window, corner_target_arg, corner_norm) local corner_target = ImVec2() ImVec2_Copy(corner_target, corner_target_arg) if bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 then local parent_window = window.ParentWindow local parent_flags = parent_window.Flags local limit_rect = ImRect() ImRect_Copy(limit_rect, parent_window.InnerRect) limit_rect:Expand(ImVec2(-ImMax(parent_window.WindowPadding.x, parent_window.WindowBorderSize), -ImMax(parent_window.WindowPadding.y, parent_window.WindowBorderSize))) if (bit32.band(parent_flags, bit32.bor(ImGuiWindowFlags.HorizontalScrollbar, ImGuiWindowFlags.AlwaysHorizontalScrollbar)) == 0) or (bit32.band(parent_flags, ImGuiWindowFlags.NoScrollbar) ~= 0) then corner_target.x = ImClamp(corner_target.x, limit_rect.Min.x, limit_rect.Max.x) end if bit32.band(parent_flags, ImGuiWindowFlags.NoScrollbar) ~= 0 then corner_target.y = ImClamp(corner_target.y, limit_rect.Min.y, limit_rect.Max.y) end end local pos_min = ImLerp(corner_target, window.Pos, corner_norm) local pos_max = ImLerp(window.Pos + window.Size, corner_target, corner_norm) local size_expected = pos_max - pos_min local size_constrained = CalcWindowSizeAfterConstraint(window, size_expected) local out_pos = pos_min if corner_norm.x == 0.0 then out_pos.x = out_pos.x - (size_constrained.x - size_expected.x) end if corner_norm.y == 0.0 then out_pos.y = out_pos.y - (size_constrained.y - size_expected.y) end return out_pos, size_constrained end --- @param window ImGuiWindow --- @param resize_grip_count int --- @param resize_grip_col table --- @param visibility_rect ImRect --- @return int, int, int local function UpdateWindowManualResize(window, resize_grip_count, resize_grip_col, visibility_rect) local g = GImGui local flags = window.Flags if (bit32.band(flags, ImGuiWindowFlags.NoResize) ~= 0 or window.AutoFitFramesX > 0 or window.AutoFitFramesY > 0) then return 0, -1, -1 end if (bit32.band(flags, ImGuiWindowFlags.AlwaysAutoResize) ~= 0 and bit32.band(window.ChildFlags, bit32.bor(ImGuiChildFlags.ResizeX, ImGuiChildFlags.ResizeY)) == 0) then return 0, -1, -1 end if window.WasActive == false then return 0, -1, -1 end local border_hovered = -1 local border_held = -1 local ret_auto_fit_mask = 0x00 local grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.35, g.Style.WindowRounding + 1.0 + g.FontSize * 0.2)) local grip_hover_inner_size = (resize_grip_count > 0) and IM_TRUNC(grip_draw_size * 0.75) or 0.0 local grip_hover_outer_size = g.WindowsBorderHoverPadding + 1 ImGui.PushID("#RESIZE") local pos_target = ImVec2(FLT_MAX, FLT_MAX) local size_target = ImVec2(FLT_MAX, FLT_MAX) -- Clip mouse interaction rectangles within the viewport rectangle (in practice the narrowing is going to happen most of the time). -- - Not narrowing would mostly benefit the situation where OS windows _without_ decoration have a threshold for hovering when outside their limits. -- This is however not the case with current backends under Win32, but a custom borderless window implementation would benefit from it. -- - When decoration are enabled we typically benefit from that distance, but then our resize elements would be conflicting with OS resize elements, so we also narrow. -- - Note that we are unable to tell if the platform setup allows hovering with a distance threshold (on Win32, decorated window have such threshold). -- We only clip interaction so we overwrite window->ClipRect, cannot call PushClipRect() yet as DrawList is not yet setup. local clip_with_viewport_rect = bit32.band(g.IO.BackendFlags, ImGuiBackendFlags.HasMouseHoveredViewport) == 0 or g.IO.MouseHoveredViewport ~= window.ViewportId or bit32.band(window.Viewport.Flags, ImGuiViewportFlags.NoDecoration) == 0 if (clip_with_viewport_rect) then ImRect_Copy(window.ClipRect, window.Viewport:GetMainRect()) end local clamp_rect = ImRect() ImRect_Copy(clamp_rect, visibility_rect) local window_move_from_title_bar = (bit32.band(window.BgClickFlags, ImGuiWindowBgClickFlags.Move) == 0) and (bit32.band(window.Flags, ImGuiWindowFlags.NoTitleBar) == 0) if window_move_from_title_bar then clamp_rect.Min.y = clamp_rect.Min.y - window.TitleBarHeight end for resize_grip_n = 0, resize_grip_count - 1 do local def = ImGuiResizeGripDef[resize_grip_n + 1] local corner_pos = def.CornerPosN local inner_dir = def.InnerDir local corner = ImVec2(window.Pos.x + corner_pos.x * window.Size.x, window.Pos.y + corner_pos.y * window.Size.y) local resize_rect = ImRect(corner - inner_dir * grip_hover_outer_size, corner + inner_dir * grip_hover_inner_size) if resize_rect.Min.x > resize_rect.Max.x then resize_rect.Min.x, resize_rect.Max.x = resize_rect.Max.x, resize_rect.Min.x end if resize_rect.Min.y > resize_rect.Max.y then resize_rect.Min.y, resize_rect.Max.y = resize_rect.Max.y, resize_rect.Min.y end local resize_grip_id = window:GetID(resize_grip_n) ImGui.ItemAdd(resize_rect, resize_grip_id, nil, ImGuiItemFlags.NoNav) local _, hovered, held = ImGui.ButtonBehavior(resize_rect, resize_grip_id, bit32.bor(ImGuiButtonFlags.FlattenChildren, ImGuiButtonFlags.NoNavFocus)) if hovered or held then ImGui.SetMouseCursor((bit32.band(resize_grip_n, 1) ~= 0) and ImGuiMouseCursor.ResizeNESW or ImGuiMouseCursor.ResizeNWSE) end if held and g.IO.MouseDoubleClicked[0] then local size_auto_fit = CalcWindowAutoFitSize(window, window.ContentSizeIdeal, -1) size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit) ret_auto_fit_mask = 0x03 ImGui.ClearActiveID() elseif held then local clamp_min = ImVec2((corner_pos.x == 1.0) and clamp_rect.Min.x or -FLT_MAX, (corner_pos.y == 1.0) and clamp_rect.Min.y or -FLT_MAX) local clamp_max = ImVec2((corner_pos.x == 0.0) and clamp_rect.Max.x or FLT_MAX, (corner_pos.y == 0.0) and clamp_rect.Max.y or FLT_MAX) local corner_target = g.IO.MousePos - g.ActiveIdClickOffset + ImLerp(def.InnerDir * grip_hover_outer_size, def.InnerDir * -grip_hover_inner_size, def.CornerPosN) corner_target = ImClamp(corner_target, clamp_min, clamp_max) pos_target, size_target = CalcResizePosSizeFromAnyCorner(window, corner_target, corner_pos) end local resize_grip_visible = held or hovered or (resize_grip_n == 0 and bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) == 0) if resize_grip_visible then local color if held then color = ImGui.GetColorU32(ImGuiCol.ResizeGripActive) else if hovered then color = ImGui.GetColorU32(ImGuiCol.ResizeGripHovered) else color = ImGui.GetColorU32(ImGuiCol.ResizeGrip) end end resize_grip_col[resize_grip_n + 1] = color end end local resize_border_mask = 0x00 if bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 then local mask_x = (bit32.band(window.ChildFlags, ImGuiChildFlags.ResizeX) ~= 0) and 0x02 or 0 local mask_y = (bit32.band(window.ChildFlags, ImGuiChildFlags.ResizeY) ~= 0) and 0x08 or 0 resize_border_mask = bit32.bor(mask_x, mask_y) else resize_border_mask = g.IO.ConfigWindowsResizeFromEdges and 0x0F or 0x00 end for border_n = 0, 3 do repeat if bit32.band(resize_border_mask, bit32.lshift(1, border_n)) == 0 then break end local def = ImGuiResizeBorderDef[border_n + 1] local axis if border_n == ImGuiDir.Left or border_n == ImGuiDir.Right then axis = ImGuiAxis.X else axis = ImGuiAxis.Y end local border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, g.WindowsBorderHoverPadding) local border_id = window:GetID(border_n + 4) ImGui.ItemAdd(border_rect, border_id, nil, ImGuiItemFlags.NoNav) local _, hovered, held = ImGui.ButtonBehavior(border_rect, border_id, bit32.bor(ImGuiButtonFlags.FlattenChildren, ImGuiButtonFlags.NoNavFocus)) if hovered and g.HoveredIdTimer <= WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER then hovered = false end if hovered or held then ImGui.SetMouseCursor((axis == ImGuiAxis.X) and ImGuiMouseCursor.ResizeEW or ImGuiMouseCursor.ResizeNS) end if held and g.IO.MouseDoubleClicked[0] then -- Double-clicking bottom or right border auto-fit on this axis -- FIXME: Support top and right borders: rework CalcResizePosSizeFromAnyCorner() to be reusable in both cases. if border_n == 1 or border_n == 3 then -- Right and bottom border local size_auto_fit = CalcWindowAutoFitSize(window, window.ContentSizeIdeal, bit32.lshift(1, axis - 1)) size_target[axis] = CalcWindowSizeAfterConstraint(window, size_auto_fit)[axis] ret_auto_fit_mask = bit32.bor(ret_auto_fit_mask, bit32.lshift(1, axis - 1)) hovered = false held = false -- So border doesn't show highlighted at new position end ImGui.ClearActiveID() elseif held then local just_scrolled_manually_while_resizing = (g.WheelingWindow ~= nil and g.WheelingWindowScrolledFrame == g.FrameCount and ImGui.IsWindowChildOf(window, g.WheelingWindow, false, true)) if g.ActiveIdIsJustActivated or just_scrolled_manually_while_resizing then g.WindowResizeBorderExpectedRect = border_rect g.WindowResizeRelativeMode = false end if (bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0) and g.WindowResizeBorderExpectedRect ~= border_rect then g.WindowResizeRelativeMode = true end local border_curr = window.Pos + ImMin(def.SegmentN1, def.SegmentN2) * window.Size local border_target_rel_mode_for_axis local border_target_abs_mode_for_axis border_target_rel_mode_for_axis = border_curr[axis] + g.IO.MouseDelta[axis] border_target_abs_mode_for_axis = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + g.WindowsBorderHoverPadding -- Match ButtonBehavior() padding above -- Use absolute mode position local border_target = ImVec2() ImVec2_Copy(border_target, window.Pos) border_target[axis] = border_target_abs_mode_for_axis -- Use relative mode target for child window, ignore resize when moving back toward the ideal absolute position. local ignore_resize = false if g.WindowResizeRelativeMode then border_target[axis] = border_target_rel_mode_for_axis if g.IO.MouseDelta[axis] == 0.0 or ((g.IO.MouseDelta[axis] > 0.0) == (border_target_rel_mode_for_axis > border_target_abs_mode_for_axis)) then ignore_resize = true end end local clamp_min = ImVec2((border_n == ImGuiDir.Right) and clamp_rect.Min.x or -FLT_MAX, (border_n == ImGuiDir.Down or (border_n == ImGuiDir.Up and window_move_from_title_bar)) and clamp_rect.Min.y or -FLT_MAX) local clamp_max = ImVec2((border_n == ImGuiDir.Left) and clamp_rect.Max.x or FLT_MAX, (border_n == ImGuiDir.Up) and clamp_rect.Max.y or FLT_MAX) border_target = ImClamp(border_target, clamp_min, clamp_max) if not ignore_resize then pos_target, size_target = CalcResizePosSizeFromAnyCorner(window, border_target, ImMin(def.SegmentN1, def.SegmentN2)) end end if hovered then border_hovered = border_n end if held then border_held = border_n end until true end ImGui.PopID() window.DC.NavLayerCurrent = ImGuiNavLayer.Main if size_target.x ~= FLT_MAX and (window.Size.x ~= size_target.x or window.SizeFull.x ~= size_target.x) then window.Size.x = size_target.x window.SizeFull.x = size_target.x end if size_target.y ~= FLT_MAX and (window.Size.y ~= size_target.y or window.SizeFull.y ~= size_target.y) then window.Size.y = size_target.y window.SizeFull.y = size_target.y end if pos_target.x ~= FLT_MAX and window.Pos.x ~= ImFloor(pos_target.x) then window.Pos.x = ImFloor(pos_target.x) end if pos_target.y ~= FLT_MAX and window.Pos.y ~= ImFloor(pos_target.y) then window.Pos.y = ImFloor(pos_target.y) end if border_held ~= -1 then g.WindowResizeBorderExpectedRect = GetResizeBorderRect(window, border_held, grip_hover_inner_size, g.WindowsBorderHoverPadding) end return ret_auto_fit_mask, border_hovered, border_held end --- @param pos ImVec2 --- @param wrap_pos_x float --- @return float function ImGui.CalcWrapWidthForPos(pos, wrap_pos_x) if wrap_pos_x < 0.0 then return 0.0 end local g = GImGui local window = g.CurrentWindow if wrap_pos_x == 0.0 then wrap_pos_x = window.WorkRect.Max.x elseif wrap_pos_x > 0.0 then wrap_pos_x = wrap_pos_x + window.Pos.x - window.Scroll.x end return ImMax(wrap_pos_x - pos.x, 1.0) end --- @param text ImString --- @param text_end int? --- @return int # Exclusive upper bound @native function ImGui.FindRenderedTextEnd(text, text_end) local text_display_end = 1 local text_len = #text if not text_end then text_end = text_len + 1 end text_display_end = ImMemchr(text, "##", 1) or text_end text_display_end = ImMin(text_display_end, text_end) return text_display_end end --- @param pos ImVec2 --- @param text string --- @param text_begin int? --- @param text_end int? --- @param hide_text_after_hash bool? # true by default @native function ImGui.RenderText(pos, text, text_begin, text_end, hide_text_after_hash) if text_begin == nil then text_begin = 1 end if hide_text_after_hash == nil then hide_text_after_hash = true end local g = GImGui local window = g.CurrentWindow -- Hide anything after a '##' string local text_display_end if hide_text_after_hash then text_display_end = ImGui.FindRenderedTextEnd(text, text_end) else if text_end == nil then text_end = #text + 1 end text_display_end = text_end end if text ~= "" and text_display_end > 1 then window.DrawList:AddText(g.Font, g.FontSize, pos, ImGui.GetColorU32(ImGuiCol.Text), text, text_begin, text_display_end, 0.0) if g.LogEnabled then -- ImGui.LogRenderedText(&pos, text, text_display_end); end end end --- @param pos ImVec2 --- @param text string --- @param text_end int? --- @param wrap_width float function ImGui.RenderTextWrapped(pos, text, text_end, wrap_width) local g = GImGui local window = g.CurrentWindow if text_end == nil then text_end = #text + 1 end if text ~= "" then window.DrawList:AddText(g.Font, g.FontSize, pos, ImGui.GetColorU32(ImGuiCol.Text), text, 1, text_end, wrap_width) end end --- @param draw_list ImDrawList --- @param pos_min ImVec2 --- @param pos_max ImVec2 --- @param text string --- @param text_begin int --- @param text_display_end int --- @param text_size_if_known? ImVec2 --- @param align? ImVec2 --- @param clip_rect? ImRect @native function ImGui.RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_begin, text_display_end, text_size_if_known, align, clip_rect) if not align then align = ImVec2(0, 0) end local pos = ImVec2() ImVec2_Copy(pos, pos_min) local text_size = text_size_if_known or ImGui.CalcTextSize(text, text_display_end, false, 0.0) local clip_min = clip_rect and clip_rect.Min or pos_min local clip_max = clip_rect and clip_rect.Max or pos_max local need_clipping = (pos.x + text_size.x >= clip_max.x) or (pos.y + text_size.y >= clip_max.y) if (clip_rect) then need_clipping = need_clipping or ((pos.x < clip_min.x) or (pos.y < clip_min.y)) end if (align.x > 0.0) then pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x) end if (align.y > 0.0) then pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y) end local g = GImGui if (need_clipping) then local fine_clip_rect = ImVec4(clip_min.x, clip_min.y, clip_max.x, clip_max.y) draw_list:AddText(nil, 0.0, pos, ImGui.GetColorU32(ImGuiCol.Text), text, text_begin, text_display_end, 0.0, fine_clip_rect) else draw_list:AddText(nil, 0.0, pos, ImGui.GetColorU32(ImGuiCol.Text), text, text_begin, text_display_end, 0.0, nil) end end --- @param pos_min ImVec2 --- @param pos_max ImVec2 --- @param text string --- @param text_end? int --- @param text_size_if_known? ImVec2 --- @param align? ImVec2 --- @param clip_rect? ImRect function ImGui.RenderTextClipped(pos_min, pos_max, text, text_end, text_size_if_known, align, clip_rect) if not align then align = ImVec2(0, 0) end local text_display_end = ImGui.FindRenderedTextEnd(text, text_end) local text_len = text_display_end - 1 if text_len == 0 then return end local g = GImGui local window = g.CurrentWindow ImGui.RenderTextClippedEx(window.DrawList, pos_min, pos_max, text, 1, text_display_end, text_size_if_known, align, clip_rect) -- if (g.LogEnabled) -- ImGui.LogRenderedText(&pos_min, text, text_display_end); end --- @param bb ImRect --- @param id ImGuiID --- @param flags? ImGuiNavRenderCursorFlags --- @param rounding? float @native function ImGui.RenderNavCursor(bb, id, flags, rounding) if flags == nil then flags = ImGuiNavRenderCursorFlags.None end if rounding == nil then rounding = -1.0 end local g = GImGui if id ~= g.NavId then return end if not g.NavCursorVisible and bit32.band(flags, ImGuiNavRenderCursorFlags.AlwaysDraw) == 0 then return end if id == g.LastItemData.ID and bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.NoNav) ~= 0 then return end local window = g.CurrentWindow if window.DC.NavHideHighlightOneFrame then return end if rounding < 0.0 then rounding = g.Style.FrameRounding end local display_rect = ImRect() ImRect_Copy(display_rect, bb) display_rect:ClipWith(window.ClipRect) local scale_factor = ImGui.GetScale() local thickness = ImTrunc(ImMax(2.0, 1.5 * scale_factor)) if bit32.band(flags, ImGuiNavRenderCursorFlags.Compact) ~= 0 then window.DrawList:AddRect(display_rect.Min, display_rect.Max, ImGui.GetColorU32(ImGuiCol.NavCursor), rounding, thickness) else local distance = ImTrunc(3.0 + thickness * 0.5) display_rect:Expand(ImVec2(distance, distance)) local fully_visible = window.ClipRect:Contains(display_rect) if not fully_visible then window.DrawList:PushClipRect(display_rect.Min, display_rect.Max) end window.DrawList:AddRect(display_rect.Min, display_rect.Max, ImGui.GetColorU32(ImGuiCol.NavCursor), rounding, thickness) if not fully_visible then window.DrawList:PopClipRect() end end end do --[[ImGui.RenderMouseCursor]] local offset = ImVec2() local size = ImVec2() local uv_border = {ImVec2(), ImVec2()} local uv_fill = {ImVec2(), ImVec2()} --- @param base_pos ImVec2 --- @param base_scale float --- @param mouse_cursor ImGuiMouseCursor --- @param col_fill ImU32 --- @param col_border ImU32 --- @param col_shadow ImU32 function ImGui.RenderMouseCursor(base_pos, base_scale, mouse_cursor, col_fill, col_border, col_shadow) local g = GImGui if mouse_cursor <= ImGuiMouseCursor.None or mouse_cursor >= ImGuiMouseCursor.COUNT then -- We intentionally accept out of bound values mouse_cursor = ImGuiMouseCursor.Arrow end local font_atlas = g.DrawListSharedData.FontAtlas for _, viewport in g.Viewports:iter() do repeat if not ImFontAtlasGetMouseCursorTexData(font_atlas, mouse_cursor, offset, size, uv_border, uv_fill) then break end local pos = base_pos - offset local scale = base_scale * viewport.DpiScale if not viewport:GetMainRect():Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale)) then break end local draw_list = ImGui.GetForegroundDrawList(viewport) local tex_ref = font_atlas.TexRef draw_list:PushTexture(tex_ref); draw_list:AddImage(tex_ref, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv_fill[1], uv_fill[2], col_shadow) draw_list:AddImage(tex_ref, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv_fill[1], uv_fill[2], col_shadow) draw_list:AddImage(tex_ref, pos, pos + size * scale, uv_fill[1], uv_fill[2], col_border) draw_list:AddImage(tex_ref, pos, pos + size * scale, uv_border[1], uv_border[2], col_fill) if mouse_cursor == ImGuiMouseCursor.Wait or mouse_cursor == ImGuiMouseCursor.Progress then local a_min = ImFmod(g.Time * 5.0, 2.0 * IM_PI) local a_max = a_min + IM_PI * 1.65 draw_list:PathArcTo(pos + ImVec2(14, -1) * scale, 6.0 * scale, a_min, a_max) draw_list:PathStroke(col_fill, 3.0 * scale) end draw_list:PopTexture() until true end end end --- Another overly complex function until we reorganize everything into a nice all-in-one helper. --- This is made more complex because we have dissociated the layout rectangle (pos_min .. pos_max) from 'ellipsis_max_x' which may be beyond it. --- This is because in the context of tabs we selectively hide part of the text when the Close Button appears, but we don't want the ellipsis to move. --- @param draw_list ImDrawList --- @param pos_min ImVec2 --- @param pos_max ImVec2 --- @param ellipsis_max_x float --- @param text string --- @param text_end_full? int --- @param text_size_if_known? ImVec2 function ImGui.RenderTextEllipsis(draw_list, pos_min, pos_max, ellipsis_max_x, text, text_end_full, text_size_if_known) local g = GImGui if text_end_full == nil then text_end_full = ImGui.FindRenderedTextEnd(text) end local text_size if text_size_if_known then text_size = ImVec2() ImVec2_Copy(text_size, text_size_if_known) else text_size = ImGui.CalcTextSize(text, text_end_full, false, 0.0) end -- draw_list:AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 6), IM_COL32(0, 0, 255, 255)) -- draw_list:AddLine(ImVec2(ellipsis_max_x, pos_min.y - 2), ImVec2(ellipsis_max_x, pos_max.y + 3), IM_COL32(0, 255, 0, 255)) -- FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few pixels. if text_size.x > pos_max.x - pos_min.x then -- Hello wo... -- | | | -- min max ellipsis_max -- <-> this is generally some padding value local font = draw_list._Data.Font local font_size = draw_list._Data.FontSize local font_scale = draw_list._Data.FontScale local text_end_ellipsis = nil local baked = font:GetFontBaked(font_size) local ellipsis_width = baked:GetCharAdvance(font.EllipsisChar) * font_scale -- We can now claim the space between pos_max.x and ellipsis_max.x local text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_width) - pos_min.x, 1.0) local text_size_clipped text_size_clipped, text_end_ellipsis = font:CalcTextSizeA(font_size, text_avail_width, 0.0, text, 1, text_end_full) local text_size_clipped_x = text_size_clipped.x -- Render text, render ellipsis ImGui.RenderTextClippedEx(draw_list, pos_min, pos_max, text, 1, text_end_ellipsis, text_size, ImVec2(0.0, 0.0)) local cpu_fine_clip_rect = ImVec4(pos_min.x, pos_min.y, pos_max.x, pos_max.y) local ellipsis_pos = ImTrunc(ImVec2(pos_min.x + text_size_clipped_x, pos_min.y)) font:RenderChar(draw_list, font_size, ellipsis_pos, ImGui.GetColorU32(ImGuiCol.Text), font.EllipsisChar, cpu_fine_clip_rect) else ImGui.RenderTextClippedEx(draw_list, pos_min, pos_max, text, 1, text_end_full, text_size, ImVec2(0.0, 0.0)) end if g.LogEnabled then ImGui.LogRenderedText(pos_min, text, text_end_full) end end --- @param p_min ImVec2 --- @param p_max ImVec2 --- @param fill_col ImU32 --- @param borders? bool --- @param rounding? float @native function ImGui.RenderFrame(p_min, p_max, fill_col, borders, rounding) if borders == nil then borders = true end if rounding == nil then rounding = 0.0 end local g = GImGui local window = g.CurrentWindow window.DrawList:AddRectFilled(p_min, p_max, fill_col, rounding) local border_size = g.Style.FrameBorderSize if borders and border_size > 0 then window.DrawList:AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), ImGui.GetColorU32(ImGuiCol.BorderShadow), rounding, border_size, 0) window.DrawList:AddRect(p_min, p_max, ImGui.GetColorU32(ImGuiCol.Border), rounding, border_size, 0) end end --- @param p_min ImVec2 --- @param p_max ImVec2 --- @param rounding float @native function ImGui.RenderFrameBorder(p_min, p_max, rounding) local g = GImGui local window = g.CurrentWindow local border_size = g.Style.FrameBorderSize if border_size > 0.0 then window.DrawList:AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), ImGui.GetColorU32(ImGuiCol.BorderShadow), rounding, border_size, 0) window.DrawList:AddRect(p_min, p_max, ImGui.GetColorU32(ImGuiCol.Border), rounding, border_size, 0) end end --- @param bb ImRect --- @param col ImU32 --- @param rounding float function ImGui.RenderColorComponentMarker(bb, col, rounding) if bb.Min.x + 1 >= bb.Max.x then return end local g = GImGui local window = g.CurrentWindow ImGui.RenderRectFilledInRangeH(window.DrawList, bb, col, bb.Min.x, ImMin(bb.Min.x + g.Style.ColorMarkerSize, bb.Max.x), rounding) end --- @param window ImGuiWindow --- @param border_n int --- @param border_col ImU32 --- @param border_size float local function RenderWindowOuterSingleBorder(window, border_n, border_col, border_size) local def = ImGuiResizeBorderDef[border_n + 1] local rounding = window.WindowRounding local border_r = GetResizeBorderRect(window, border_n, rounding, 0.0) window.DrawList:PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5, 0.5) + def.InnerDir * rounding, rounding, def.OuterAngle - IM_PI * 0.25, def.OuterAngle) window.DrawList:PathArcTo(ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5, 0.5) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25) window.DrawList:PathStroke(border_col, border_size) end local function RenderWindowOuterBorders(window) local g = GImGui local border_size = window.WindowBorderSize local border_col = ImGui.GetColorU32(ImGuiCol.Border) if border_size > 0.0 and (bit32.band(window.Flags, ImGuiWindowFlags.NoBackground) == 0) then window.DrawList:AddRect(window.Pos, window.Pos + window.Size, border_col, window.WindowRounding, window.WindowBorderSize, 0) elseif border_size > 0.0 then if bit32.band(window.ChildFlags, ImGuiChildFlags.ResizeX) ~= 0 then RenderWindowOuterSingleBorder(window, 1, border_col, border_size) end if bit32.band(window.ChildFlags, ImGuiChildFlags.ResizeY) ~= 0 then RenderWindowOuterSingleBorder(window, 3, border_col, border_size) end end if window.ResizeBorderHovered ~= -1 or window.ResizeBorderHeld ~= -1 then local border_n = (window.ResizeBorderHeld ~= -1) and window.ResizeBorderHeld or window.ResizeBorderHovered local border_col_resizing = ImGui.GetColorU32((window.ResizeBorderHeld ~= -1) and ImGuiCol.SeparatorActive or ImGuiCol.SeparatorHovered) RenderWindowOuterSingleBorder(window, border_n, border_col_resizing, ImMax(2.0, window.WindowBorderSize)) end if g.Style.FrameBorderSize > 0 and (bit32.band(window.Flags, ImGuiWindowFlags.NoTitleBar) == 0) and not window.DockIsActive then local y = window.Pos.y + window.TitleBarHeight - 1 window.DrawList:AddLineH(window.Pos.x + border_size * 0.5, window.Pos.x + window.Size.x - border_size * 0.5, y, border_col, g.Style.FrameBorderSize) end end --- @param window ImGuiWindow --- @param title_bar_rect ImRect --- @param title_bar_is_highlight bool --- @param handle_borders_and_resize_grips bool --- @param resize_grip_col ImU32[] --- @param resize_grip_draw_size float local function RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips, resize_grip_col, resize_grip_draw_size) local g = GImGui local style = g.Style local flags = window.Flags local window_rounding = window.WindowRounding local window_border_size = window.WindowBorderSize if window.Collapsed then local backup_border_size = style.FrameBorderSize g.Style.FrameBorderSize = window.WindowBorderSize local title_bar_col = ImGui.GetColorU32((title_bar_is_highlight and g.NavCursorVisible) and ImGuiCol.TitleBgActive or ImGuiCol.TitleBgCollapsed) ImGui.RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding) g.Style.FrameBorderSize = backup_border_size else -- Window background if bit32.band(flags, ImGuiWindowFlags.NoBackground) == 0 then local bg_col = ImGui.GetColorU32(GetWindowBgColorIdx(window)) local override_alpha = false local alpha = 1.0 if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasBgAlpha) ~= 0 then alpha = g.NextWindowData.BgAlphaVal override_alpha = true end if override_alpha then bg_col = bit32.band(bg_col, bit32.bnot(IM_COL32_A_MASK)) or bit32.lshift(IM_F32_TO_INT8_SAT(alpha), IM_COL32_A_SHIFT) end if bit32.band(bg_col, IM_COL32_A_MASK) ~= 0 then local bg_rect = ImRect(window.Pos + ImVec2(0, window.TitleBarHeight), window.Pos + window.Size) local bg_rounding_flags if window.DockIsActive then bg_rounding_flags = ImGui.CalcRoundingFlagsForRectInRect(bg_rect, window.DockNode.HostWindow:Rect(), 0.0) else bg_rounding_flags = (bit32.band(flags, ImGuiWindowFlags.NoTitleBar) ~= 0) and ImDrawFlags.RoundCornersAll or ImDrawFlags.RoundCornersBottom end local bg_draw_list = window.DockIsActive and window.DockNode.HostWindow.DrawList or window.DrawList if window.DockIsActive then bg_draw_list:ChannelsSetCurrent(0) end bg_draw_list:AddRectFilled(bg_rect.Min, bg_rect.Max, bg_col, window_rounding, bg_rounding_flags) if window.DockIsActive then bg_draw_list:ChannelsSetCurrent(1) end end end if window.DockIsActive then window.DockNode.IsBgDrawnThisFrame = true end -- Title bar if bit32.band(flags, ImGuiWindowFlags.NoTitleBar) == 0 and not window.DockIsActive then local title_bar_col = ImGui.GetColorU32(title_bar_is_highlight and ImGuiCol.TitleBgActive or ImGuiCol.TitleBg) window.DrawList:AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags.RoundCornersTop) end -- Menu bar if bit32.band(flags, ImGuiWindowFlags.MenuBar) ~= 0 then local menu_bar_rect = window:MenuBarRect() menu_bar_rect:ClipWith(window:Rect()) window.DrawList:AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, ImGui.GetColorU32(ImGuiCol.MenuBarBg), (bit32.band(flags, ImGuiWindowFlags.NoTitleBar) ~= 0) and window_rounding or 0.0, ImDrawFlags.RoundCornersTop) if style.FrameBorderSize > 0.0 and menu_bar_rect.Max.y < window.Pos.y + window.Size.y then window.DrawList:AddLineH(menu_bar_rect.Min.x + window_border_size * 0.5, menu_bar_rect.Max.x - window_border_size * 0.5, menu_bar_rect.Max.y, ImGui.GetColorU32(ImGuiCol.Border), style.FrameBorderSize) end end -- Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock local node = window.DockNode if window.DockIsActive and node:IsHiddenTabBar() and not node:IsNoTabBar() then local unhide_sz_draw = ImTrunc(g.FontSize * 0.70) local unhide_sz_hit = ImTrunc(g.FontSize * 0.55) local p = node.Pos local r = ImRect(p, p + ImVec2(unhide_sz_hit, unhide_sz_hit)) local unhide_id = window:GetID("#UNHIDE") ImGui.KeepAliveID(unhide_id) local pressed, hovered, held = ImGui.ButtonBehavior(r, unhide_id, ImGuiButtonFlags.FlattenChildren) if pressed then node.WantHiddenTabBarToggle = true elseif held and ImGui.IsMouseDragging(0) then ImGui.StartMouseMovingWindowOrNode(window, node, true) end local col = ImGui.GetColorU32(((held and hovered) or (node.IsFocused and not hovered)) and ImGuiCol.ButtonActive or (hovered and ImGuiCol.ButtonHovered or ImGuiCol.Button)) window.DrawList:AddTriangleFilled(p, p + ImVec2(unhide_sz_draw, 0.0), p + ImVec2(0.0, unhide_sz_draw), col) end if window.ScrollbarX then ImGui.Scrollbar(ImGuiAxis.X) end if window.ScrollbarY then ImGui.Scrollbar(ImGuiAxis.Y) end -- Resize grip(s) if handle_borders_and_resize_grips and (bit32.band(flags, ImGuiWindowFlags.NoResize) == 0) then for resize_grip_n = 1, #ImGuiResizeGripDef do repeat local col = resize_grip_col[resize_grip_n] if bit32.band(col, IM_COL32_A_MASK) == 0 then break end local grip = ImGuiResizeGripDef[resize_grip_n] local corner = ImLerp(window.Pos, window.Pos + window.Size, grip.CornerPosN) local border_inner = IM_ROUND(window_border_size * 0.5) window.DrawList:PathLineTo(corner + grip.InnerDir * ((resize_grip_n % 2 == 0) and ImVec2(border_inner, resize_grip_draw_size) or ImVec2(resize_grip_draw_size, border_inner))) window.DrawList:PathLineTo(corner + grip.InnerDir * ((resize_grip_n % 2 == 0) and ImVec2(resize_grip_draw_size, border_inner) or ImVec2(border_inner, resize_grip_draw_size))) window.DrawList:PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + border_inner), corner.y + grip.InnerDir.y * (window_rounding + border_inner)), window_rounding, grip.AngleMin12, grip.AngleMax12) window.DrawList:PathFillConvex(col) until true end end if handle_borders_and_resize_grips and not window.DockNodeAsHost then RenderWindowOuterBorders(window) end end window.DC.NavLayerCurrent = ImGuiNavLayer.Main end ImGui.RenderWindowOuterBorders = RenderWindowOuterBorders --- @param window ImGuiWindow --- @param title_bar_rect ImRect --- @param name string --- @param open bool? --- @return bool? local function RenderWindowTitleBarContents(window, title_bar_rect, name, open) local g = GImGui local style = g.Style local flags = window.Flags local has_close_button = (open ~= nil) local has_collapse_button = bit32.band(flags, ImGuiWindowFlags.NoCollapse) == 0 and (style.WindowMenuButtonPosition ~= ImGuiDir.None) local item_flags_backup = g.CurrentItemFlags g.CurrentItemFlags = bit32.bor(g.CurrentItemFlags, ImGuiItemFlags.NoNavDefaultFocus) window.DC.NavLayerCurrent = ImGuiNavLayer.Menu local pad_l = g.Style.FramePadding.x local pad_r = g.Style.FramePadding.x local button_sz = g.FontSize local close_button_pos local collapse_button_pos if has_close_button then close_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - button_sz, title_bar_rect.Min.y + style.FramePadding.y) pad_r = pad_r + button_sz + style.ItemInnerSpacing.x end if has_collapse_button and style.WindowMenuButtonPosition == ImGuiDir.Right then collapse_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - button_sz, title_bar_rect.Min.y + style.FramePadding.y) pad_r = pad_r + button_sz + style.ItemInnerSpacing.x end if has_collapse_button and style.WindowMenuButtonPosition == ImGuiDir.Left then collapse_button_pos = ImVec2(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y + style.FramePadding.y) pad_l = pad_l + button_sz + style.ItemInnerSpacing.x end if has_collapse_button then if ImGui.CollapseButton(window:GetID("#COLLAPSE"), collapse_button_pos) then window.WantCollapseToggle = true -- Defer actual collapsing to next frame as we are too far in the Begin() function end end if has_close_button then local backup_item_flags = g.CurrentItemFlags g.CurrentItemFlags = bit32.bor(g.CurrentItemFlags, ImGuiItemFlags.NoFocus) if ImGui.CloseButton(window:GetID("#CLOSE"), close_button_pos) then open = false end g.CurrentItemFlags = backup_item_flags end window.DC.NavLayerCurrent = ImGuiNavLayer.Main g.CurrentItemFlags = item_flags_backup local marker_size_x = (bit32.band(flags, ImGuiWindowFlags.UnsavedDocument) ~= 0) and (button_sz * 0.80) or 0.0 local text_size = ImGui.CalcTextSize(name, nil, true) + ImVec2(marker_size_x, 0.0) if (pad_l > style.FramePadding.x) then pad_l = pad_l + g.Style.ItemInnerSpacing.x end if (pad_r > style.FramePadding.x) then pad_r = pad_r + g.Style.ItemInnerSpacing.x end if (style.WindowTitleAlign.x > 0.0 and style.WindowTitleAlign.x < 1.0) then local centerness = ImSaturate(1.0 - ImFabs(style.WindowTitleAlign.x - 0.5) * 2.0) local pad_extend = ImMin(ImMax(pad_l, pad_r), title_bar_rect:GetWidth() - pad_l - pad_r - text_size.x) pad_l = ImMax(pad_l, pad_extend * centerness) pad_r = ImMax(pad_r, pad_extend * centerness) end local layout_r = ImRect(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y, title_bar_rect.Max.x - pad_r, title_bar_rect.Max.y) local clip_r = ImRect(layout_r.Min.x, layout_r.Min.y, ImMin(layout_r.Max.x + g.Style.ItemInnerSpacing.x, title_bar_rect.Max.x), layout_r.Max.y) if bit32.band(flags, ImGuiWindowFlags.UnsavedDocument) ~= 0 then local marker_pos = ImVec2() marker_pos.x = ImClamp(layout_r.Min.x + (layout_r:GetWidth() - text_size.x) * style.WindowTitleAlign.x + text_size.x, layout_r.Min.x, layout_r.Max.x) marker_pos.y = (layout_r.Min.y + layout_r.Max.y) * 0.5 if marker_pos.x > layout_r.Min.x then ImGui.RenderBullet(window.DrawList, marker_pos, ImGui.GetColorU32(ImGuiCol.UnsavedMarker)) clip_r.Max.x = ImMin(clip_r.Max.x, marker_pos.x - math.floor(marker_size_x * 0.5)) end end ImGui.RenderTextClipped(layout_r.Min, layout_r.Max, name, nil, text_size, style.WindowTitleAlign, clip_r) return open end --- @param window ImGuiWindow --- @param flags ImGuiWindowFlags --- @param parent_window? ImGuiWindow function ImGui.UpdateWindowParentAndRootLinks(window, flags, parent_window) window.ParentWindow = parent_window window.RootWindow = window window.RootWindowPopupTree = window window.RootWindowDockTree = window window.RootWindowForTitleBarHighlight = window window.RootWindowForNav = window if parent_window and (bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0) and (bit32.band(flags, ImGuiWindowFlags.Tooltip) == 0) then window.RootWindowDockTree = parent_window.RootWindowDockTree if not window.DockIsActive and (bit32.band(parent_window.Flags, ImGuiWindowFlags.DockNodeHost) == 0) then window.RootWindow = parent_window.RootWindow end end if parent_window and (bit32.band(flags, ImGuiWindowFlags.Popup) ~= 0) then window.RootWindowPopupTree = parent_window.RootWindowPopupTree end if parent_window and (bit32.band(flags, ImGuiWindowFlags.Modal) == 0) and (bit32.band(flags, bit32.bor(ImGuiWindowFlags.ChildWindow, ImGuiWindowFlags.Popup, ImGuiWindowFlags.Tooltip)) ~= 0) then window.RootWindowForTitleBarHighlight = parent_window.RootWindowForTitleBarHighlight end while bit32.band(window.RootWindowForNav.ChildFlags, ImGuiChildFlags.NavFlattened) ~= 0 do IM_ASSERT(window.RootWindowForNav.ParentWindow ~= nil) window.RootWindowForNav = window.RootWindowForNav.ParentWindow end end -- [EXPERIMENTAL] Called by Begin(). NextWindowData is valid at this point. -- This is designed as a toy/test-bed for --- @param window ImGuiWindow function ImGui.UpdateWindowSkipRefresh(window) local g = GImGui window.SkipRefresh = false if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasRefreshPolicy) == 0 then return end if bit32.band(g.NextWindowData.RefreshFlagsVal, ImGuiWindowRefreshFlags.TryToAvoidRefresh) ~= 0 then -- FIXME-IDLE: Tests for e.g. mouse clicks or keyboard while focused. if window.Appearing then -- If currently appearing return end if window.Hidden then -- If was hidden (previous frame) return end if bit32.band(g.NextWindowData.RefreshFlagsVal, ImGuiWindowRefreshFlags.RefreshOnHover) ~= 0 and g.HoveredWindow then if window.RootWindow == g.HoveredWindow.RootWindow or ImGui.IsWindowWithinBeginStackOf(g.HoveredWindow.RootWindow, window) then return end end if bit32.band(g.NextWindowData.RefreshFlagsVal, ImGuiWindowRefreshFlags.RefreshOnFocus) ~= 0 and g.NavWindow then if window.RootWindow == g.NavWindow.RootWindow or ImGui.IsWindowWithinBeginStackOf(g.NavWindow.RootWindow, window) then return end end window.DrawList = nil window.SkipRefresh = true end end --- @param window ImGuiWindow local function SetWindowActiveForSkipRefresh(window) window.Active = true for _, child in window.DC.ChildWindows:iter() do if not child.Hidden then child.Active = true child.SkipRefresh = true SetWindowActiveForSkipRefresh(child) end end end --- @param window ImGuiWindow? local function SetCurrentWindow(window) local g = GImGui --- @diagnostic disable-next-line g.CurrentWindow = window g.CurrentTable = (window and window.DC.CurrentTableIdx ~= -1) and g.Tables:GetByIndex(window.DC.CurrentTableIdx) or nil if window then if bit32.band(g.IO.BackendFlags, ImGuiBackendFlags.RendererHasTextures) ~= 0 then local viewport = window.Viewport g.FontRasterizerDensity = (viewport.FramebufferScale.x ~= 0.0) and viewport.FramebufferScale.x or g.IO.DisplayFramebufferScale.x end local backup_skip_items = window.SkipItems window.SkipItems = false ImGui.UpdateCurrentFontSize(0.0) window.SkipItems = backup_skip_items ImGui.NavUpdateCurrentWindowIsScrollPushableX() end end -- TODO: function ImGui.GcCompactTransientMiscBuffers() local g = GImGui g.ItemFlagsStack:clear() g.GroupStack:clear() g.InputTextLineIndex:clear() ImGui.TableGcCompactSettings() for _, atlas in g.FontAtlases:iter() do atlas:CompactCache() end end --- @param window ImGuiWindow function ImGui.GcCompactTransientWindowBuffers(window) window.MemoryCompacted = true window.MemoryDrawListIdxCapacity = ImMin(math.floor(window.DrawList.IdxBuffer.Size * 1.05), window.DrawList.IdxBuffer.Capacity) window.MemoryDrawListVtxCapacity = ImMin(math.floor(window.DrawList.VtxBuffer.Size * 1.05), window.DrawList.VtxBuffer.Capacity) window.IDStack:clear() window.DrawList:_ClearFreeMemory() window.DC.ChildWindows:clear() window.DC.ItemWidthStack:clear() window.DC.TextWrapPosStack:clear() end --- @param window ImGuiWindow function ImGui.GcAwakeTransientWindowBuffers(window) window.MemoryCompacted = false window.DrawList.IdxBuffer:reserve(window.MemoryDrawListIdxCapacity) window.DrawList.VtxBuffer:reserve(window.MemoryDrawListVtxCapacity) window.MemoryDrawListIdxCapacity = 0 window.MemoryDrawListVtxCapacity = 0 end function ImGui.GetWindowWidth() local window = GImGui.CurrentWindow return window.Size.x end --- @param window ImGuiWindow --- @param pos ImVec2 --- @param cond? ImGuiCond function ImGui.SetWindowPos(window, pos, cond) -- Overloads: SetWindowPos(pos, cond) / SetWindowPos(name, pos, cond) / SetWindowPos(window, pos, cond) if type(window) == "string" then window = ImGui.FindWindowByName(window) if window == nil then return end elseif window.SetWindowPosAllowFlags == nil then window, pos, cond = ImGui.GetCurrentWindowRead(), window, pos end if cond == nil then cond = 0 end if (cond ~= 0) and (bit32.band(window.SetWindowPosAllowFlags, cond) == 0) then return end IM_ASSERT(cond == 0 or ImIsPowerOfTwo(cond)) window.SetWindowPosAllowFlags = bit32.band(window.SetWindowPosAllowFlags, bit32.bnot(bit32.bor(ImGuiCond.Once, ImGuiCond.FirstUseEver, ImGuiCond.Appearing))) ImVec2_Copy(window.SetWindowPosVal, ImVec2(FLT_MAX, FLT_MAX)) local old_pos = ImVec2() ImVec2_Copy(old_pos, window.Pos) window.Pos.x = ImTrunc(pos.x) window.Pos.y = ImTrunc(pos.y) local offset = window.Pos - old_pos if offset.x == 0 and offset.y == 0 then return end ImGui.MarkIniSettingsDirty(window) ImVec2_Copy(window.DC.CursorPos, window.DC.CursorPos + offset) ImVec2_Copy(window.DC.CursorMaxPos, window.DC.CursorMaxPos + offset) ImVec2_Copy(window.DC.IdealMaxPos, window.DC.IdealMaxPos + offset) ImVec2_Copy(window.DC.CursorStartPos, window.DC.CursorStartPos + offset) end --- @param window ImGuiWindow --- @param size ImVec2 --- @param cond? ImGuiCond function ImGui.SetWindowSize(window, size, cond) -- Overloads: SetWindowSize(size, cond) / SetWindowSize(name, size, cond) / SetWindowSize(window, size, cond) if type(window) == "string" then window = ImGui.FindWindowByName(window) if window == nil then return end elseif window.SetWindowSizeAllowFlags == nil then window, size, cond = GImGui.CurrentWindow, window, size end if cond == nil then cond = 0 end if ((cond ~= 0) and bit32.band(window.SetWindowSizeAllowFlags, cond) == 0) then return end IM_ASSERT(cond == 0 or ImIsPowerOfTwo(cond)) window.SetWindowSizeAllowFlags = bit32.band(window.SetWindowSizeAllowFlags, bit32.bnot(bit32.bor(ImGuiCond.Once, ImGuiCond.FirstUseEver, ImGuiCond.Appearing))) if bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) == 0 or window.Appearing or bit32.band(window.ChildFlags, ImGuiChildFlags.AlwaysAutoResize) ~= 0 then window.AutoFitFramesX = (size.x <= 0.0) and 2 or 0 end if bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) == 0 or window.Appearing or bit32.band(window.ChildFlags, ImGuiChildFlags.AlwaysAutoResize) ~= 0 then window.AutoFitFramesY = (size.y <= 0.0) and 2 or 0 end local old_size_x, old_size_y = window.SizeFull.x, window.SizeFull.y if size.x <= 0.0 then window.AutoFitOnlyGrows = false else window.SizeFull.x = IM_TRUNC(size.x) end if size.y <= 0.0 then window.AutoFitOnlyGrows = false else window.SizeFull.y = IM_TRUNC(size.y) end if old_size_x ~= window.SizeFull.x or old_size_y ~= window.SizeFull.y then ImGui.MarkIniSettingsDirty(window) end end --- Overloads: SetWindowCollapsed(collapsed, cond) / SetWindowCollapsed(name, collapsed, cond) / SetWindowCollapsed(window, collapsed, cond) function ImGui.SetWindowCollapsed(window, collapsed, cond) if type(window) == "string" then window = ImGui.FindWindowByName(window) if window == nil then return end elseif type(window) == "boolean" then window, collapsed, cond = GImGui.CurrentWindow, window, collapsed end if cond == nil then cond = 0 end if cond ~= 0 and bit32.band(window.SetWindowCollapsedAllowFlags, cond) == 0 then return end window.SetWindowCollapsedAllowFlags = bit32.band(window.SetWindowCollapsedAllowFlags, bit32.bnot(bit32.bor(ImGuiCond.Once, ImGuiCond.FirstUseEver, ImGuiCond.Appearing))) -- Queue applying in Begin() if window.WantCollapseToggle then window.Collapsed = not window.Collapsed end window.WantCollapseToggle = (window.Collapsed ~= collapsed) end --- @param window ImGuiWindow --- @param pos ImVec2 --- @param size ImVec2 function ImGui.SetWindowHitTestHole(window, pos, size) IM_ASSERT(window.HitTestHoleSize.x == 0) -- We don't support multiple holes/hit test filters window.HitTestHoleSize = ImVec2(math.floor(size.x), math.floor(size.y)) window.HitTestHoleOffset = ImVec2(math.floor(pos.x - window.Pos.x), math.floor(pos.y - window.Pos.y)) end function ImGui.IsWindowCollapsed() return ImGui.GetCurrentWindowRead().Collapsed end function ImGui.IsWindowAppearing() return ImGui.GetCurrentWindowRead().Appearing end function ImGui.GetWindowHeight() return GImGui.CurrentWindow.Size.y end --- @return ImVec2 # Ref, do not modify function ImGui.GetWindowPos() return GImGui.CurrentWindow.Pos end --- @return ImVec2 # Ref, do not modify function ImGui.GetWindowSize() return ImGui.GetCurrentWindowRead().Size end -- Overloads: SetWindowFocus() / SetWindowFocus(name). Passing nil name removes focus. --- @param name? string function ImGui.SetWindowFocus(...) if select("#", ...) == 0 then ImGui.FocusWindow(GImGui.CurrentWindow) return end local name = ... if name then local window = ImGui.FindWindowByName(name) if window then ImGui.FocusWindow(window) end else ImGui.FocusWindow(nil) end end --- @param window ImGuiWindow function ImGui.SetWindowHiddenAndSkipItemsForCurrentFrame(window) window.Hidden = true window.SkipItems = true window.HiddenFramesCanSkipItems = 1 end --- @param pos ImVec2 --- @param cond? ImGuiCond --- @param pivot? ImVec2 function ImGui.SetNextWindowPos(pos, cond, pivot) if cond == nil then cond = 0 end if pivot == nil then pivot = ImVec2(0, 0) end local g = GImGui IM_ASSERT(cond == 0 or ImIsPowerOfTwo(cond)) g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasPos) ImVec2_Copy(g.NextWindowData.PosVal, pos) ImVec2_Copy(g.NextWindowData.PosPivotVal, pivot) g.NextWindowData.PosCond = (cond ~= 0) and cond or ImGuiCond.Always g.NextWindowData.PosUndock = true end --- @param size ImVec2 --- @param cond? ImGuiCond function ImGui.SetNextWindowSize(size, cond) if cond == nil then cond = 0 end local g = GImGui IM_ASSERT(cond == 0 or ImIsPowerOfTwo(cond)) g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasSize) ImVec2_Copy(g.NextWindowData.SizeVal, size) g.NextWindowData.SizeCond = (cond ~= 0) and cond or ImGuiCond.Always end -- For each axis: -- - Use 0.0f as min or FLT_MAX as max if you don't want limits, e.g. size_min = (500.0f, 0.0f), size_max = (FLT_MAX, FLT_MAX) sets a minimum width. -- - Use -1 for both min and max of same axis to preserve current size which itself is a constraint. -- - See "Demo->Examples->Constrained-resizing window" for examples. --- @param size_min ImVec2 --- @param size_max ImVec2 --- @param custom_callback? function --- @param custom_callback_user_data? any function ImGui.SetNextWindowSizeConstraints(size_min, size_max, custom_callback, custom_callback_user_data) local g = GImGui g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasSizeConstraint) g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max) g.NextWindowData.SizeCallback = custom_callback g.NextWindowData.SizeCallbackUserData = custom_callback_user_data end --- @param alpha float function ImGui.SetNextWindowBgAlpha(alpha) local g = GImGui g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasBgAlpha) g.NextWindowData.BgAlphaVal = alpha end function ImGui.GetWindowDrawList() local window = ImGui.GetCurrentWindow() return window.DrawList end function ImGui.GetFont() return GImGui.Font end function ImGui.GetFontSize() return GImGui.FontSize end --- @return ImVec2 function ImGui.GetFontTexUvWhitePixel() return GImGui.DrawListSharedData.TexUvWhitePixel end -- This is a shortcut for not taking ownership of 100+ keys, frequently used by drag operations. -- FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more fine-tuned version if needed? function ImGui.SetActiveIdUsingAllKeyboardKeys() local g = GImGui IM_ASSERT(g.ActiveId ~= 0) g.ActiveIdUsingNavDirMask = (bit32.lshift(1, ImGuiDir.COUNT) - 1) g.ActiveIdUsingAllKeyboardKeys = true ImGui.NavMoveRequestCancel() end --- @return ImVec2 # Ref function ImGui.GetItemRectMin() local g = GImGui return g.LastItemData.Rect.Min end --- @return ImVec2 # Ref function ImGui.GetItemRectMax() local g = GImGui return g.LastItemData.Rect.Max end --- @param name string --- @param id ImGuiID --- @param size_arg ImVec2 --- @param child_flags ImGuiChildFlags --- @param window_flags ImGuiWindowFlags function ImGui.BeginChildEx(name, id, size_arg, child_flags, window_flags) local g = GImGui local parent_window = g.CurrentWindow IM_ASSERT(id ~= 0) if child_flags == nil then child_flags = 0 end if window_flags == nil then window_flags = 0 end if size_arg == nil then size_arg = ImVec2(0, 0) end if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasChildFlags) ~= 0 then child_flags = bit32.band(bit32.bor(child_flags, g.NextWindowData.ChildFlagsSet), bit32.bnot(g.NextWindowData.ChildFlagsClear)) end -- Sanity check as it is likely that some user will accidentally pass ImGuiWindowFlags into the ImGuiChildFlags argument local ImGuiChildFlags_SupportedMask_ = bit32.bor(ImGuiChildFlags.Borders, ImGuiChildFlags.AlwaysUseWindowPadding, ImGuiChildFlags.ResizeX, ImGuiChildFlags.ResizeY, ImGuiChildFlags.AutoResizeX, ImGuiChildFlags.AutoResizeY, ImGuiChildFlags.AlwaysAutoResize, ImGuiChildFlags.FrameStyle, ImGuiChildFlags.NavFlattened) -- IM_UNUSED(ImGuiChildFlags_SupportedMask_) IM_ASSERT(bit32.band(child_flags, bit32.bnot(ImGuiChildFlags_SupportedMask_)) == 0, "Illegal ImGuiChildFlags value. Did you pass ImGuiWindowFlags values instead of ImGuiChildFlags?") IM_ASSERT(bit32.band(window_flags, ImGuiWindowFlags.AlwaysAutoResize) == 0, "Cannot specify ImGuiWindowFlags.AlwaysAutoResize for BeginChild(). Use ImGuiChildFlags.AlwaysAutoResize!") if bit32.band(child_flags, ImGuiChildFlags.AlwaysAutoResize) ~= 0 then IM_ASSERT(bit32.band(child_flags, bit32.bor(ImGuiChildFlags.ResizeX, ImGuiChildFlags.ResizeY)) == 0, "Cannot use ImGuiChildFlags.ResizeX or ImGuiChildFlags.ResizeY with ImGuiChildFlags.AlwaysAutoResize!") IM_ASSERT(bit32.band(child_flags, bit32.bor(ImGuiChildFlags.AutoResizeX, ImGuiChildFlags.AutoResizeY)) ~= 0, "Must use ImGuiChildFlags.AutoResizeX or ImGuiChildFlags.AutoResizeY with ImGuiChildFlags.AlwaysAutoResize!") end if bit32.band(child_flags, ImGuiChildFlags.AutoResizeX) ~= 0 then child_flags = bit32.band(child_flags, bit32.bnot(ImGuiChildFlags.ResizeX)) end if bit32.band(child_flags, ImGuiChildFlags.AutoResizeY) ~= 0 then child_flags = bit32.band(child_flags, bit32.bnot(ImGuiChildFlags.ResizeY)) end -- Set window flags window_flags = bit32.bor(window_flags, ImGuiWindowFlags.ChildWindow, ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoDocking) window_flags = bit32.bor(window_flags, bit32.band(parent_window.Flags, ImGuiWindowFlags.NoMove)) -- Inherit the NoMove flag if bit32.band(child_flags, bit32.bor(ImGuiChildFlags.AutoResizeX, ImGuiChildFlags.AutoResizeY, ImGuiChildFlags.AlwaysAutoResize)) ~= 0 then window_flags = bit32.bor(window_flags, ImGuiWindowFlags.AlwaysAutoResize) end if bit32.band(child_flags, bit32.bor(ImGuiChildFlags.ResizeX, ImGuiChildFlags.ResizeY)) == 0 then window_flags = bit32.bor(window_flags, ImGuiWindowFlags.NoResize, ImGuiWindowFlags.NoSavedSettings) end -- Special framed style if bit32.band(child_flags, ImGuiChildFlags.FrameStyle) ~= 0 then ImGui.PushStyleColor(ImGuiCol.ChildBg, g.Style.Colors[ImGuiCol.FrameBg]) ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, g.Style.FrameRounding) ImGui.PushStyleVar(ImGuiStyleVar.ChildBorderSize, g.Style.FrameBorderSize) ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, g.Style.FramePadding) child_flags = bit32.bor(child_flags, ImGuiChildFlags.Borders, ImGuiChildFlags.AlwaysUseWindowPadding) window_flags = bit32.bor(window_flags, ImGuiWindowFlags.NoMove) end -- Forward size -- Important: Begin() has special processing to switch condition to ImGuiCond_FirstUseEver for a given axis when ImGuiChildFlags.ResizeXXX is set. -- (the alternative would to store conditional flags per axis, which is possible but more code) local size_avail = ImGui.GetContentRegionAvail() local size_default = ImVec2((bit32.band(child_flags, ImGuiChildFlags.AutoResizeX) ~= 0) and 0.0 or size_avail.x, (bit32.band(child_flags, ImGuiChildFlags.AutoResizeY) ~= 0) and 0.0 or size_avail.y) local size = ImGui.CalcItemSize(size_arg, size_default.x, size_default.y) -- A SetNextWindowSize() call always has priority (#8020) -- (since the code in Begin() never supported SizeVal==0.0f aka auto-resize via SetNextWindowSize() call, we don't support it here for now) -- FIXME: We only support ImGuiCond_Always in this path. Supporting other paths would requires to obtain window pointer. if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasSize) ~= 0 and bit32.band(g.NextWindowData.SizeCond, ImGuiCond.Always) ~= 0 then if g.NextWindowData.SizeVal.x > 0.0 then size.x = g.NextWindowData.SizeVal.x child_flags = bit32.band(child_flags, bit32.bnot(ImGuiChildFlags.ResizeX)) end if g.NextWindowData.SizeVal.y > 0.0 then size.y = g.NextWindowData.SizeVal.y child_flags = bit32.band(child_flags, bit32.bnot(ImGuiChildFlags.ResizeY)) end end ImGui.SetNextWindowSize(size) ImGui.SetNextWindowChildFlags(child_flags, true) -- Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. -- FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names, which the trailing hash helped workaround. -- e.g. "ParentName###ParentIdentifier/ChildName###ChildIdentifier" would get hashed incorrectly by ImHashStr(), trailing _%08X somehow fixes it. local temp_window_name if name then temp_window_name = ImFormatString("%s/%s_%08X", parent_window.Name, name, id) else temp_window_name = ImFormatString("%s/%08X", parent_window.Name, id) end -- Set style local backup_border_size = g.Style.ChildBorderSize if bit32.band(child_flags, ImGuiChildFlags.Borders) == 0 then g.Style.ChildBorderSize = 0.0 end -- Begin into window local _, ret = ImGui.Begin(temp_window_name, nil, window_flags) -- Restore style g.Style.ChildBorderSize = backup_border_size if bit32.band(child_flags, ImGuiChildFlags.FrameStyle) ~= 0 then ImGui.PopStyleVar(3) ImGui.PopStyleColor() end local child_window = g.CurrentWindow child_window.ChildId = id -- Set the cursor to handle case where the user called SetNextWindowPos()+BeginChild() manually. -- While this is not really documented/defined, it seems that the expected thing to do. if child_window.BeginCount == 1 then ImVec2_Copy(parent_window.DC.CursorPos, child_window.Pos) end -- Process navigation-in immediately so NavInit can run on first frame -- Can enter a child if (A) it has navigable items or (B) it can be scrolled. local temp_id_for_activation = ImHashStr("##Child", nil, id) if g.ActiveId == temp_id_for_activation then ImGui.ClearActiveID() end if g.NavActivateId == id and bit32.band(child_flags, ImGuiChildFlags.NavFlattened) == 0 and (child_window.DC.NavLayersActiveMask ~= 0 or child_window.DC.NavWindowHasScrollY) then ImGui.FocusWindow(child_window) ImGui.NavInitWindow(child_window, false) ImGui.SetActiveID(temp_id_for_activation, child_window) -- Steal ActiveId with another arbitrary id so that key-press won't activate child item g.ActiveIdSource = g.NavInputSource end return ret end -- Prior to v1.90 2023/10/16, the BeginChild() function took a 'bool border = false' parameter instead of 'ImGuiChildFlags child_flags = 0'. -- `true` is accepted as ImGuiChildFlags.Borders for legacy code. --- @param str_id_or_id string|ImGuiID --- @param size_arg? ImVec2 --- @param child_flags? ImGuiChildFlags|bool --- @param window_flags? ImGuiWindowFlags --- @return bool # visible (always call EndChild()!) function ImGui.BeginChild(str_id_or_id, size_arg, child_flags, window_flags) if child_flags == true then child_flags = ImGuiChildFlags.Borders elseif not child_flags then child_flags = 0 end if type(str_id_or_id) == "number" then return ImGui.BeginChildEx(nil, str_id_or_id, size_arg, child_flags, window_flags) end local id = ImGui.GetCurrentWindow():GetID(str_id_or_id) return ImGui.BeginChildEx(str_id_or_id, id, size_arg, child_flags, window_flags) end --- @param window ImGuiWindow --- @param rect ImRect function ImGui.SetLastItemDataForChildWindowItem(window, rect) local g = GImGui ImGui.SetLastItemData(window.ChildId, g.CurrentItemFlags, window.DC.ChildItemStatusFlags or 0, rect) end function ImGui.EndChild() local g = GImGui local child_window = g.CurrentWindow local backup_within_end_child_id = g.WithinEndChildID IM_ASSERT(bit32.band(child_window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0) -- Mismatched BeginChild()/EndChild() calls g.WithinEndChildID = child_window.ID local child_size = ImVec2(child_window.Size.x, child_window.Size.y) ImGui.End() if child_window.BeginCount == 1 then local parent_window = g.CurrentWindow local bb = ImRect(parent_window.DC.CursorPos, parent_window.DC.CursorPos + child_size) ImGui.ItemSize(child_size) local nav_flattened = bit32.band(child_window.ChildFlags, ImGuiChildFlags.NavFlattened) ~= 0 if (child_window.DC.NavLayersActiveMask ~= 0 or child_window.DC.NavWindowHasScrollY) and not nav_flattened then ImGui.ItemAdd(bb, child_window.ChildId) ImGui.RenderNavCursor(bb, child_window.ChildId) -- When browsing a window that has no activable items (scroll only) we keep a highlight on the child (pass g.NavId to trick into always displaying) if child_window.DC.NavLayersActiveMask == 0 and child_window == g.NavWindow then ImGui.RenderNavCursor(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavRenderCursorFlags.Compact) end else -- Not navigable into ImGui.ItemAdd(bb, child_window.ChildId, nil, ImGuiItemFlags.NoNav) -- But when flattened we directly reach items, adjust active layer mask accordingly if nav_flattened then parent_window.DC.NavLayersActiveMaskNext = bit32.bor(parent_window.DC.NavLayersActiveMaskNext, child_window.DC.NavLayersActiveMaskNext) end end if g.HoveredWindow == child_window then g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredWindow) end child_window.DC.ChildItemStatusFlags = g.LastItemData.StatusFlags else ImGui.SetLastItemDataForChildWindowItem(child_window, child_window:Rect()) end g.WithinEndChildID = backup_within_end_child_id g.LogLinePosY = -FLT_MAX -- To enforce a carriage return end --- @param flags ImGuiChildFlags --- @param enabled bool function ImGui.SetNextWindowChildFlags(flags, enabled) local g = GImGui local nwd = g.NextWindowData if bit32.band(nwd.HasFlags, ImGuiNextWindowDataFlags.HasChildFlags) == 0 then nwd.ChildFlagsSet = 0; nwd.ChildFlagsClear = 0 end if enabled then nwd.ChildFlagsSet = bit32.bor(nwd.ChildFlagsSet, flags) nwd.ChildFlagsClear = bit32.band(nwd.ChildFlagsClear, bit32.bnot(flags)) else nwd.ChildFlagsSet = bit32.band(nwd.ChildFlagsSet, bit32.bnot(flags)) nwd.ChildFlagsClear = bit32.bor(nwd.ChildFlagsClear, flags) end nwd.HasFlags = bit32.bor(nwd.HasFlags, ImGuiNextWindowDataFlags.HasChildFlags) end --- @param flags ImGuiWindowFlags --- @param enabled bool function ImGui.SetNextWindowFlags(flags, enabled) local g = GImGui local nwd = g.NextWindowData if bit32.band(nwd.HasFlags, ImGuiNextWindowDataFlags.HasWindowFlags) == 0 then nwd.WindowFlagsSet = 0; nwd.WindowFlagsClear = 0 end if enabled then nwd.WindowFlagsSet = bit32.bor(nwd.WindowFlagsSet, flags) nwd.WindowFlagsClear = bit32.band(nwd.WindowFlagsClear, bit32.bnot(flags)) else nwd.WindowFlagsSet = bit32.band(nwd.WindowFlagsSet, bit32.bnot(flags)) nwd.WindowFlagsClear = bit32.bor(nwd.WindowFlagsClear, flags) end nwd.HasFlags = bit32.bor(nwd.HasFlags, ImGuiNextWindowDataFlags.HasWindowFlags) end -- Content size = inner scrollable rectangle, padded with WindowPadding. --- @param size ImVec2 function ImGui.SetNextWindowContentSize(size) local g = GImGui g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasContentSize) ImVec2_CopyV(g.NextWindowData.ContentSizeVal, ImTrunc(size.x), ImTrunc(size.y)) end --- @param scroll ImVec2 function ImGui.SetNextWindowScroll(scroll) local g = GImGui g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasScroll) ImVec2_Copy(g.NextWindowData.ScrollVal, scroll) end --- @param collapsed bool --- @param cond? ImGuiCond function ImGui.SetNextWindowCollapsed(collapsed, cond) if cond == nil then cond = 0 end local g = GImGui IM_ASSERT(cond == 0 or ImIsPowerOfTwo(cond)) g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasCollapsed) g.NextWindowData.CollapsedVal = collapsed g.NextWindowData.CollapsedCond = (cond ~= 0) and cond or ImGuiCond.Always end function ImGui.SetNextWindowFocus() local g = GImGui g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasFocus) end --- @param id ImGuiID function ImGui.SetNextWindowViewport(id) local g = GImGui g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasViewport) g.NextWindowData.ViewportId = id end --- @param window_class ImGuiWindowClass function ImGui.SetNextWindowClass(window_class) local g = GImGui g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasWindowClass) local wc = ImGuiWindowClass() for k, v in pairs(window_class) do wc[k] = v end -- C++ copies the struct g.NextWindowData.WindowClass = wc end -- This is experimental and meant to be a toy for exploring a future/wider range of features. --- @param flags ImGuiWindowRefreshFlags function ImGui.SetNextWindowRefreshPolicy(flags) local g = GImGui g.NextWindowData.HasFlags = bit32.bor(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasRefreshPolicy) g.NextWindowData.RefreshFlagsVal = flags end function ImGui.GetWindowDpiScale() return 1.0 -- Roblox: single viewport, no DPI scaling end function ImGui.GetWindowViewport() local g = GImGui return g.CurrentViewport or ImGui.GetMainViewport() end -- Prefer using PushFont(nil, style.FontSizeBase * factor), or use style.FontScaleMain to scale all windows. --- @param scale float function ImGui.SetWindowFontScale(scale) IM_ASSERT(scale > 0.0) local window = ImGui.GetCurrentWindow() window.FontWindowScale = scale ImGui.UpdateCurrentFontSize(0.0) end --- @param window ImGuiWindow function ImGui.StartMouseMovingWindow(window) local g = GImGui ImGui.FocusWindow(window) ImGui.SetActiveID(window.MoveId, window) if (g.IO.ConfigNavCursorVisibleAuto) then g.NavCursorVisible = false end g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window.RootWindowDockTree.Pos g.ActiveIdNoClearOnFocusLoss = true ImGui.SetActiveIdUsingAllKeyboardKeys() local can_move_window = true if bit32.band(window.Flags, ImGuiWindowFlags.NoMove) ~= 0 or bit32.band(window.RootWindowDockTree.Flags, ImGuiWindowFlags.NoMove) ~= 0 then can_move_window = false end local node = window.DockNodeAsHost if node and node.VisibleWindow and bit32.band(node.VisibleWindow.Flags, ImGuiWindowFlags.NoMove) ~= 0 then can_move_window = false end if can_move_window then g.MovingWindow = window end end function ImGui.UpdateMouseMovingWindowNewFrame() local g = GImGui if g.MovingWindow then ImGui.KeepAliveID(g.ActiveId) IM_ASSERT(g.MovingWindow and g.MovingWindow.RootWindowDockTree) local moving_window = g.MovingWindow.RootWindowDockTree local window_disappeared = (not moving_window.WasActive and not moving_window.Active) if g.IO.MouseDown[0] and ImGui.IsMousePosValid(g.IO.MousePos) and not window_disappeared then local pos = g.IO.MousePos - g.ActiveIdClickOffset if moving_window.Pos.x ~= pos.x or moving_window.Pos.y ~= pos.y then ImGui.SetWindowPos(moving_window, pos, ImGuiCond.Always) if moving_window.Viewport and moving_window.ViewportOwned then -- Synchronize viewport immediately because some overlays may relies on clipping rectangle before we Begin() into the window. ImVec2_Copy(moving_window.Viewport.Pos, pos) moving_window.Viewport:UpdateWorkRect() end end ImGui.FocusWindow(g.MovingWindow) else ImGui.StopMouseMovingWindow() ImGui.ClearActiveID() end else if (g.ActiveIdWindow and g.ActiveIdWindow.MoveId == g.ActiveId) then ImGui.KeepAliveID(g.ActiveId) if not g.IO.MouseDown[0] then ImGui.ClearActiveID() end end end end function ImGui.GetDrawListSharedData() return GImGui.DrawListSharedData end --- @param id ImGuiID --- @param popup_flags ImGuiPopupFlags function ImGui.IsPopupOpen(id, popup_flags) local g = GImGui if popup_flags == nil then popup_flags = 0 end if type(id) == "string" then -- str_id overload: with AnyPopupId the id must be 0, otherwise it is hashed with the current window's ID stack id = (bit32.band(popup_flags, ImGuiPopupFlags.AnyPopupId) ~= 0) and 0 or g.CurrentWindow:GetID(id) end if bit32.band(popup_flags, ImGuiPopupFlags.AnyPopupId) ~= 0 then -- Return true if any popup is open at the current BeginPopup() level of the popup stack -- This may be used to e.g. test for another popups already opened to handle popups priorities at the same level. IM_ASSERT(id == 0) if bit32.band(popup_flags, ImGuiPopupFlags.AnyPopupLevel) ~= 0 then return g.OpenPopupStack.Size > 0 else return g.OpenPopupStack.Size > g.BeginPopupStack.Size end else if bit32.band(popup_flags, ImGuiPopupFlags.AnyPopupLevel) ~= 0 then -- Return true if the popup is open anywhere in the popup stack for n = 1, g.OpenPopupStack.Size do if g.OpenPopupStack.Data[n].PopupId == id then return true end end return false else -- Return true if the popup is open at the current BeginPopup() level of the popup stack (this is the most-common query) return g.OpenPopupStack.Size > g.BeginPopupStack.Size and g.OpenPopupStack.Data[g.BeginPopupStack.Size + 1].PopupId == id end end end --- @return ImGuiWindow? function ImGui.GetTopMostPopupModal() local g = GImGui for n = g.OpenPopupStack.Size, 1, -1 do local popup = g.OpenPopupStack.Data[n].Window if popup then if bit32.band(popup.Flags, ImGuiWindowFlags.Modal) ~= 0 then return popup end end end return nil end function ImGui.UpdateMouseMovingWindowEndFrame() local g = GImGui if g.ActiveId ~= 0 or (g.HoveredId ~= 0 and not g.HoveredIdIsDisabled) then return end if g.NavWindow and g.NavWindow.Appearing then return end local hovered_window = g.HoveredWindow if ImGui.IsMouseClickedEx(0, ImGuiInputFlags.None, ImGuiKeyOwner_NoOwner) then local hovered_root if hovered_window then hovered_root = hovered_window.RootWindow else hovered_root = nil end local is_closed_popup = hovered_root and (bit32.band(hovered_root.Flags, ImGuiWindowFlags.Popup) ~= 0) and not ImGui.IsPopupOpen(hovered_root.PopupId, ImGuiPopupFlags.AnyPopupLevel) local is_queued_focus_request = g.NavMoveSubmitted and (bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.FocusApi) ~= 0) if hovered_window ~= nil and not is_closed_popup and not is_queued_focus_request then ImGui.StartMouseMovingWindow(hovered_window) -- Cancel moving if clicked outside of title bar if bit32.band(hovered_window.BgClickFlags, ImGuiWindowBgClickFlags.Move) == 0 then -- set by io.ConfigWindowsMoveFromTitleBarOnly if bit32.band(hovered_root.Flags, ImGuiWindowFlags.NoTitleBar) == 0 then if not hovered_root:TitleBarRect():Contains(g.IO.MouseClickedPos[0]) then g.MovingWindow = nil end end end -- Cancel moving if clicked over an item which was disabled or inhibited by popups -- (when g.HoveredIdIsDisabled == true && g.HoveredId == 0 we are inhibited by popups, when g.HoveredIdIsDisabled == true && g.HoveredId != 0 we are over a disabled item) if g.HoveredIdIsDisabled then g.MovingWindow = nil g.ActiveIdDisabledId = g.HoveredId end elseif hovered_window == nil and g.NavWindow ~= nil then ImGui.FocusWindow(nil, ImGuiFocusRequestFlags.UnlessBelowModal) end end -- With right mouse button we close popups without changing focus based on where the mouse is aimed -- Instead, focus will be restored to the window under the bottom-most closed popup. -- (The left mouse button path calls FocusWindow on the hovered window, which will lead NewFrame->ClosePopupsOverWindow to trigger) if g.HoveredId == 0 and ImGui.IsMouseClickedEx(1, ImGuiInputFlags.None, ImGuiKeyOwner_NoOwner) then -- Find the top-most window between HoveredWindow and the top-most Modal Window. -- This is where we can trim the popup stack. local modal = ImGui.GetTopMostPopupModal() local hovered_window_above_modal = hovered_window and (modal == nil or ImGui.IsWindowAbove(hovered_window, modal)) ImGui.ClosePopupsOverWindow(hovered_window_above_modal and hovered_window or modal, true) end end --- @param window ImGuiWindow --- @param delta ImVec2 function ImGui.TranslateWindow(window, delta) ImVec2_Copy(window.Pos, window.Pos + delta) window.ClipRect:Translate(delta) window.OuterRectClipped:Translate(delta) window.InnerRect:Translate(delta) ImVec2_Copy(window.DC.CursorPos, window.DC.CursorPos + delta) ImVec2_Copy(window.DC.CursorStartPos, window.DC.CursorStartPos + delta) ImVec2_Copy(window.DC.CursorMaxPos, window.DC.CursorMaxPos + delta) ImVec2_Copy(window.DC.IdealMaxPos, window.DC.IdealMaxPos + delta) end --- ImGui::FindWindowByID function ImGui.FindWindowByID(id) local g = GImGui if not g then return end return g.WindowsById[id] end --- @param name string --- @return ImGuiWindow? function ImGui.FindWindowByName(name) local id = ImHashStr(name) return ImGui.FindWindowByID(id) end --- Push a new Dear ImGui window to add widgets to. --- - Passing a non-nil `open` displays a close button on the upper-right corner of the window --- @param name string --- @param open? bool --- @param flags? ImGuiWindowFlags --- @return bool is_open # The updated `open` passed in --- @return bool no_skip_items # You always need to call `ImGui.End()` even if false is returned function ImGui.Begin(name, open, flags) if flags == nil then flags = 0 end local g = GImGui local style = g.Style IM_ASSERT(name ~= nil and name ~= "") IM_ASSERT(g.WithinFrameScope) IM_ASSERT(g.FrameCountEnded ~= g.FrameCount) local window = ImGui.FindWindowByName(name) local window_just_created = (window == nil) if window_just_created then window = CreateNewWindow(name, flags) --- @cast window ImGuiWindow end if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasWindowFlags) ~= 0 then flags = bit32.band(bit32.bor(flags, g.NextWindowData.WindowFlagsSet), bit32.bnot(g.NextWindowData.WindowFlagsClear)) end -- Automatically disable manual moving/resizing when NoInputs is set if bit32.band(flags, ImGuiWindowFlags.NoInputs) == ImGuiWindowFlags.NoInputs then flags = bit32.bor(flags, ImGuiWindowFlags.NoMove, ImGuiWindowFlags.NoResize) end local current_frame = g.FrameCount local first_begin_of_the_frame = (window.LastFrameActive ~= current_frame) window.IsFallbackWindow = (g.CurrentWindowStack.Size == 0 and g.WithinFrameScopeWithImplicitWindow) local window_just_activated_by_user = (window.LastFrameActive < (current_frame - 1)) if bit32.band(flags, ImGuiWindowFlags.Popup) ~= 0 then local popup_ref = g.OpenPopupStack.Data[g.BeginPopupStack.Size + 1] window_just_activated_by_user = window_just_activated_by_user or (window.PopupId ~= popup_ref.PopupId) -- We recycle popups so treat window as activated if popup id changed window_just_activated_by_user = window_just_activated_by_user or (window ~= popup_ref.Window) end -- Update Flags, LastFrameActive, BeginOrderXXX fields local window_was_appearing = window.Appearing if first_begin_of_the_frame then ImGui.UpdateWindowInFocusOrderList(window, window_just_created, flags) window.Appearing = window_just_activated_by_user if (window.Appearing) then SetWindowConditionAllowFlags(window, ImGuiCond.Appearing, true) end window.FlagsPreviousFrame = window.Flags window.Flags = flags window.ChildFlags = (bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasChildFlags) ~= 0) and g.NextWindowData.ChildFlagsSet or 0 window.LastFrameActive = current_frame window.LastTimeActive = g.Time window.BeginOrderWithinParent = 0 window.BeginOrderWithinContext = g.WindowsActiveCount g.WindowsActiveCount = g.WindowsActiveCount + 1 else flags = window.Flags end -- Docking -- (NB: during the frame dock nodes are created, it is possible that (window->DockIsActive == false) even though (window->DockNode->Windows.Size > 1) IM_ASSERT(window.DockNode == nil or window.DockNodeAsHost == nil) -- Cannot be both if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasDock) ~= 0 then ImGui.SetWindowDock(window, g.NextWindowData.DockId, g.NextWindowData.DockCond) end if first_begin_of_the_frame then local has_dock_node = (window.DockId ~= 0 or window.DockNode ~= nil) local new_auto_dock_node = not has_dock_node and ImGui.GetWindowAlwaysWantOwnTabBar(window) local dock_node_was_visible = window.DockNodeIsVisible local dock_tab_was_visible = window.DockTabIsVisible window.DockIsActive = false window.DockNodeIsVisible = false window.DockTabIsVisible = false if has_dock_node or new_auto_dock_node then open = ImGui.BeginDocked(window, open) flags = window.Flags if window.DockIsActive then IM_ASSERT(window.DockNode ~= nil) g.NextWindowData.HasFlags = bit32.band(g.NextWindowData.HasFlags, bit32.bnot(ImGuiNextWindowDataFlags.HasSizeConstraint)) end -- Amend the Appearing flag if window.DockTabIsVisible and not dock_tab_was_visible and dock_node_was_visible and not window.Appearing and not window_was_appearing then window.Appearing = true SetWindowConditionAllowFlags(window, ImGuiCond.Appearing, true) end end end local parent_window_in_stack if window.DockIsActive and window.DockNode.HostWindow then parent_window_in_stack = window.DockNode.HostWindow elseif g.CurrentWindowStack:empty() then parent_window_in_stack = nil else parent_window_in_stack = g.CurrentWindowStack:back().Window end local parent_window if first_begin_of_the_frame then if (bit32.band(flags, bit32.bor(ImGuiWindowFlags.ChildWindow, ImGuiWindowFlags.Popup, ImGuiWindowFlags.Tooltip)) ~= 0) then parent_window = parent_window_in_stack else parent_window = nil end else parent_window = window.ParentWindow end if window.IDStack.Size == 0 then window.IDStack:push_back(window.ID) end -- Add to stack g.CurrentWindow = window g.CurrentWindowStack:resize(g.CurrentWindowStack.Size + 1) g.CurrentWindowStack.Data[g.CurrentWindowStack.Size] = ImGuiWindowStackData() local window_stack_data = g.CurrentWindowStack.Data[g.CurrentWindowStack.Size] window_stack_data.Window = window ImGuiLastItemData_Copy(window_stack_data.ParentLastItemDataBackup, g.LastItemData) window_stack_data.DisabledOverrideReenable = (bit32.band(flags, ImGuiWindowFlags.Tooltip) ~= 0) and (bit32.band(g.CurrentItemFlags, ImGuiItemFlags.Disabled) ~= 0) window_stack_data.DisabledOverrideReenableAlphaBackup = 0.0 window_stack_data.StackSizesInBegin = ImGuiErrorRecoveryState() ImGui.ErrorRecoveryStoreState(window_stack_data.StackSizesInBegin) g.StackSizesInBeginForCurrentWindow = window_stack_data.StackSizesInBegin if bit32.band(flags, ImGuiWindowFlags.ChildMenu) ~= 0 then g.BeginMenuDepth = g.BeginMenuDepth + 1 end if first_begin_of_the_frame then ImGui.UpdateWindowParentAndRootLinks(window, flags, parent_window) window.ParentWindowInBeginStack = parent_window_in_stack if window.RootWindow ~= window then window.ParentWindowForFocusRoute = parent_window_in_stack else window.ParentWindowForFocusRoute = nil end if window.ParentWindowForFocusRoute == nil and window.DockNode ~= nil then if bit32.band(window.DockNode.MergedFlags, ImGuiDockNodeFlags.DockedWindowsInFocusRoute) ~= 0 then window.ParentWindowForFocusRoute = window.DockNode.HostWindow end end if window.WindowClass and window.WindowClass.FocusRouteParentWindowId ~= nil and window.WindowClass.FocusRouteParentWindowId ~= 0 then window.ParentWindowForFocusRoute = ImGui.FindWindowByID(window.WindowClass.FocusRouteParentWindowId) end if parent_window then window.FontWindowScaleParents = parent_window.FontWindowScaleParents * parent_window.FontWindowScale else window.FontWindowScaleParents = 1.0 end end ImGui.PushFocusScope((bit32.band(window.ChildFlags, ImGuiChildFlags.NavFlattened) ~= 0) and g.CurrentFocusScopeId or window.ID) window.NavRootFocusScopeId = g.CurrentFocusScopeId -- Add to popup stacks: update OpenPopupStack data, push to BeginPopupStack if bit32.band(flags, ImGuiWindowFlags.Popup) ~= 0 then local popup_ref = g.OpenPopupStack.Data[g.BeginPopupStack.Size + 1] popup_ref.Window = window popup_ref.ParentNavLayer = parent_window_in_stack.DC.NavLayerCurrent g.BeginPopupStack:push_back(popup_ref) window.PopupId = popup_ref.PopupId end local window_pos_set_by_api = false local window_size_x_set_by_api = false local window_size_y_set_by_api = false if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasPos) ~= 0 then window_pos_set_by_api = (bit32.band(window.SetWindowPosAllowFlags, g.NextWindowData.PosCond) ~= 0) if window_pos_set_by_api and ImLengthSqr(g.NextWindowData.PosPivotVal) > 1e-5 then -- FIXME: Look into removing the branch so everything can go through this same code path for consistency. ImVec2_Copy(window.SetWindowPosVal, g.NextWindowData.PosVal) ImVec2_Copy(window.SetWindowPosPivot, g.NextWindowData.PosPivotVal) window.SetWindowPosAllowFlags = bit32.band(window.SetWindowPosAllowFlags, bit32.bnot(bit32.bor(ImGuiCond.Once, ImGuiCond.FirstUseEver, ImGuiCond.Appearing))) else ImGui.SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond) end end if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasSize) ~= 0 then window_size_x_set_by_api = (bit32.band(window.SetWindowSizeAllowFlags, g.NextWindowData.SizeCond) ~= 0) and (g.NextWindowData.SizeVal.x > 0.0) window_size_y_set_by_api = (bit32.band(window.SetWindowSizeAllowFlags, g.NextWindowData.SizeCond) ~= 0) and (g.NextWindowData.SizeVal.y > 0.0) if (bit32.band(window.ChildFlags, ImGuiChildFlags.ResizeX) ~= 0 and bit32.band(window.SetWindowSizeAllowFlags, ImGuiCond.FirstUseEver) == 0) then g.NextWindowData.SizeVal.x = window.SizeFull.x end if (bit32.band(window.ChildFlags, ImGuiChildFlags.ResizeY) ~= 0 and bit32.band(window.SetWindowSizeAllowFlags, ImGuiCond.FirstUseEver) == 0) then g.NextWindowData.SizeVal.y = window.SizeFull.y end ImGui.SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond); end if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasScroll) ~= 0 then if g.NextWindowData.ScrollVal.x >= 0.0 then window.ScrollTarget.x = g.NextWindowData.ScrollVal.x window.ScrollTargetCenterRatio.x = 0.0 end if g.NextWindowData.ScrollVal.y >= 0.0 then window.ScrollTarget.y = g.NextWindowData.ScrollVal.y window.ScrollTargetCenterRatio.y = 0.0 end end if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasContentSize) ~= 0 then ImVec2_Copy(window.ContentSizeExplicit, g.NextWindowData.ContentSizeVal) elseif first_begin_of_the_frame then window.ContentSizeExplicit = ImVec2(0.0, 0.0) end if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasWindowClass) ~= 0 then window.WindowClass = g.NextWindowData.WindowClass end if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasCollapsed) ~= 0 then ImGui.SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond) end if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasFocus) ~= 0 then ImGui.FocusWindow(window) end if window.Appearing then SetWindowConditionAllowFlags(window, ImGuiCond.Appearing, false) end -- [EXPERIMENTAL] Skip Refresh mode ImGui.UpdateWindowSkipRefresh(window) if window_stack_data.DisabledOverrideReenable and window.RootWindow == window then ImGui.BeginDisabledOverrideReenable() end g.CurrentWindow = nil if first_begin_of_the_frame and not window.SkipRefresh then local window_is_child_tooltip = (bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0 and bit32.band(flags, ImGuiWindowFlags.Tooltip) ~= 0) local window_just_appearing_after_hidden_for_resize = (window.HiddenFramesCannotSkipItems > 0) window.Active = true window.HasCloseButton = (open ~= nil) ImRect_CopyFromV4(window.ClipRect, ImVec4(-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX)) window.IDStack:resize(1) window.DrawList:_ResetForNewFrame() window.DC.CurrentTableIdx = -1 if bit32.band(flags, ImGuiWindowFlags.DockNodeHost) ~= 0 then window.DrawList:ChannelsSplit(2) window.DrawList:ChannelsSetCurrent(1) -- DOCKING_HOST_DRAW_CHANNEL_FG: render decorations on channel 1, backgrounds rendered manually later end if window.MemoryCompacted then ImGui.GcAwakeTransientWindowBuffers(window) end -- UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS -- Update contents size from last frame for auto-fitting (or use explicit size) CalcWindowContentSizes(window, window.ContentSize, window.ContentSizeIdeal) if window.HiddenFramesCanSkipItems > 0 then window.HiddenFramesCanSkipItems = window.HiddenFramesCanSkipItems - 1 end if window.HiddenFramesCannotSkipItems > 0 then window.HiddenFramesCannotSkipItems = window.HiddenFramesCannotSkipItems - 1 end if window.HiddenFramesForRenderOnly > 0 then window.HiddenFramesForRenderOnly = window.HiddenFramesForRenderOnly - 1 end -- Hide new windows for one frame until they calculate their size if window_just_created and (not window_size_x_set_by_api or not window_size_y_set_by_api) then window.HiddenFramesCannotSkipItems = 1 end -- Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) -- We reset Size/ContentSize for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size. if window_just_activated_by_user and bit32.band(flags, bit32.bor(ImGuiWindowFlags.Popup, ImGuiWindowFlags.Tooltip)) ~= 0 then window.HiddenFramesCannotSkipItems = 1 if bit32.band(flags, ImGuiWindowFlags.AlwaysAutoResize) ~= 0 then if not window_size_x_set_by_api then window.SizeFull.x = 0.0 window.Size.x = 0.0 end if not window_size_y_set_by_api then window.SizeFull.y = 0.0 window.Size.y = 0.0 end ImVec2_Copy(window.ContentSize, ImVec2(0.0, 0.0)) ImVec2_Copy(window.ContentSizeIdeal, ImVec2(0.0, 0.0)) end end -- SELECT VIEWPORT ImGui.WindowSelectViewport(window) ImGui.SetCurrentViewport(window, window.Viewport) SetCurrentWindow(window) flags = window.Flags if not window.DockIsActive and bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0 then window.WindowBorderSize = style.ChildBorderSize else window.WindowBorderSize = (bit32.band(flags, bit32.bor(ImGuiWindowFlags.Popup, ImGuiWindowFlags.Tooltip)) ~= 0 and bit32.band(flags, ImGuiWindowFlags.Modal) == 0) and style.PopupBorderSize or style.WindowBorderSize end window.WindowPadding = style.WindowPadding if not window.DockIsActive and (bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0) and (bit32.band(flags, ImGuiWindowFlags.Popup) == 0) and (bit32.band(window.ChildFlags, ImGuiChildFlags.AlwaysUseWindowPadding) == 0) and window.WindowBorderSize == 0.0 then if bit32.band(flags, ImGuiWindowFlags.MenuBar) ~= 0 then window.WindowPadding = ImVec2(0.0, style.WindowPadding.y) else window.WindowPadding = ImVec2(0.0, 0.0) end end -- Lock menu offset so size calculation can use it as menu-bar windows need a minimum size. window.DC.MenuBarOffset.x = ImMax(ImMax(window.WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x) window.DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y if bit32.band(flags, ImGuiWindowFlags.NoTitleBar) ~= 0 then window.TitleBarHeight = 0.0 else window.TitleBarHeight = g.FontSize + g.Style.FramePadding.y * 2.0 end if bit32.band(flags, ImGuiWindowFlags.MenuBar) ~= 0 then window.MenuBarHeight = window.DC.MenuBarOffset.y + g.FontSize + g.Style.FramePadding.y * 2.0 else window.MenuBarHeight = 0.0 end window.TitleBarHeight = (bit32.band(flags, ImGuiWindowFlags.NoTitleBar) ~= 0) and 0 or g.FontSize + g.Style.FramePadding.y * 2 window.FontRefSize = g.FontSize -- Lock this to discourage calling window:CalcFontSize() outside of current window. local use_current_size_for_scrollbar_x = window_just_created local use_current_size_for_scrollbar_y = window_just_created if window_size_x_set_by_api and window.ContentSizeExplicit.x ~= 0.0 then use_current_size_for_scrollbar_x = true end if window_size_y_set_by_api and window.ContentSizeExplicit.y ~= 0.0 then use_current_size_for_scrollbar_y = true end -- Collapse window by double-clicking on title bar -- At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing if bit32.band(flags, ImGuiWindowFlags.NoTitleBar) == 0 and bit32.band(flags, ImGuiWindowFlags.NoCollapse) == 0 and not window.DockIsActive then local title_bar_rect = window:TitleBarRect() if g.HoveredWindow == window and g.HoveredId == 0 and g.HoveredIdPreviousFrame == 0 and g.ActiveId == 0 and ImGui.IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) then if g.IO.MouseClickedCount[0] == 2 and ImGui.GetKeyOwner(ImGuiKey.MouseLeft) == ImGuiKeyOwner_NoOwner then window.WantCollapseToggle = true ImGui.SetKeyOwner(ImGuiKey.MouseLeft, window.MoveId) end end if window.WantCollapseToggle then window.Collapsed = not window.Collapsed if not window.Collapsed then use_current_size_for_scrollbar_y = true end ImGui.MarkIniSettingsDirty(window) end else window.Collapsed = false end window.WantCollapseToggle = false local scrollbar_sizes_from_last_frame = ImVec2() ImVec2_Copy(scrollbar_sizes_from_last_frame, window.ScrollbarSizes) window.DecoOuterSizeX1 = 0.0 window.DecoOuterSizeX2 = 0.0 window.DecoOuterSizeY1 = window.TitleBarHeight + window.MenuBarHeight window.DecoOuterSizeY2 = 0.0 window.ScrollbarSizes = ImVec2(0.0, 0.0) -- Calculate auto-fit size, handle automatic resize -- - Using SetNextWindowSize() overrides ImGuiWindowFlags.AlwaysAutoResize, so it can be used on tooltips/popups, etc. -- - We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags.AlwaysAutoResize when collapsed. -- - Auto-fit may only grow window during the first few frames. do local size_auto_fit_x_always = not window_size_x_set_by_api and (bit32.band(flags, ImGuiWindowFlags.AlwaysAutoResize) ~= 0) and not window.Collapsed local size_auto_fit_y_always = not window_size_y_set_by_api and (bit32.band(flags, ImGuiWindowFlags.AlwaysAutoResize) ~= 0) and not window.Collapsed local size_auto_fit_x_current = not window_size_x_set_by_api and (window.AutoFitFramesX > 0) local size_auto_fit_y_current = not window_size_y_set_by_api and (window.AutoFitFramesY > 0) local size_auto_fit_mask = 0 if size_auto_fit_x_always or size_auto_fit_x_current then size_auto_fit_mask = bit32.bor(size_auto_fit_mask, bit32.lshift(1, ImGuiAxis.X - 1)) end if size_auto_fit_y_always or size_auto_fit_y_current then size_auto_fit_mask = bit32.bor(size_auto_fit_mask, bit32.lshift(1, ImGuiAxis.Y - 1)) end local size_auto_fit = CalcWindowAutoFitSize(window, window.ContentSizeIdeal, size_auto_fit_mask) local old_size = ImVec2(window.SizeFull.x, window.SizeFull.y) if size_auto_fit_x_always or size_auto_fit_x_current then if size_auto_fit_x_always then window.SizeFull.x = size_auto_fit.x else if window.AutoFitOnlyGrows then window.SizeFull.x = ImMax(window.SizeFull.x, size_auto_fit.x) else window.SizeFull.x = size_auto_fit.x end end use_current_size_for_scrollbar_x = true end if size_auto_fit_y_always or size_auto_fit_y_current then if size_auto_fit_y_always then window.SizeFull.y = size_auto_fit.y else if window.AutoFitOnlyGrows then window.SizeFull.y = ImMax(window.SizeFull.y, size_auto_fit.y) else window.SizeFull.y = size_auto_fit.y end end use_current_size_for_scrollbar_y = true end if old_size.x ~= window.SizeFull.x or old_size.y ~= window.SizeFull.y then ImGui.MarkIniSettingsDirty(window) end end ImVec2_Copy(window.SizeFull, CalcWindowSizeAfterConstraint(window, window.SizeFull)) ImVec2_Copy(window.Size, (window.Collapsed and bit32.band(flags, ImGuiWindowFlags.ChildWindow) == 0) and window:TitleBarRect():GetSize() or window.SizeFull) -- POSITION -- Popup latch its initial position, will position itself when it appears next frame if window_just_activated_by_user then window.AutoPosLastDirection = ImGuiDir.None if bit32.band(flags, ImGuiWindowFlags.Popup) ~= 0 and bit32.band(flags, ImGuiWindowFlags.Modal) == 0 and not window_pos_set_by_api then -- FIXME: BeginPopup() could use SetNextWindowPos() ImVec2_Copy(window.Pos, g.BeginPopupStack:back().OpenPopupPos) end end if bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0 then IM_ASSERT(parent_window and parent_window.Active) window.BeginOrderWithinParent = parent_window.DC.ChildWindows.Size parent_window.DC.ChildWindows:push_back(window) if (bit32.band(flags, ImGuiWindowFlags.Popup) == 0) and not window_pos_set_by_api and not window_is_child_tooltip then ImVec2_Copy(window.Pos, parent_window.DC.CursorPos) end end local window_pos_with_pivot = (window.SetWindowPosVal.x ~= FLT_MAX and window.HiddenFramesCannotSkipItems == 0) if window_pos_with_pivot then ImGui.SetWindowPos(window, window.SetWindowPosVal - window.Size * window.SetWindowPosPivot, 0) -- Position given a pivot (e.g. for centering) elseif (bit32.band(flags, ImGuiWindowFlags.ChildMenu) ~= 0) then ImVec2_Copy(window.Pos, ImGui.FindBestWindowPosForPopup(window)) elseif (bit32.band(flags, ImGuiWindowFlags.Popup) ~= 0) and not window_pos_set_by_api and window_just_appearing_after_hidden_for_resize then ImVec2_Copy(window.Pos, ImGui.FindBestWindowPosForPopup(window)) elseif (bit32.band(flags, ImGuiWindowFlags.Tooltip) ~= 0) and not window_pos_set_by_api and not window_is_child_tooltip then ImVec2_Copy(window.Pos, ImGui.FindBestWindowPosForPopup(window)) end -- Late create viewport if we don't fit within our current host viewport. if window.ViewportAllowPlatformMonitorExtend >= 0 and not window.ViewportOwned and bit32.band(window.Viewport.Flags, ImGuiViewportFlags.IsMinimized) == 0 then if not window.Viewport:GetMainRect():Contains(window:Rect()) then -- This is based on the assumption that the DPI will be known ahead (same as the DPI of the selection done in UpdateSelectWindowViewport) -- local old_viewport = window.Viewport window.Viewport = ImGui.AddUpdateViewport(window, window.ID, window.Pos, window.Size, ImGuiViewportFlags.NoFocusOnAppearing) -- FIXME-DPI -- IM_ASSERT(old_viewport.DpiScale == window.Viewport.DpiScale) -- FIXME-DPI: Something went wrong ImGui.SetCurrentViewport(window, window.Viewport) SetCurrentWindow(window) end end if window.ViewportOwned then ImGui.WindowSyncOwnedViewport(window, parent_window_in_stack) end local viewport_rect = window.Viewport:GetMainRect() local viewport_work_rect = window.Viewport:GetWorkRect() local visibility_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding) local visibility_rect = ImRect(viewport_work_rect.Min + visibility_padding, viewport_work_rect.Max - visibility_padding) -- Clamp position/size so window stays visible within its viewport or monitor if not window_pos_set_by_api and bit32.band(flags, ImGuiWindowFlags.ChildWindow) == 0 then if not window.ViewportOwned and viewport_rect:GetWidth() > 0 and viewport_rect:GetHeight() > 0.0 then local size_for_clamping_y = window.Size.y if bit32.band(window.BgClickFlags, ImGuiWindowBgClickFlags.Move) == 0 and bit32.band(window.Flags, ImGuiWindowFlags.NoTitleBar) == 0 then size_for_clamping_y = window.TitleBarHeight end window.Pos.x = ImClamp(window.Pos.x, visibility_rect.Min.x - window.Size.x, visibility_rect.Max.x) window.Pos.y = ImClamp(window.Pos.y, visibility_rect.Min.y - size_for_clamping_y, visibility_rect.Max.y) end end window.Pos.x = ImTrunc(window.Pos.x) window.Pos.y = ImTrunc(window.Pos.y) local want_focus = false if (window_just_activated_by_user and bit32.band(flags, ImGuiWindowFlags.NoFocusOnAppearing) == 0) then if bit32.band(flags, ImGuiWindowFlags.Popup) ~= 0 then want_focus = true elseif (window.DockIsActive or bit32.band(flags, ImGuiWindowFlags.ChildWindow) == 0) and bit32.band(flags, ImGuiWindowFlags.Tooltip) == 0 then want_focus = true end end if bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0 and not window.DockIsActive then window.WindowRounding = style.ChildRounding elseif window.RootWindowDockTree.ViewportOwned then window.WindowRounding = 0.0 else if (bit32.band(flags, ImGuiWindowFlags.Popup) ~= 0 and bit32.band(flags, ImGuiWindowFlags.Modal) == 0) then window.WindowRounding = style.PopupRounding else window.WindowRounding = style.WindowRounding end end local handle_borders_and_resize_grips = (window.DockNodeAsHost ~= nil or not window.DockIsActive) if bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0 and window.ParentWindow.SkipItems then handle_borders_and_resize_grips = false end local border_hovered, border_held = -1, -1 local resize_grip_col = {0, 0, 0, 0} local resize_grip_count if (bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0) and (bit32.band(flags, ImGuiWindowFlags.Popup) == 0) then if (bit32.band(window.ChildFlags, ImGuiChildFlags.ResizeX) ~= 0) and (bit32.band(window.ChildFlags, ImGuiChildFlags.ResizeY) ~= 0) then resize_grip_count = 1 else resize_grip_count = 0 end else resize_grip_count = g.IO.ConfigWindowsResizeFromEdges and 2 or 1 -- Allow resize from lower-left if we have the mouse cursor feedback for it. end local resize_grip_draw_size = ImTrunc(ImMax(g.FontSize * 1.10, g.Style.WindowRounding + 1.0 + g.FontSize * 0.2)) if handle_borders_and_resize_grips and not window.Collapsed then local auto_fit_mask auto_fit_mask, border_hovered, border_held = UpdateWindowManualResize(window, resize_grip_count, resize_grip_col, visibility_rect) if auto_fit_mask ~= 0 then if bit32.band(auto_fit_mask, bit32.lshift(1, ImGuiAxis.X - 1)) ~= 0 then use_current_size_for_scrollbar_x = true end if bit32.band(auto_fit_mask, bit32.lshift(1, ImGuiAxis.Y - 1)) ~= 0 then use_current_size_for_scrollbar_y = true end end end window.ResizeBorderHovered = border_hovered window.ResizeBorderHeld = border_held -- Synchronize window --> viewport again and one last time (clamping and manual resize may have affected either) if window.ViewportOwned then if not window.Viewport.PlatformRequestMove then ImVec2_Copy(window.Viewport.Pos, window.Pos) end if not window.Viewport.PlatformRequestResize then ImVec2_Copy(window.Viewport.Size, window.Size) end window.Viewport:UpdateWorkRect() viewport_rect = window.Viewport:GetMainRect() end -- Save last known viewport position within the window itself (so it can be saved in .ini file and restored) ImVec2_Copy(window.ViewportPos, window.Viewport.Pos) --- SCROLLBAR VISIBILITY -- Update scrollbar visibility (based on the Size that was effective during last frame or the auto-resized Size) if not window.Collapsed then -- When reading the current size we need to read it after size constraints have been applied. -- Intentionally use previous frame values for InnerRect and ScrollbarSizes. -- And when we use window.DecoOuterSizeY1 here it doesn't have ScrollbarSizes.y applied yet. local avail_size_from_current_frame = ImVec2(window.SizeFull.x, window.SizeFull.y - (window.DecoOuterSizeY1 + window.DecoOuterSizeY2)) local avail_size_from_last_frame = window.InnerRect:GetSize() + scrollbar_sizes_from_last_frame local needed_size_from_last_frame = window_just_created and ImVec2(0, 0) or window.ContentSize + window.WindowPadding * 2.0 local size_for_scrollbars_x = use_current_size_for_scrollbar_x and avail_size_from_current_frame.x or avail_size_from_last_frame.x local size_for_scrollbars_y = use_current_size_for_scrollbar_y and avail_size_from_current_frame.y or avail_size_from_last_frame.y local scrollbar_x_prev = window.ScrollbarX -- local scrollbar_y_from_last_frame = window.ScrollbarY -- FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons? window.ScrollbarY = (bit32.band(flags, ImGuiWindowFlags.AlwaysVerticalScrollbar) ~= 0) or ((needed_size_from_last_frame.y > size_for_scrollbars_y) and not (bit32.band(flags, ImGuiWindowFlags.NoScrollbar) ~= 0)) window.ScrollbarX = (bit32.band(flags, ImGuiWindowFlags.AlwaysHorizontalScrollbar) ~= 0) or ((needed_size_from_last_frame.x > size_for_scrollbars_x - (window.ScrollbarY and style.ScrollbarSize or 0.0)) and not (bit32.band(flags, ImGuiWindowFlags.NoScrollbar) ~= 0) and (bit32.band(flags, ImGuiWindowFlags.HorizontalScrollbar) ~= 0)) -- Track when ScrollbarX visibility keeps toggling, which is a sign of a feedback loop, and stabilize by enforcing visibility (#3285, #8488) -- (Feedback loops of this sort can manifest in various situations, but combining horizontal + vertical scrollbar + using a clipper with varying width items is one frequent cause. -- The better solution is to, either (1) enforce visibility by using ImGuiWindowFlags.AlwaysHorizontalScrollbar or (2) declare stable contents width with SetNextWindowContentSize(), if you can compute it) window.ScrollbarXStabilizeToggledHistory = bit32.lshift(window.ScrollbarXStabilizeToggledHistory, 1) if scrollbar_x_prev ~= window.ScrollbarX then window.ScrollbarXStabilizeToggledHistory = bit32.bor(window.ScrollbarXStabilizeToggledHistory, 0x01) end local scrollbar_x_stabilize = (window.ScrollbarXStabilizeToggledHistory ~= 0) and ImCountSetBits(window.ScrollbarXStabilizeToggledHistory) >= 4 -- 4 == half of bits in our U8 history. if scrollbar_x_stabilize then window.ScrollbarX = true end -- if scrollbar_x_stabilize and not window.ScrollbarXStabilizeEnabled then -- IMGUI_DEBUG_LOG("[scroll] Stabilize ScrollbarX for Window '%s'\n", window.Name) -- end window.ScrollbarXStabilizeEnabled = scrollbar_x_stabilize if window.ScrollbarX and not window.ScrollbarY then window.ScrollbarY = (needed_size_from_last_frame.y > size_for_scrollbars_y - style.ScrollbarSize) and not (bit32.band(flags, ImGuiWindowFlags.NoScrollbar) ~= 0) end window.ScrollbarSizes = ImVec2(window.ScrollbarY and style.ScrollbarSize or 0.0, window.ScrollbarX and style.ScrollbarSize or 0.0) -- Amend the partially filled window.DecoOuterSizeX2 values. window.DecoOuterSizeX2 = window.DecoOuterSizeX2 + window.ScrollbarSizes.x window.DecoOuterSizeY2 = window.DecoOuterSizeY2 + window.ScrollbarSizes.y end local host_rect if (bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0 and bit32.band(flags, ImGuiWindowFlags.Popup) == 0 and not window_is_child_tooltip) then host_rect = parent_window.ClipRect else host_rect = viewport_rect end local outer_rect = window:Rect() local title_bar_rect = window:TitleBarRect() ImRect_Copy(window.OuterRectClipped, outer_rect) if window.DockIsActive then window.OuterRectClipped.Min.y = window.OuterRectClipped.Min.y + window.TitleBarHeight end window.OuterRectClipped:ClipWith(host_rect) window.InnerRect.Min.x = window.Pos.x + window.DecoOuterSizeX1 window.InnerRect.Min.y = window.Pos.y + window.DecoOuterSizeY1 window.InnerRect.Max.x = window.Pos.x + window.Size.x - window.DecoOuterSizeX2 window.InnerRect.Max.y = window.Pos.y + window.Size.y - window.DecoOuterSizeY2 local top_border_size = ((bit32.band(flags, ImGuiWindowFlags.MenuBar) ~= 0 or bit32.band(flags, ImGuiWindowFlags.NoTitleBar) == 0) and style.FrameBorderSize or window.WindowBorderSize) window.InnerClipRect.Min.x = ImFloor(0.5 + window.InnerRect.Min.x + window.WindowBorderSize * 0.5) window.InnerClipRect.Min.y = ImFloor(0.5 + window.InnerRect.Min.y + top_border_size * 0.5) window.InnerClipRect.Max.x = ImFloor(window.InnerRect.Max.x - window.WindowBorderSize * 0.5) window.InnerClipRect.Max.y = ImFloor(window.InnerRect.Max.y - window.WindowBorderSize * 0.5) window.InnerClipRect:ClipWithFull(host_rect) --- Scrolling -- Lock down maximum scrolling -- The value of ScrollMax are ahead from ScrollbarX/ScrollbarY which is intentionally using InnerRect from previous rect in order to accommodate -- for right/bottom aligned items without creating a scrollbar. window.ScrollMax.x = ImMax(0.0, window.ContentSize.x + window.WindowPadding.x * 2.0 - window.InnerRect:GetWidth()) window.ScrollMax.y = ImMax(0.0, window.ContentSize.y + window.WindowPadding.y * 2.0 - window.InnerRect:GetHeight()) -- Apply scrolling ImVec2_Copy(window.Scroll, CalcNextScrollFromScrollTargetAndClamp(window)) ImVec2_CopyV(window.ScrollTarget, FLT_MAX, FLT_MAX) window.DecoInnerSizeX1 = 0.0; window.DecoInnerSizeY1 = 0.0 IM_ASSERT(window.DrawList.CmdBuffer.Size == 1 and window.DrawList.CmdBuffer.Data[1].ElemCount == 0) window.DrawList:PushTexture(g.Font.OwnerAtlas.TexRef) ImGui.PushClipRect(host_rect.Min, host_rect.Max, false) local is_undocked_or_docked_visible = not window.DockIsActive or window.DockTabIsVisible if is_undocked_or_docked_visible then local render_decorations_in_parent = false if (bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0) and (bit32.band(flags, ImGuiWindowFlags.Popup) == 0) and not window_is_child_tooltip then -- - We test overlap with the previous child window only (testing all would end up being O(log N) not a good investment here) -- - We disable this when the parent window has zero vertices, which is a common pattern leading to laying out multiple overlapping childs local previous_child if parent_window.DC.ChildWindows.Size >= 2 then previous_child = parent_window.DC.ChildWindows.Data[parent_window.DC.ChildWindows.Size - 1] else previous_child = nil end local previous_child_overlapping if previous_child ~= nil then previous_child_overlapping = previous_child:Rect():Overlaps(window:Rect()) else previous_child_overlapping = false end local parent_is_empty = (parent_window.DrawList.VtxBuffer.Size == 0) if (window.DrawList.CmdBuffer:back().ElemCount == 0 and not parent_is_empty and not previous_child_overlapping) then render_decorations_in_parent = true end end if render_decorations_in_parent then window.DrawList = parent_window.DrawList end local window_to_highlight = g.NavWindowingTarget or g.NavWindow local title_bar_is_highlight = want_focus or (window_to_highlight ~= nil and (window.RootWindowForTitleBarHighlight == window_to_highlight.RootWindowForTitleBarHighlight or (window.DockNode ~= nil and window.DockNode == window_to_highlight.DockNode))) RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips, resize_grip_col, resize_grip_draw_size) if render_decorations_in_parent then window.DrawList = window.DrawListInst end end local allow_scrollbar_x = (bit32.band(flags, ImGuiWindowFlags.NoScrollbar) == 0) and (bit32.band(flags, ImGuiWindowFlags.HorizontalScrollbar) ~= 0) local allow_scrollbar_y = (bit32.band(flags, ImGuiWindowFlags.NoScrollbar) == 0) local work_rect_size_x if window.ContentSizeExplicit.x ~= 0.0 then work_rect_size_x = window.ContentSizeExplicit.x else local content_size_x = allow_scrollbar_x and (window.ContentSize and window.ContentSize.x or 0.0) or 0.0 local window_size_x = window.Size.x - window.WindowPadding.x * 2.0 - (window.DecoOuterSizeX1 + window.DecoOuterSizeX2) work_rect_size_x = ImMax(content_size_x, window_size_x) end local work_rect_size_y if window.ContentSizeExplicit.y ~= 0.0 then work_rect_size_y = window.ContentSizeExplicit.y else local content_size_y = allow_scrollbar_y and (window.ContentSize and window.ContentSize.y or 0.0) or 0.0 local window_size_y = window.Size.y - window.WindowPadding.y * 2.0 - (window.DecoOuterSizeY1 + window.DecoOuterSizeY2) work_rect_size_y = ImMax(content_size_y, window_size_y) end window.WorkRect.Min.x = ImTrunc(window.InnerRect.Min.x - window.Scroll.x + ImMax(window.WindowPadding.x, window.WindowBorderSize)) window.WorkRect.Min.y = ImTrunc(window.InnerRect.Min.y - window.Scroll.y + ImMax(window.WindowPadding.y, window.WindowBorderSize)) window.WorkRect.Max.x = window.WorkRect.Min.x + work_rect_size_x window.WorkRect.Max.y = window.WorkRect.Min.y + work_rect_size_y window.ParentWorkRect = window.WorkRect -- [LEGACY] Content Region -- FIXME-OBSOLETE: window->ContentRegionRect.Max is currently very misleading / partly faulty, but some BeginChild() patterns relies on it. -- Unless explicit content size is specified by user, this currently represent the region leading to no scrolling. -- Used by: -- - Mouse wheel scrolling + many other things window.ContentRegionRect.Min.x = window.Pos.x - window.Scroll.x + window.WindowPadding.x + window.DecoOuterSizeX1 window.ContentRegionRect.Min.y = window.Pos.y - window.Scroll.y + window.WindowPadding.y + window.DecoOuterSizeY1 window.ContentRegionRect.Max.x = window.ContentRegionRect.Min.x + (window.ContentSizeExplicit.x ~= 0.0 and window.ContentSizeExplicit.x or (window.Size.x - window.WindowPadding.x * 2.0 - (window.DecoOuterSizeX1 + window.DecoOuterSizeX2))) window.ContentRegionRect.Max.y = window.ContentRegionRect.Min.y + (window.ContentSizeExplicit.y ~= 0.0 and window.ContentSizeExplicit.y or (window.Size.y - window.WindowPadding.y * 2.0 - (window.DecoOuterSizeY1 + window.DecoOuterSizeY2))) -- Setup drawing context -- (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.) window.DC.Indent.x = window.DecoOuterSizeX1 + window.WindowPadding.x - window.Scroll.x window.DC.GroupOffset.x = 0.0 window.DC.ColumnsOffset.x = 0.0 -- Record the loss of precision of CursorStartPos which can happen due to really large scrolling amount. -- This is used by clipper to compensate and fix the most common use case of large scroll area. Easy and cheap, next best thing compared to switching everything to double or ImU64. local start_pos_highp_x = window.Pos.x + window.WindowPadding.x - window.Scroll.x + window.DecoOuterSizeX1 + window.DC.ColumnsOffset.x local start_pos_highp_y = window.Pos.y + window.WindowPadding.y - window.Scroll.y + window.DecoOuterSizeY1 ImVec2_CopyV(window.DC.CursorStartPos, start_pos_highp_x, start_pos_highp_y) ImVec2_CopyV(window.DC.CursorStartPosLossyness, start_pos_highp_x - window.DC.CursorStartPos.x, start_pos_highp_y - window.DC.CursorStartPos.y) ImVec2_Copy(window.DC.CursorPos, window.DC.CursorStartPos) ImVec2_Copy(window.DC.CursorPosPrevLine, window.DC.CursorPos) ImVec2_Copy(window.DC.CursorMaxPos, window.DC.CursorStartPos) ImVec2_Copy(window.DC.IdealMaxPos, window.DC.CursorStartPos) ImVec2_CopyV(window.DC.CurrLineSize, 0.0, 0.0) ImVec2_CopyV(window.DC.PrevLineSize, 0.0, 0.0) window.DC.CurrLineTextBaseOffset = 0.0 window.DC.PrevLineTextBaseOffset = 0.0 window.DC.IsSameLine = false window.DC.IsSetPos = false window.DC.NavLayerCurrent = ImGuiNavLayer.Main window.DC.NavLayersActiveMask = window.DC.NavLayersActiveMaskNext window.DC.NavLayersActiveMaskNext = 0x00 window.DC.NavIsScrollPushableX = true window.DC.NavHideHighlightOneFrame = false window.DC.NavWindowHasScrollY = window.ScrollMax.y > 0.0 window.DC.MenuBarAppending = false window.DC.MenuColumns:Update(style.ItemSpacing.x, window_just_activated_by_user) window.DC.TreeDepth = 0 window.DC.TreeHasStackDataDepthMask = 0x00 window.DC.TreeRecordsClippedNodesY2Mask = 0x00 window.DC.ChildWindows:resize(0) window.DC.StateStorage = window.StateStorage window.DC.CurrentColumns = nil window.DC.LayoutType = ImGuiLayoutType.Vertical window.DC.ParentLayoutType = (parent_window ~= nil) and parent_window.DC.LayoutType or ImGuiLayoutType.Vertical -- Default item width. Make it proportional to window size if window can be manually resized. -- (we cannot use AutoFitFramesX/AutoFitFramesY which is a temporary state) local is_resizable_width if bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0 and not window.DockIsActive then is_resizable_width = (window.Size.x > 0.0) and (bit32.band(window.ChildFlags, bit32.bor(ImGuiChildFlags.AutoResizeX, ImGuiChildFlags.AlwaysAutoResize)) == 0) else is_resizable_width = (window.Size.x > 0.0) and (bit32.band(flags, ImGuiWindowFlags.AlwaysAutoResize) == 0) end if is_resizable_width then window.DC.ItemWidthDefault = ImTrunc(window.Size.x * 0.65) else window.DC.ItemWidthDefault = ImTrunc(g.FontSize * 16.0) end window.DC.ItemWidth = window.DC.ItemWidthDefault window.DC.ItemWidthStack:resize(0) window.DC.TextWrapPos = -1.0 window.DC.TextWrapPosStack:resize(0) if bit32.band(flags, ImGuiWindowFlags.Modal) ~= 0 then window.DC.ModalDimBgColor = ImGui.ColorConvertFloat4ToU32(ImGui.GetStyleColorVec4(ImGuiCol.ModalWindowDimBg)) end if window.AutoFitFramesX > 0 then window.AutoFitFramesX = window.AutoFitFramesX - 1 end if window.AutoFitFramesY > 0 then window.AutoFitFramesY = window.AutoFitFramesY - 1 end g.NextWindowData:ClearFlags() if want_focus then ImGui.FocusWindow(window, ImGuiFocusRequestFlags.UnlessBelowModal) end if want_focus and window == g.NavWindow then ImGui.NavInitWindow(window, false) end -- Pressing Ctrl+C copy window content into the clipboard if g.IO.ConfigWindowsCopyContentsWithCtrlC then if g.NavWindow and g.NavWindow.RootWindow == window and g.ActiveId == 0 and ImGui.Shortcut(bit32.bor(ImGuiMod_Ctrl, ImGuiKey.C)) then ImGui.LogToClipboard(0) end end if bit32.band(flags, ImGuiWindowFlags.NoTitleBar) == 0 and not window.DockIsActive then open = RenderWindowTitleBarContents(window, ImRect(title_bar_rect.Min.x + window.WindowBorderSize, title_bar_rect.Min.y, title_bar_rect.Max.x - window.WindowBorderSize, title_bar_rect.Max.y), name, open) elseif bit32.band(flags, ImGuiWindowFlags.NoTitleBar) == 0 and window.DockIsActive then ImGui.LogText("%s\n", string.sub(window.Name, 1, ImGui.FindRenderedTextEnd(window.Name) - 1)) end -- Clear hit test shape every frame window.HitTestHoleSize.x = 0; window.HitTestHoleSize.y = 0 if bit32.band(flags, ImGuiWindowFlags.Tooltip) ~= 0 then g.TooltipPreviousWindow = window end if bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.DockingEnable) ~= 0 then -- Docking: Dragging a dockable window (or any of its child) turns it into a drag and drop source. -- We need to do this _before_ we overwrite window->DC.LastItemId below because BeginDockableDragDropSource() also overwrites it. if g.MovingWindow == window and bit32.band(window.RootWindowDockTree.Flags, ImGuiWindowFlags.NoDocking) == 0 then ImGui.BeginDockableDragDropSource(window) end -- Docking: Any dockable window can act as a target. For dock node hosts we call BeginDockableDragDropTarget() in DockNodeUpdate() instead. if g.DragDropActive and bit32.band(flags, ImGuiWindowFlags.NoDocking) == 0 then if g.MovingWindow == nil or g.MovingWindow.RootWindowDockTree ~= window then if window == window.RootWindowDockTree and bit32.band(window.Flags, ImGuiWindowFlags.DockNodeHost) == 0 then ImGui.BeginDockableDragDropTarget(window) end end end end if bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0 then window.BgClickFlags = parent_window.BgClickFlags else window.BgClickFlags = g.IO.ConfigWindowsMoveFromTitleBarOnly and ImGuiWindowBgClickFlags.None or ImGuiWindowBgClickFlags.Move end window.DC.WindowItemStatusFlags = ImGuiItemStatusFlags.None if ImGui.IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) then window.DC.WindowItemStatusFlags = bit32.bor(window.DC.WindowItemStatusFlags, ImGuiItemStatusFlags.HoveredRect) end ImGui.SetLastItemDataForWindow(window, title_bar_rect) else if (window.SkipRefresh) then SetWindowActiveForSkipRefresh(window) end ImGui.SetCurrentViewport(window, window.Viewport) SetCurrentWindow(window) g.NextWindowData:ClearFlags() ImGui.SetLastItemDataForWindow(window, window:TitleBarRect()) end if bit32.band(flags, ImGuiWindowFlags.DockNodeHost) == 0 and not window.SkipRefresh then ImGui.PushClipRect(window.InnerClipRect.Min, window.InnerClipRect.Max, true) -- [Roblox backend] content clip + scroll origin: lets the backend move scrolled content instead of redrawing it local dl, cr = window.DrawList, window.DrawList._CmdHeader.ClipRect dl._ContentClipX0, dl._ContentClipY0, dl._ContentClipX1, dl._ContentClipY1 = cr.x, cr.y, cr.z, cr.w dl._ScrollOriginX, dl._ScrollOriginY = window.Pos.x - window.Scroll.x, window.Pos.y - window.Scroll.y end window.WriteAccessed = false window.BeginCount = window.BeginCount + 1 g.NextWindowData:ClearFlags() if first_begin_of_the_frame and not window.SkipRefresh then -- When we are about to select this tab (which will only be visible on the _next frame_), flag it with a non-zero HiddenFramesCannotSkipItems. if window.DockIsActive and not window.DockTabIsVisible then if window.LastFrameJustFocused == g.FrameCount then window.HiddenFramesCannotSkipItems = 1 else window.HiddenFramesCanSkipItems = 1 end end if (bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0) and (bit32.band(flags, ImGuiWindowFlags.ChildMenu) == 0) then -- Child window can be out of sight and have "negative" clip windows. -- Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar). IM_ASSERT((bit32.band(flags, ImGuiWindowFlags.NoTitleBar) ~= 0) or window.DockIsActive) local nav_request = (bit32.band(window.ChildFlags, ImGuiChildFlags.NavFlattened) ~= 0) and (g.NavAnyRequest and g.NavWindow and g.NavWindow.RootWindowForNav == window.RootWindowForNav) if not g.LogEnabled and not nav_request then if window.OuterRectClipped.Min.x >= window.OuterRectClipped.Max.x or window.OuterRectClipped.Min.y >= window.OuterRectClipped.Max.y then if window.AutoFitFramesX > 0 or window.AutoFitFramesY > 0 then window.HiddenFramesCannotSkipItems = 1 else window.HiddenFramesCanSkipItems = 1 end end end -- Hide along with parent or if parent is collapsed if parent_window and (parent_window.Collapsed or parent_window.HiddenFramesCanSkipItems > 0) then window.HiddenFramesCanSkipItems = 1 end if parent_window and parent_window.HiddenFramesCannotSkipItems > 0 then window.HiddenFramesCannotSkipItems = 1 end end if style.Alpha <= 0.0 then window.HiddenFramesCanSkipItems = 1 end local hidden_regular = (window.HiddenFramesCanSkipItems > 0) or (window.HiddenFramesCannotSkipItems > 0) window.Hidden = hidden_regular or (window.HiddenFramesForRenderOnly > 0) -- Disable inputs for requested number of frames if window.DisableInputsFrames > 0 then window.DisableInputsFrames = window.DisableInputsFrames - 1 window.Flags = bit32.bor(window.Flags, ImGuiWindowFlags.NoInputs) end -- Update the SkipItems flag, used to early out of all items functions (no layout required) local skip_items = false if window.Collapsed or not window.Active or hidden_regular then if window.AutoFitFramesX <= 0 and window.AutoFitFramesY <= 0 and window.HiddenFramesCannotSkipItems <= 0 then skip_items = true end end window.SkipItems = skip_items -- Restore NavLayersActiveMaskNext to previous value when not visible, so a CTRL+Tab back can use a safe value. if window.SkipItems then window.DC.NavLayersActiveMaskNext = window.DC.NavLayersActiveMask end elseif first_begin_of_the_frame then window.SkipItems = true end if open == nil then open = true end return open, not window.SkipItems end function ImGui.End() local g = GImGui local window = g.CurrentWindow if (g.CurrentWindowStack.Size <= 1 and g.WithinFrameScopeWithImplicitWindow) then IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size > 1, "Calling End() too many times!") return end local window_stack_data = g.CurrentWindowStack:back() if bit32.band(window.Flags, ImGuiWindowFlags.Popup) ~= 0 then IM_ASSERT_USER_ERROR(g.WithinEndPopupID == window.ID, "Must call EndPopup() and not End()!") end if bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 and bit32.band(window.Flags, ImGuiWindowFlags.DockNodeHost) == 0 and not window.DockIsActive then IM_ASSERT_USER_ERROR(g.WithinEndChildID == window.ID, "Must call EndChild() and not End()!") end if window.DC.CurrentColumns then ImGui.EndColumns() end if bit32.band(window.Flags, ImGuiWindowFlags.DockNodeHost) == 0 and not window.SkipRefresh then ImGui.PopClipRect() end ImGui.PopFocusScope() if window_stack_data.DisabledOverrideReenable and window.RootWindow == window then ImGui.EndDisabledOverrideReenable() end if (window.SkipRefresh) then IM_ASSERT(window.DrawList == nil) window.DrawList = window.DrawListInst end -- Stop logging if g.LogWindow == window then -- FIXME: add more options for scope of logging ImGui.LogFinish() end if window.DC.IsSetPos then ImGui.ErrorCheckUsingSetCursorPosToExtendParentBoundaries() end -- Docking: report contents sizes to parent to allow for auto-resize if window.DockNode and window.DockTabIsVisible then local host_window = window.DockNode.HostWindow if host_window then ImVec2_Copy(host_window.DC.CursorMaxPos, window.DC.CursorMaxPos + window.WindowPadding - host_window.WindowPadding) end end -- Pop from window stack ImGuiLastItemData_Copy(g.LastItemData, window_stack_data.ParentLastItemDataBackup) if bit32.band(window.Flags, ImGuiWindowFlags.ChildMenu) ~= 0 then g.BeginMenuDepth = g.BeginMenuDepth - 1 end if bit32.band(window.Flags, ImGuiWindowFlags.Popup) ~= 0 then g.BeginPopupStack:pop_back() end -- Error handling, state recovery if g.IO.ConfigErrorRecovery and window_stack_data.StackSizesInBegin then ImGui.ErrorRecoveryTryToRecoverWindowState(window_stack_data.StackSizesInBegin) end g.CurrentWindowStack:pop_back() -- LUA: No "Ternary Operator", since lua (... and (1) or (2)) will eval (1) and (2) no matter what -- so something like `SetCurrentWindow((g.CurrentWindowStack.Size == 0) and nil or g.CurrentWindowStack:back().Window)` will error if (g.CurrentWindowStack.Size == 0) then SetCurrentWindow(nil) else SetCurrentWindow(g.CurrentWindowStack:back().Window) end if g.CurrentWindow then ImGui.SetCurrentViewport(g.CurrentWindow, g.CurrentWindow.Viewport) end end --- @param option ImGuiItemFlags --- @param enabled bool function ImGui.PushItemFlag(option, enabled) local g = GImGui local item_flags = g.CurrentItemFlags IM_ASSERT(item_flags == g.ItemFlagsStack:back()) if enabled then item_flags = bit32.bor(item_flags, option) else item_flags = bit32.band(item_flags, bit32.bnot(option)) end g.CurrentItemFlags = item_flags g.ItemFlagsStack:push_back(item_flags) end function ImGui.PopItemFlag() local g = GImGui IM_ASSERT_USER_ERROR_RET(g.ItemFlagsStack.Size > 1, "Calling PopItemFlag() too many times!") g.ItemFlagsStack:pop_back() g.CurrentItemFlags = g.ItemFlagsStack:back() end --- @param disabled? bool function ImGui.BeginDisabled(disabled) if disabled == nil then disabled = true end local g = GImGui local was_disabled = bit32.band(g.CurrentItemFlags, ImGuiItemFlags.Disabled) ~= 0 if not was_disabled and disabled then g.DisabledAlphaBackup = g.Style.Alpha g.Style.Alpha = g.Style.Alpha * g.Style.DisabledAlpha end if was_disabled or disabled then g.CurrentItemFlags = bit32.bor(g.CurrentItemFlags, ImGuiItemFlags.Disabled) end g.ItemFlagsStack:push_back(g.CurrentItemFlags) g.DisabledStackSize = g.DisabledStackSize + 1 end function ImGui.EndDisabled() local g = GImGui IM_ASSERT_USER_ERROR_RET(g.DisabledStackSize > 0, "Calling EndDisabled() too many times!") g.DisabledStackSize = g.DisabledStackSize - 1 local was_disabled = bit32.band(g.CurrentItemFlags, ImGuiItemFlags.Disabled) ~= 0 g.ItemFlagsStack:pop_back() g.CurrentItemFlags = g.ItemFlagsStack:back() if was_disabled and bit32.band(g.CurrentItemFlags, ImGuiItemFlags.Disabled) == 0 then g.Style.Alpha = g.DisabledAlphaBackup end end function ImGui.BeginDisabledOverrideReenable() local g = GImGui IM_ASSERT(bit32.band(g.CurrentItemFlags, ImGuiItemFlags.Disabled) ~= 0) g.CurrentWindowStack.Data[g.CurrentWindowStack.Size].DisabledOverrideReenableAlphaBackup = g.Style.Alpha g.Style.Alpha = g.DisabledAlphaBackup g.CurrentItemFlags = bit32.band(g.CurrentItemFlags, bit32.bnot(ImGuiItemFlags.Disabled)) g.ItemFlagsStack:push_back(g.CurrentItemFlags) g.DisabledStackSize = g.DisabledStackSize + 1 end function ImGui.EndDisabledOverrideReenable() local g = GImGui IM_ASSERT(g.DisabledStackSize > 0) g.DisabledStackSize = g.DisabledStackSize - 1 g.ItemFlagsStack:pop_back() g.CurrentItemFlags = g.ItemFlagsStack:back() g.Style.Alpha = g.CurrentWindowStack:back().DisabledOverrideReenableAlphaBackup end --- @param pos ImVec2 --- @param find_first_and_in_any_viewport bool --- @return ImGuiWindow, ImGuiWindow? local function FindHoveredWindowEx(pos, find_first_and_in_any_viewport) local g = GImGui local hovered_window = nil local hovered_window_under_moving_window = nil -- Special handling for the window being moved: Ignore the mouse viewport check (because it may reset/lose its viewport during the undocking frame) hovered_window = g.MovingWindow local backup_moving_window_viewport = nil if (find_first_and_in_any_viewport == false and g.MovingWindow) then backup_moving_window_viewport = g.MovingWindow.Viewport g.MovingWindow.Viewport = g.MouseViewport if (bit32.band(g.MovingWindow.Flags, ImGuiWindowFlags.NoMouseInputs) == 0) then hovered_window = g.MovingWindow end end local padding_regular = g.Style.TouchExtraPadding local padding_for_resize = ImMax(g.Style.TouchExtraPadding, ImVec2(g.Style.WindowBorderHoverPadding, g.Style.WindowBorderHoverPadding)) local window local stop_6075 = false for i = g.Windows.Size, 1, -1 do repeat window = g.Windows.Data[i] if not window.WasActive or window.Hidden then break end if bit32.band(window.Flags, ImGuiWindowFlags.NoMouseInputs) ~= 0 then break end IM_ASSERT(window.Viewport) if (window.Viewport ~= g.MouseViewport) then break end local hit_padding if bit32.band(window.Flags, bit32.bor(ImGuiWindowFlags.NoResize, ImGuiWindowFlags.AlwaysAutoResize)) ~= 0 then hit_padding = padding_regular else hit_padding = padding_for_resize end if not window.OuterRectClipped:ContainsWithPad(pos, hit_padding) then break end if window.HitTestHoleSize.x ~= 0 then local hx, hy = window.Pos.x + window.HitTestHoleOffset.x, window.Pos.y + window.HitTestHoleOffset.y if pos.x >= hx and pos.y >= hy and pos.x < hx + window.HitTestHoleSize.x and pos.y < hy + window.HitTestHoleSize.y then break -- continue end end if find_first_and_in_any_viewport then hovered_window = window stop_6075 = true; break else if hovered_window == nil then hovered_window = window end if hovered_window_under_moving_window == nil and (not g.MovingWindow or window.RootWindowDockTree ~= g.MovingWindow.RootWindowDockTree) then hovered_window_under_moving_window = window end if hovered_window and hovered_window_under_moving_window then stop_6075 = true; break end end until true if stop_6075 then break end end if (find_first_and_in_any_viewport == false and g.MovingWindow) then g.MovingWindow.Viewport = backup_moving_window_viewport end return hovered_window, hovered_window_under_moving_window end -- TODO: function ImGui.UpdateHoveredWindowAndCaptureFlags(mouse_pos) local g = GImGui local io = g.IO g.WindowsBorderHoverPadding = ImMax(ImMax(g.Style.TouchExtraPadding.x, g.Style.TouchExtraPadding.y), g.Style.WindowBorderHoverPadding) local clear_hovered_windows = false g.HoveredWindow, g.HoveredWindowUnderMovingWindow = FindHoveredWindowEx(mouse_pos, false) IM_ASSERT(g.HoveredWindow == nil or g.HoveredWindow == g.MovingWindow or g.HoveredWindow.Viewport == g.MouseViewport) g.HoveredWindowBeforeClear = g.HoveredWindow local modal_window = ImGui.GetTopMostPopupModal() if modal_window and g.HoveredWindow and not ImGui.IsWindowWithinBeginStackOf(g.HoveredWindow.RootWindow, modal_window) then clear_hovered_windows = true end if bit32.band(io.ConfigFlags, ImGuiConfigFlags.NoMouse) ~= 0 then clear_hovered_windows = true end local has_open_popup = (g.OpenPopupStack.Size > 0) local has_open_modal = (modal_window ~= nil) local mouse_earliest_down = -1 local mouse_any_down = false for i = 0, 2 do -- IM_COUNTOF(io.MouseDown) if io.MouseClicked[i] then io.MouseDownOwned[i] = (g.HoveredWindow ~= nil) or has_open_popup io.MouseDownOwnedUnlessPopupClose[i] = (g.HoveredWindow ~= nil) or has_open_modal end mouse_any_down = mouse_any_down or io.MouseDown[i] if (io.MouseDown[i] or io.MouseReleased[i]) then if (mouse_earliest_down == -1 or (io.MouseClickedTime[i] < io.MouseClickedTime[mouse_earliest_down])) then mouse_earliest_down = i end end end local mouse_avail = (mouse_earliest_down == -1) or io.MouseDownOwned[mouse_earliest_down] local mouse_avail_unless_popup_close = (mouse_earliest_down == -1) or io.MouseDownOwnedUnlessPopupClose[mouse_earliest_down] local mouse_dragging_extern_payload = g.DragDropActive and bit32.band(g.DragDropSourceFlags, ImGuiDragDropFlags.SourceExtern) ~= 0 if not mouse_avail and not mouse_dragging_extern_payload then clear_hovered_windows = true end if clear_hovered_windows then g.HoveredWindow = nil g.HoveredWindowUnderMovingWindow = nil end if (g.WantCaptureMouseNextFrame ~= -1) then io.WantCaptureMouse = (g.WantCaptureMouseNextFrame ~= 0) io.WantCaptureMouseUnlessPopupClose = io.WantCaptureMouse else io.WantCaptureMouse = (mouse_avail and (g.HoveredWindow ~= nil or mouse_any_down)) or has_open_popup io.WantCaptureMouseUnlessPopupClose = (mouse_avail_unless_popup_close and (g.HoveredWindow ~= nil or mouse_any_down)) or has_open_modal end io.WantCaptureKeyboard = false if bit32.band(io.ConfigFlags, ImGuiConfigFlags.NoKeyboard) == 0 then if (g.ActiveId ~= 0) or (modal_window ~= nil) then io.WantCaptureKeyboard = true elseif io.NavActive and bit32.band(io.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard) ~= 0 and io.ConfigNavCaptureKeyboard then io.WantCaptureKeyboard = true end end if g.WantCaptureKeyboardNextFrame ~= -1 then io.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame ~= 0) end io.WantTextInput = (g.WantTextInputNextFrame ~= -1) and (g.WantTextInputNextFrame ~= 0) or false end --- @param key ImGuiKey --- @param v bool --- @param analog_value float function ImGui.UpdateAliasKey(key, v, analog_value) IM_ASSERT(ImGui.IsAliasKey(key)) local key_data = ImGui.GetKeyData(nil, key) key_data.Down = v key_data.AnalogValue = analog_value end function ImGui.GetMergedModsFromKeys() local g = GImGui local mods = 0 if ImGui.GetKeyData(g, ImGuiMod_Ctrl).Down then mods = bit32.bor(mods, ImGuiMod_Ctrl) end if ImGui.GetKeyData(g, ImGuiMod_Shift).Down then mods = bit32.bor(mods, ImGuiMod_Shift) end if ImGui.GetKeyData(g, ImGuiMod_Alt).Down then mods = bit32.bor(mods, ImGuiMod_Alt) end if ImGui.GetKeyData(g, ImGuiMod_Super).Down then mods = bit32.bor(mods, ImGuiMod_Super) end return mods end function ImGui.UpdateKeyboardInputs() local g = GImGui local io = g.IO if bit32.band(io.ConfigFlags, ImGuiConfigFlags.NoKeyboard) ~= 0 then io:ClearInputKeys() end for n = 0, 2 do -- TODO: ImGuiMouseButton.COUNT - 1 ImGui.UpdateAliasKey(ImGui.MouseButtonToKey(n), io.MouseDown[n], io.MouseDown[n] and 1.0 or 0.0) end ImGui.UpdateAliasKey(ImGuiKey.MouseWheelX, io.MouseWheelH ~= 0.0, io.MouseWheelH) ImGui.UpdateAliasKey(ImGuiKey.MouseWheelY, io.MouseWheel ~= 0.0, io.MouseWheel) -- Synchronize io.KeyMods and io.KeyCtrl/io.KeyShift/etc. values local prev_key_mods = io.KeyMods io.KeyMods = ImGui.GetMergedModsFromKeys() io.KeyCtrl = bit32.band(io.KeyMods, ImGuiMod_Ctrl) ~= 0 io.KeyShift = bit32.band(io.KeyMods, ImGuiMod_Shift) ~= 0 io.KeyAlt = bit32.band(io.KeyMods, ImGuiMod_Alt) ~= 0 io.KeySuper = bit32.band(io.KeyMods, ImGuiMod_Super) ~= 0 if prev_key_mods ~= io.KeyMods then g.LastKeyModsChangeTime = g.Time end if prev_key_mods ~= io.KeyMods and prev_key_mods == 0 then g.LastKeyModsChangeFromNoneTime = g.Time end -- Clear gamepad data if disabled if bit32.band(io.BackendFlags, ImGuiBackendFlags.HasGamepad) == 0 then for key = ImGuiKey_Gamepad_BEGIN, ImGuiKey_Gamepad_END - 1 do io.KeysData[key - ImGuiKey.NamedKey_BEGIN].Down = false io.KeysData[key - ImGuiKey.NamedKey_BEGIN].AnalogValue = 0.0 end end -- Update keys for key = ImGuiKey.NamedKey_BEGIN, ImGuiKey.NamedKey_END - 1 do local key_data = io.KeysData[key - ImGuiKey.NamedKey_BEGIN] key_data.DownDurationPrev = key_data.DownDuration if key_data.Down then if key_data.DownDuration < 0.0 then key_data.DownDuration = 0.0 else key_data.DownDuration = key_data.DownDuration + io.DeltaTime end else key_data.DownDuration = -1.0 end if key_data.DownDuration == 0.0 then if ImGui.IsKeyboardKey(key) then g.LastKeyboardKeyPressTime = g.Time elseif key == ImGuiKey.ReservedForModCtrl or key == ImGuiKey.ReservedForModShift or key == ImGuiKey.ReservedForModAlt or key == ImGuiKey.ReservedForModSuper then g.LastKeyboardKeyPressTime = g.Time end end end -- Update keys/input owner (named keys only): one entry per key for key = ImGuiKey.NamedKey_BEGIN, ImGuiKey.NamedKey_END - 1 do local key_data = io.KeysData[key - ImGuiKey.NamedKey_BEGIN] local owner_data = g.KeysOwnerData[key - ImGuiKey.NamedKey_BEGIN] owner_data.OwnerCurr = owner_data.OwnerNext if not key_data.Down then -- Important: ownership is released on the frame after a release. Ensure a 'MouseDown -> CloseWindow -> MouseUp' chain doesn't lead to someone else seeing the MouseUp. owner_data.OwnerNext = ImGuiKeyOwner_NoOwner end owner_data.LockThisFrame = owner_data.LockUntilRelease and key_data.Down owner_data.LockUntilRelease = owner_data.LockUntilRelease and key_data.Down -- Clear LockUntilRelease when key is not Down anymore end ImGui.UpdateKeyRoutingTable(g.KeysRoutingTable) end function ImGui.UpdateMouseInputs() local g = GImGui local io = g.IO -- Mouse Wheel swapping flag -- As a standard behavior holding Shift while using Vertical Mouse Wheel triggers Horizontal scroll instead -- - We avoid doing it on OSX as it the OS input layer handles this already io.MouseWheelRequestAxisSwap = io.KeyShift and not io.ConfigMacOSXBehaviors -- Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well) if (ImGui.IsMousePosValid(io.MousePos)) then local x_val = ImFloor(io.MousePos.x) local y_val = ImFloor(io.MousePos.y) io.MousePos = ImVec2(x_val, y_val) g.MouseLastValidPos = ImVec2(x_val, y_val) end -- If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta if (ImGui.IsMousePosValid(io.MousePos) and ImGui.IsMousePosValid(io.MousePosPrev)) then io.MouseDelta = io.MousePos - io.MousePosPrev else io.MouseDelta = ImVec2(0.0, 0.0) end -- Update stationary timer -- FIXME: May need to rework again to have some tolerance for occasional small movement, while being functional on high-framerates local mouse_stationary_threshold = (io.MouseSource == ImGuiMouseSource.Mouse) and 2.0 or 3.0 local mouse_stationary = (ImLengthSqr(io.MouseDelta) <= mouse_stationary_threshold * mouse_stationary_threshold) g.MouseStationaryTimer = mouse_stationary and (g.MouseStationaryTimer + io.DeltaTime) or 0.0 -- If mouse moved we re-enable mouse hovering in case it was disabled by keyboard/gamepad. In theory should use a >0.0 threshold but would need to reset in everywhere we set this to true. if io.MouseDelta.x ~= 0.0 or io.MouseDelta.y ~= 0.0 then g.NavHighlightItemUnderNav = false end for i = 0, 2 do -- IM_COUNTOF(io.MouseDown) io.MouseClicked[i] = io.MouseDown[i] and (io.MouseDownDuration[i] < 0.0) io.MouseClickedCount[i] = 0 -- Will be filled below io.MouseReleased[i] = not io.MouseDown[i] and (io.MouseDownDuration[i] >= 0.0) if (io.MouseReleased[i]) then io.MouseReleasedTime[i] = g.Time end io.MouseDownDurationPrev[i] = io.MouseDownDuration[i] if io.MouseDown[i] then if io.MouseDownDuration[i] < 0.0 then io.MouseDownDuration[i] = 0.0 else io.MouseDownDuration[i] = io.MouseDownDuration[i] + io.DeltaTime end else io.MouseDownDuration[i] = -1.0 end if io.MouseClicked[i] then local is_repeated_click = false if (g.Time - io.MouseClickedTime[i]) < io.MouseDoubleClickTime then local delta_from_click_pos if ImGui.IsMousePosValid(io.MousePos) then delta_from_click_pos = io.MousePos - io.MouseClickedPos[i] else delta_from_click_pos = ImVec2(0.0, 0.0) end if ImLengthSqr(delta_from_click_pos) < io.MouseDoubleClickMaxDist * io.MouseDoubleClickMaxDist then is_repeated_click = true end end if is_repeated_click then io.MouseClickedLastCount[i] = io.MouseClickedLastCount[i] + 1 else io.MouseClickedLastCount[i] = 1 end io.MouseClickedTime[i] = g.Time io.MouseClickedPos[i] = io.MousePos io.MouseClickedCount[i] = io.MouseClickedLastCount[i] ImVec2_Copy(io.MouseDragMaxDistanceAbs[i], ImVec2(0.0, 0.0)) io.MouseDragMaxDistanceSqr[i] = 0.0 elseif io.MouseDown[i] then -- Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold local delta_from_click_pos if ImGui.IsMousePosValid(io.MousePos) then delta_from_click_pos = (io.MousePos - io.MouseClickedPos[i]) else delta_from_click_pos = ImVec2(0.0, 0.0) end io.MouseDragMaxDistanceSqr[i] = ImMax(io.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos)) io.MouseDragMaxDistanceAbs[i].x = ImMax(io.MouseDragMaxDistanceAbs[i].x, (delta_from_click_pos.x < 0.0) and -delta_from_click_pos.x or delta_from_click_pos.x) io.MouseDragMaxDistanceAbs[i].y = ImMax(io.MouseDragMaxDistanceAbs[i].y, (delta_from_click_pos.y < 0.0) and -delta_from_click_pos.y or delta_from_click_pos.y) end io.MouseDoubleClicked[i] = (io.MouseClickedCount[i] == 2) if (io.MouseClicked[i]) then g.NavHighlightItemUnderNav = false end end end --- @param window? ImGuiWindow --- @param wheel_amount float local function LockWheelingWindow(window, wheel_amount) local g = GImGui if window then g.WheelingWindowReleaseTimer = ImMin(g.WheelingWindowReleaseTimer + ImAbs(wheel_amount) * WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER, WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER) else g.WheelingWindowReleaseTimer = 0.0 end if (g.WheelingWindow == window) then return end -- IMGUI_DEBUG_LOG_IO("[io] LockWheelingWindow() \"%s\"\n", window ? window->Name : "NULL") g.WheelingWindow = window ImVec2_Copy(g.WheelingWindowRefMousePos, g.IO.MousePos) if window == nil then g.WheelingWindowStartFrame = -1 g.WheelingAxisAvg = ImVec2(0.0, 0.0) end end --- @param wheel ImVec2 --- @return ImGuiWindow? local function FindBestWheelingWindow(wheel) local g = GImGui local windows = {nil, nil} for axis = ImGuiAxis.X, ImGuiAxis.Y do if wheel[axis] ~= 0.0 then windows[axis] = g.HoveredWindow local window = g.HoveredWindow while bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 do local has_scrolling = (window.ScrollMax[axis] ~= 0.0) local inputs_disabled = (bit32.band(window.Flags, ImGuiWindowFlags.NoScrollWithMouse) ~= 0) and not (bit32.band(window.Flags, ImGuiWindowFlags.NoMouseInputs) ~= 0) if has_scrolling and not inputs_disabled then break -- select this window end windows[axis] = window.ParentWindow window = window.ParentWindow end end end if windows[1] == nil and windows[2] == nil then return nil end if (windows[1] == windows[2] or windows[1] == nil or windows[2] == nil) then return windows[2] and windows[2] or windows[1] end if (g.WheelingWindowStartFrame == -1) then g.WheelingWindowStartFrame = g.FrameCount end if ((g.WheelingWindowStartFrame == g.FrameCount and wheel.x ~= 0.0 and wheel.y ~= 0.0) or (g.WheelingAxisAvg.x == g.WheelingAxisAvg.y)) then ImVec2_Copy(g.WheelingWindowWheelRemainder, wheel) return nil end return (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) and windows[1] or windows[2] end function ImGui.UpdateMouseWheel() local g = GImGui if g.WheelingWindow ~= nil then g.WheelingWindowReleaseTimer = g.WheelingWindowReleaseTimer - g.IO.DeltaTime if ImGui.IsMousePosValid() and ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) > g.IO.MouseDragThreshold * g.IO.MouseDragThreshold then g.WheelingWindowReleaseTimer = 0.0 end if g.WheelingWindowReleaseTimer <= 0.0 then LockWheelingWindow(nil, 0.0) end end local mouse_window = g.WheelingWindow and g.WheelingWindow or g.HoveredWindow if not mouse_window or mouse_window.Collapsed then return end local owner_id = mouse_window.ID local wheel = ImVec2() wheel.x = ImGui.TestKeyOwner(ImGuiKey.MouseWheelX, owner_id) and g.IO.MouseWheelH or 0.0 wheel.y = ImGui.TestKeyOwner(ImGuiKey.MouseWheelY, owner_id) and g.IO.MouseWheel or 0.0 if g.WheelingWindow ~= nil then ImGui.SetKeyOwner(ImGuiKey.MouseWheelX, owner_id) ImGui.SetKeyOwner(ImGuiKey.MouseWheelY, owner_id) end -- Zoom / Scale window -- FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best redesigned if wheel.y ~= 0.0 and g.IO.KeyCtrl and g.IO.FontAllowUserScaling then LockWheelingWindow(mouse_window, wheel.y) local window = mouse_window local new_font_scale = ImClamp(window.FontWindowScale + g.IO.MouseWheel * 0.10, 0.50, 2.50) local scale = new_font_scale / window.FontWindowScale window.FontWindowScale = new_font_scale if window == window.RootWindow then local offset = window.Size * (1.0 - scale) * (g.IO.MousePos - window.Pos) / window.Size ImGui.SetWindowPos(window, window.Pos + offset, 0) -- MarkIniSettingsDirty(window) end return end if g.IO.KeyCtrl then return end -- Mouse wheel scrolling if (g.IO.MouseWheelRequestAxisSwap) then wheel = ImVec2(wheel.y, 0.0) end -- Maintain a rough average of moving magnitude on both axes -- FIXME: should by based on wall clock time rather than frame-counter g.WheelingAxisAvg.x = ImStd.ImExponentialMovingAverage(g.WheelingAxisAvg.x, ImAbs(wheel.x), 30) g.WheelingAxisAvg.y = ImStd.ImExponentialMovingAverage(g.WheelingAxisAvg.y, ImAbs(wheel.y), 30) -- In the rare situation where FindBestWheelingWindow() had to defer first frame of wheeling due to ambiguous main axis, reinject it now wheel = wheel + g.WheelingWindowWheelRemainder g.WheelingWindowWheelRemainder = ImVec2(0.0, 0.0) if (wheel.x == 0.0 and wheel.y == 0.0) then return end -- Mouse wheel scrolling: find target and apply -- - don't renew lock if axis doesn't apply on the window. -- - select a main axis when both axes are being moved. local window if g.WheelingWindow then window = g.WheelingWindow else window = FindBestWheelingWindow(wheel) end if window then if not (bit32.band(window.Flags, ImGuiWindowFlags.NoScrollWithMouse) ~= 0) and not (bit32.band(window.Flags, ImGuiWindowFlags.NoMouseInputs) ~= 0) then local do_scroll = { wheel.x ~= 0.0 and window.ScrollMax.x ~= 0.0, wheel.y ~= 0.0 and window.ScrollMax.y ~= 0.0 } if do_scroll[ImGuiAxis.X] and do_scroll[ImGuiAxis.Y] then do_scroll[(g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) and ImGuiAxis.Y or ImGuiAxis.X] = false end if do_scroll[ImGuiAxis.X] then LockWheelingWindow(window, wheel.x) local max_step = window.InnerRect:GetWidth() * 0.67 local scroll_step = ImTrunc(ImMin(2 * window.FontRefSize, max_step)) ImGui.SetScrollX(window, window.Scroll.x - wheel.x * scroll_step) g.WheelingWindowScrolledFrame = g.FrameCount end if do_scroll[ImGuiAxis.Y] then LockWheelingWindow(window, wheel.y) local max_step = window.InnerRect:GetHeight() * 0.67 local scroll_step = ImTrunc(ImMin(5 * window.FontRefSize, max_step)) ImGui.SetScrollY(window, window.Scroll.y - wheel.y * scroll_step) g.WheelingWindowScrolledFrame = g.FrameCount end end end end local function SetupDrawListSharedData() local g = GImGui local virtual_space = ImRect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX) for _, viewport in g.Viewports:iter() do virtual_space:Add(viewport:GetMainRect()) end g.DrawListSharedData.ClipRectFullscreen = virtual_space:ToVec4() g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol g.DrawListSharedData:SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError) g.DrawListSharedData.InitialFlags = ImDrawListFlags.None if g.Style.AntiAliasedLines then g.DrawListSharedData.InitialFlags = bit32.bor(g.DrawListSharedData.InitialFlags, ImDrawListFlags.AntiAliasedLines) end if g.Style.AntiAliasedLinesUseTex and not bit32.band(g.IO.Fonts.Flags, ImFontAtlasFlags.NoBakedLines) then g.DrawListSharedData.InitialFlags = bit32.bor(g.DrawListSharedData.InitialFlags, ImDrawListFlags.AntiAliasedLinesUseTex) end if g.Style.AntiAliasedFill then g.DrawListSharedData.InitialFlags = bit32.bor(g.DrawListSharedData.InitialFlags, ImDrawListFlags.AntiAliasedFill) end if bit32.band(g.IO.BackendFlags, ImGuiBackendFlags.RendererHasVtxOffset) then g.DrawListSharedData.InitialFlags = bit32.bor(g.DrawListSharedData.InitialFlags, ImDrawListFlags.AllowVtxOffset) end g.DrawListSharedData.InitialFringeScale = 1.0 end --- @param viewport ImGuiViewportP local function InitViewportDrawData(viewport) local g = GImGui local draw_data = viewport.DrawDataP viewport.DrawData = draw_data viewport.DrawDataBuilder.Layers[1] = draw_data.CmdLists viewport.DrawDataBuilder.Layers[2] = viewport.DrawDataBuilder.LayerData1 viewport.DrawDataBuilder.Layers[1]:resize(0) viewport.DrawDataBuilder.Layers[2]:resize(0) -- When minimized, we report draw_data->DisplaySize as zero to be consistent with non-viewport mode, -- and to allow applications/backends to easily skip rendering. -- FIXME: Note that we however do NOT attempt to report "zero drawlist / vertices" into the ImDrawData structure. -- This is because the work has been done already, and its wasted! We should fix that and add optimizations for -- it earlier in the pipeline, rather than pretend to hide the data at the end of the pipeline. local is_minimized = bit32.band(viewport.Flags, ImGuiViewportFlags.IsMinimized) ~= 0 draw_data.Valid = true draw_data.FrameCount = g.FrameCount draw_data.TotalVtxCount = 0 draw_data.TotalIdxCount = 0 draw_data.DisplayPos = viewport.Pos if is_minimized then draw_data.DisplaySize = ImVec2(0.0, 0.0) else ImVec2_Copy(draw_data.DisplaySize, viewport.Size) end if viewport.FramebufferScale.x ~= 0.0 then draw_data.FramebufferScale = viewport.FramebufferScale else draw_data.FramebufferScale = g.IO.DisplayFramebufferScale end draw_data.OwnerViewport = viewport draw_data.Textures = g.PlatformIO.Textures end -- FIXME-DPI: function ImGui.GetScale() local g = GImGui return g.Style._MainScale end --- @return ImGuiWindow function ImGui.GetCurrentWindowRead() local g = GImGui return g.CurrentWindow end --- @return ImGuiWindow @native function ImGui.GetCurrentWindow() local g = GImGui g.CurrentWindow.WriteAccessed = true return g.CurrentWindow end --- @param clip_rect_min ImVec2 --- @param clip_rect_max ImVec2 --- @param intersect_with_current_clip_rect? bool @native function ImGui.PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect) local window = ImGui.GetCurrentWindow() window.DrawList:PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect) ImRect_CopyFromV4(window.ClipRect, window.DrawList._ClipRectStack:back()) end function ImGui.PopClipRect() local window = ImGui.GetCurrentWindow() window.DrawList:PopClipRect() ImRect_CopyFromV4(window.ClipRect, window.DrawList._ClipRectStack:back()) end --- @param viewport ImGuiViewportP --- @param drawlist_no size_t # background(1), foreground(2) --- @param drawlist_name string --- @return ImDrawList local function GetViewportBgFgDrawList(viewport, drawlist_no, drawlist_name) local g = GImGui IM_ASSERT(drawlist_no <= 2) -- IM_COUNTOF(viewport->BgFgDrawLists) local draw_list = viewport.BgFgDrawLists[drawlist_no] if draw_list == nil then draw_list = ImDrawList(g.DrawListSharedData) draw_list._OwnerName = drawlist_name viewport.BgFgDrawLists[drawlist_no] = draw_list end if viewport.BgFgDrawListsLastTimeActive[drawlist_no] ~= g.Time then draw_list:_ResetForNewFrame() draw_list:PushTexture(g.IO.Fonts.TexRef) draw_list:PushClipRect(viewport.Pos, viewport.Pos + viewport.Size, false) viewport.BgFgDrawListsLastTimeActive[drawlist_no] = g.Time end return draw_list end --- @param viewport? ImGuiViewport function ImGui.GetBackgroundDrawList(viewport) local g = GImGui if (viewport == nil) then viewport = g.CurrentWindow.Viewport end --- @cast viewport ImGuiViewportP return GetViewportBgFgDrawList(viewport, 1, "##Background") end --- @param viewport? ImGuiViewport function ImGui.GetForegroundDrawList(viewport) local g = GImGui if (viewport == nil) then viewport = g.CurrentWindow.Viewport end --- @cast viewport ImGuiViewportP return GetViewportBgFgDrawList(viewport, 2, "##Foreground") end -- FIXME: Add a more explicit sort order in the window structure --- @param lhs ImGuiWindow --- @param rhs ImGuiWindow local function ChildWindowComparer(lhs, rhs) local a, b = lhs, rhs local d = (bit32.band(a.Flags, ImGuiWindowFlags.Popup) ~= 0 and 1 or 0) - (bit32.band(b.Flags, ImGuiWindowFlags.Popup) ~= 0 and 1 or 0) if d ~= 0 then return (d < 0) end d = (bit32.band(a.Flags, ImGuiWindowFlags.Tooltip) ~= 0 and 1 or 0) - (bit32.band(b.Flags, ImGuiWindowFlags.Tooltip) ~= 0 and 1 or 0) if d ~= 0 then return (d < 0) end return (a.BeginOrderWithinParent - b.BeginOrderWithinParent < 0) end --- @param out_sorted_windows ImVector --- @param window ImGuiWindow local function AddWindowToSortBuffer(out_sorted_windows, window) out_sorted_windows:push_back(window) if window.Active then local count = window.DC.ChildWindows.Size ImStd.ImQsort(window.DC.ChildWindows.Data, count, ChildWindowComparer) for i = 1, count do local child = window.DC.ChildWindows[i] if child.Active then AddWindowToSortBuffer(out_sorted_windows, child) end end end end local function AddWindowToDrawData(window, layer) local g = GImGui local viewport = window.Viewport g.IO.MetricsRenderWindows = g.IO.MetricsRenderWindows + 1 if window.DrawList._Splitter._Count > 1 then window.DrawList:ChannelsMerge() -- Merge if user forgot to merge back. Also required for DockNodeHost windows. end ImGui.AddDrawListToDrawDataEx(viewport.DrawDataP, viewport.DrawDataBuilder.Layers[layer], window.DrawList) for _, child in window.DC.ChildWindows:iter() do if (ImGui.IsWindowActiveAndVisible(child)) then -- Clipped children may have been marked not active AddWindowToDrawData(child, layer) end end end --- @param window ImGuiWindow local function GetWindowDisplayLayer(window) return (bit32.band(window.Flags, ImGuiWindowFlags.Tooltip) ~= 0) and 2 or 1 end --- @param window ImGuiWindow local function AddRootWindowToDrawData(window) AddWindowToDrawData(window, GetWindowDisplayLayer(window)) end --- @param builder ImDrawDataBuilder local function FlattenDrawDataIntoSingleLayer(builder) local n = builder.Layers[1].Size local full_size = n for i = 2, #builder.Layers do full_size = full_size + builder.Layers[i].Size end builder.Layers[1]:resize(full_size) for layer_n = 2, #builder.Layers do local layer = builder.Layers[layer_n] if not layer:empty() then for i = 1, layer.Size do builder.Layers[1].Data[n + i] = layer.Data[i] end n = n + layer.Size layer:resize(0) end end end function ImGui.NewFrame() IM_ASSERT(GImGui ~= nil, "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?") local g = GImGui g.ConfigFlagsLastFrame = g.ConfigFlagsCurrFrame g.ConfigFlagsCurrFrame = g.IO.ConfigFlags -- Load settings on first frame, save settings when modified (after a delay) ImGui.UpdateSettings() g.Time = g.Time + g.IO.DeltaTime g.FrameCount = g.FrameCount + 1 g.TooltipOverrideCount = 0 g.WindowsActiveCount = 0 g.MenusIdSubmittedThisFrame:resize(0) -- FIXME: are lines below correct and necessary g.FramerateSecPerFrameAccum = g.FramerateSecPerFrameAccum + (g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]) g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % 60 g.FramerateSecPerFrameCount = ImMin(g.FramerateSecPerFrameCount + 1, 60) if g.FramerateSecPerFrameAccum > 0 then g.IO.Framerate = (1.0 / (g.FramerateSecPerFrameAccum / g.FramerateSecPerFrameCount)) else g.IO.Framerate = FLT_MAX end ImGui.UpdateInputEvents(g.IO.ConfigInputTrickleEventQueue) ImGui.UpdateViewportsNewFrame() ImGui.UpdateTexturesNewFrame() SetupDrawListSharedData() ImGui.UpdateFontsNewFrame() g.WithinFrameScope = true -- Mark rendering data as invalid to prevent user who may have a handle on it to use it. for _, viewport in g.Viewports:iter() do viewport.DrawData = nil viewport.DrawDataP.Valid = false end if g.DragDropActive and g.DragDropPayload.SourceId == g.ActiveId then ImGui.KeepAliveID(g.DragDropPayload.SourceId) end if not g.IO.ConfigDebugHighlightIdConflicts or not g.IO.KeyCtrl then g.DebugDrawIdConflictsId = 0 end if g.IO.ConfigDebugHighlightIdConflicts and g.HoveredIdPreviousFrameItemCount > 1 then g.DebugDrawIdConflictsId = g.HoveredIdPreviousFrame end -- Update HoveredId data if not g.HoveredIdPreviousFrame then g.HoveredIdTimer = 0.0 end if not g.HoveredIdPreviousFrame or (g.HoveredId and g.ActiveId == g.HoveredId) then g.HoveredIdNotActiveTimer = 0.0 end if g.HoveredId then g.HoveredIdTimer = g.HoveredIdTimer + g.IO.DeltaTime end if g.HoveredId and g.ActiveId ~= g.HoveredId then g.HoveredIdNotActiveTimer = g.HoveredIdNotActiveTimer + g.IO.DeltaTime end g.HoveredIdPreviousFrame = g.HoveredId g.HoveredIdPreviousFrameItemCount = 0 g.HoveredId = 0 g.HoveredIdAllowOverlap = false g.HoveredIdIsDisabled = false if (g.ActiveId ~= 0 and g.ActiveIdIsAlive ~= g.ActiveId and g.ActiveIdPreviousFrame == g.ActiveId) then IMGUI_DEBUG_LOG_ACTIVEID("NewFrame(): ClearActiveID() 0x%08X because it isn't marked alive anymore!", g.ActiveId) ImGui.ClearActiveID() end -- Update ActiveId data (clear reference to active widget if the widget isn't alive anymore) if g.ActiveId then g.ActiveIdTimer = g.ActiveIdTimer + g.IO.DeltaTime end if g.ActiveId and g.ActiveId == g.LastActiveId then g.LastActiveIdWasSelected = g.ActiveIdWasSelected g.LastActiveIdWasSoleSelected = g.ActiveIdWasSoleSelected end g.LastActiveIdTimer = g.LastActiveIdTimer + g.IO.DeltaTime g.ActiveIdPreviousFrame = g.ActiveId g.ActiveIdIsAlive = 0 g.AnyIdHasBeenEditedThisFrame = false g.ActiveIdHasBeenEditedThisFrame = false g.ActiveIdIsJustActivated = false if g.TempInputId ~= 0 and g.ActiveId ~= g.TempInputId then g.TempInputId = 0 end if g.InputTextReactivateId ~= 0 and g.InputTextReactivateId ~= g.DeactivatedItemData.ID then g.InputTextReactivateId = 0 end if g.ActiveId == 0 then g.ActiveIdUsingNavDirMask = 0x00 g.ActiveIdUsingAllKeyboardKeys = false end if g.DeactivatedItemData.ElapseFrame < g.FrameCount then g.DeactivatedItemData.ID = 0 end g.DeactivatedItemData.IsAlive = false if g.InputTextDeactivatedState.ElapseFrame < g.FrameCount then g.InputTextDeactivatedState.ID = 0 end -- Record when we have been stationary as this state is preserved while over same item. -- FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values. -- To allow this we should store HoverItemMaxStationaryTime+ID and perform the >= check in IsItemHovered() function. if g.HoverItemDelayId ~= 0 and g.MouseStationaryTimer >= g.Style.HoverStationaryDelay then g.HoverItemUnlockedStationaryId = g.HoverItemDelayId elseif g.HoverItemDelayId == 0 then g.HoverItemUnlockedStationaryId = 0 end if g.HoveredWindow ~= nil and g.MouseStationaryTimer >= g.Style.HoverStationaryDelay then g.HoverWindowUnlockedStationaryId = g.HoveredWindow.ID elseif g.HoveredWindow == nil then g.HoverWindowUnlockedStationaryId = 0 end -- Update hover delay for IsItemHovered() with delays and tooltips g.HoverItemDelayIdPreviousFrame = g.HoverItemDelayId if g.HoverItemDelayId ~= 0 then g.HoverItemDelayTimer = g.HoverItemDelayTimer + g.IO.DeltaTime g.HoverItemDelayClearTimer = 0.0 g.HoverItemDelayId = 0 elseif g.HoverItemDelayTimer > 0.0 then -- This gives a little bit of leeway before clearing the hover timer, allowing mouse to cross gaps -- We could expose 0.25f as style.HoverClearDelay but I am not sure of the logic yet, this is particularly subtle. g.HoverItemDelayClearTimer = g.HoverItemDelayClearTimer + g.IO.DeltaTime if g.HoverItemDelayClearTimer >= ImMax(0.25, g.IO.DeltaTime * 2.0) then -- ~7 frames at 30 Hz + allow for low framerate g.HoverItemDelayTimer = 0.0 g.HoverItemDelayClearTimer = 0.0 -- May want a decaying timer, in which case need to clamp at max first, based on max of caller last requested timer. end end ImGui.UpdateKeyboardInputs() -- Drag and drop g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr g.DragDropAcceptIdCurr = 0 g.DragDropAcceptFlagsPrev = g.DragDropAcceptFlagsCurr g.DragDropAcceptFlagsCurr = ImGuiDragDropFlags.None g.DragDropAcceptIdCurrRectSurface = FLT_MAX g.DragDropWithinSource = false g.DragDropWithinTarget = false g.DragDropHoldJustPressedId = 0 if g.DragDropActive then local owner_id = (g.ActiveId ~= 0) and g.ActiveId or ImHashStr("##DragDropCancelHandler") if ImGui.Shortcut(ImGuiKey.Escape, ImGuiInputFlags.RouteGlobal, owner_id) then ImGui.ClearActiveID() ImGui.ClearDragDrop() end end g.TooltipPreviousWindow = nil ImGui.NavUpdate() ImGui.UpdateMouseInputs() -- Undocking -- (needs to be before UpdateMouseMovingWindowNewFrame so the window is already offset and following the mouse on the detaching frame) ImGui.DockContextNewFrameUpdateUndocking(g) IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size) local gc_all = g.GcCompactAll or g.IO.ConfigMemoryCompactTimer < 0.0 local memory_compact_start_time = gc_all and FLT_MAX or (g.Time - g.IO.ConfigMemoryCompactTimer) for _, window in g.Windows:iter() do window.WasActive = window.Active window.Active = false window.WriteAccessed = false window.BeginCountPreviousFrame = window.BeginCount window.BeginCount = 0 if (not window.WasActive or gc_all) and not window.MemoryCompacted and window.LastTimeActive < memory_compact_start_time then ImGui.GcCompactTransientWindowBuffers(window) end end ImGui.UpdateHoveredWindowAndCaptureFlags(g.IO.MousePos) ImGui.UpdateMouseMovingWindowNewFrame() -- Background darkening/whitening if ImGui.GetTopMostPopupModal() ~= nil or (g.NavWindowingTarget ~= nil and g.NavWindowingHighlightAlpha > 0.0) then g.DimBgRatio = ImMin(g.DimBgRatio + g.IO.DeltaTime * 6.0, 1.0) else g.DimBgRatio = ImMax(g.DimBgRatio - g.IO.DeltaTime * 10.0, 0.0) end g.MouseCursor = ImGuiMouseCursor.Arrow g.WantCaptureMouseNextFrame = -1 g.WantCaptureKeyboardNextFrame = -1 g.WantTextInputNextFrame = -1 -- Platform IME data: reset for the frame ImGuiPlatformImeData_Copy(g.PlatformImeDataPrev, g.PlatformImeData) g.PlatformImeData.WantVisible = false g.PlatformImeData.WantTextInput = false ImGui.UpdateMouseWheel() -- Garbage collect transient buffers of recently unused tables for i = 1, g.TablesLastTimeActive.Size do if g.TablesLastTimeActive.Data[i] >= 0.0 and g.TablesLastTimeActive.Data[i] < memory_compact_start_time then ImGui.TableGcCompactTransientBuffers(g.Tables:GetByIndex(i - 1)) end end for _, table_temp_data in g.TablesTempData:iter() do table_temp_data.ReconcileColumnsRequests:clear() -- Unusual: clear every frame because this is rarely used. if table_temp_data.LastTimeActive >= 0.0 and table_temp_data.LastTimeActive < memory_compact_start_time then ImGui.TableGcCompactTransientBuffers(table_temp_data) end end if g.GcCompactAll then ImGui.GcCompactTransientMiscBuffers() end g.GcCompactAll = false if (g.NavWindow and not g.NavWindow.WasActive) then ImGui.FocusTopMostWindowUnderOne(nil, nil, nil, ImGuiFocusRequestFlags.RestoreFocusedChild) end g.CurrentWindowStack:resize(0) g.BeginPopupStack:resize(0) g.ItemFlagsStack:resize(0) g.ItemFlagsStack:push_back(ImGuiItemFlags.Default_) g.CurrentItemFlags = g.ItemFlagsStack:back() g.GroupStack:resize(0) -- [DEBUG] ID Stack Tool query ImGui.UpdateDebugToolItemPathQuery() -- Docking ImGui.DockContextNewFrameUpdateDocking(g) g.WithinFrameScopeWithImplicitWindow = true ImGui.SetNextWindowSize(ImVec2(400, 400), ImGuiCond.FirstUseEver) ImGui.Begin("Debug##Default") IM_ASSERT(g.CurrentWindow.IsFallbackWindow == true) -- Store stack sizes g.ErrorCountCurrentFrame = 0 g.StackSizesInNewFrame = g.StackSizesInNewFrame or ImGuiErrorRecoveryState() ImGui.ErrorRecoveryStoreState(g.StackSizesInNewFrame) end function ImGui.EndFrame() local g = GImGui IM_ASSERT(g.Initialized) if g.FrameCountEnded == g.FrameCount then return end IM_ASSERT_USER_ERROR(g.WithinFrameScope, "Forgot to call ImGui::NewFrame()?") -- [EXPERIMENTAL] Recover from errors if g.IO.ConfigErrorRecovery and g.StackSizesInNewFrame then ImGui.ErrorRecoveryTryToRecoverState(g.StackSizesInNewFrame) end ImGui.ErrorCheckEndFrameFinalizeErrorTooltip() -- Notify Platform when our Input Method Editor cursor has moved local ime_data = g.PlatformImeData if g.PlatformIO.Platform_SetImeDataFn ~= nil and not ImGuiPlatformImeData_Compare(ime_data, g.PlatformImeDataPrev) then local viewport = ImGui.FindViewportByID(ime_data.ViewportId) if viewport == nil then viewport = ImGui.GetMainViewport() end -- IMGUI_DEBUG_LOG_IO("[io] Calling Platform_SetImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f) for Viewport 0x%08X", ime_data.WantVisible, ime_data.InputPos.x, ime_data.InputPos.y, viewport.ID) g.PlatformIO.Platform_SetImeDataFn(g, viewport, ime_data) end g.WantTextInputNextFrame = ime_data.WantTextInput g.WithinFrameScopeWithImplicitWindow = false if (g.CurrentWindow and g.CurrentWindow.IsFallbackWindow and g.CurrentWindow.WriteAccessed == false) then g.CurrentWindow.Active = false end ImGui.End() -- Update navigation: Ctrl+Tab, wrap-around requests ImGui.NavEndFrame() -- Update docking ImGui.DockContextEndFrame(g) ImGui.SetCurrentViewport(nil, nil) -- Drag and Drop: Elapse payload (if delivered, or if source stops being submitted) if g.DragDropActive then local is_delivered = g.DragDropPayload.Delivery local is_elapsed = (g.DragDropSourceFrameCount + 1 < g.FrameCount) and (bit32.band(g.DragDropSourceFlags, ImGuiDragDropFlags.PayloadAutoExpire) ~= 0 or g.DragDropMouseButton == -1 or not ImGui.IsMouseDown(g.DragDropMouseButton)) if is_delivered or is_elapsed then ImGui.ClearDragDrop() end end -- Drag and Drop: Fallback for missing source tooltip. This is not ideal but better than nothing. -- If you want to handle source item disappearing: instead of submitting your description tooltip -- in the BeginDragDropSource() block of the dragged item, you can submit them from a safe single spot -- (e.g. end of your item loop, or before EndFrame) by reading payload data. -- In the typical case, the contents of drag tooltip should be possible to infer solely from payload data. if g.DragDropActive and g.DragDropSourceFrameCount + 1 < g.FrameCount and (bit32.band(g.DragDropSourceFlags, ImGuiDragDropFlags.SourceNoPreviewTooltip) == 0) then g.DragDropWithinSource = true ImGui.SetTooltip("...") g.DragDropWithinSource = false end g.WithinFrameScope = false g.FrameCountEnded = g.FrameCount ImGui.UpdateFontsEndFrame() ImGui.UpdateMouseMovingWindowEndFrame() ImGui.UpdateViewportsEndFrame() g.WindowsTempSortBuffer:resize(0) g.WindowsTempSortBuffer:reserve(g.Windows.Size) for _, window in g.Windows:iter() do repeat if window.Active and bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 then -- if a child is active its parent will add it break end AddWindowToSortBuffer(g.WindowsTempSortBuffer, window) until true end IM_ASSERT(g.Windows.Size == g.WindowsTempSortBuffer.Size) g.Windows:swap(g.WindowsTempSortBuffer) g.IO.MetricsActiveWindows = g.WindowsActiveCount ImGui.UpdateTexturesEndFrame() for _, atlas in g.FontAtlases:iter() do atlas.Locked = false end ImVec2_Copy(g.IO.MousePosPrev, g.IO.MousePos) g.IO.AppFocusLost = false g.IO.MouseWheel = 0.0 g.IO.MouseWheelH = 0.0 g.IO.InputQueueCharacters:resize(0) end -- TODO: function ImGui.Render() local g = GImGui IM_ASSERT(g.Initialized) if g.FrameCountEnded ~= g.FrameCount then ImGui.EndFrame() end if g.FrameCountRendered == g.FrameCount then return end g.FrameCountRendered = g.FrameCount g.IO.MetricsRenderWindows = 0 for _, viewport in g.Viewports:iter() do InitViewportDrawData(viewport) if viewport.BgFgDrawLists[1] ~= nil and viewport.BgFgDrawListsLastTimeActive[1] == g.Time then ImGui.AddDrawListToDrawDataEx(viewport.DrawDataP, viewport.DrawDataBuilder.Layers[1], ImGui.GetBackgroundDrawList(viewport)) end end ImGui.RenderDimmedBackgrounds() local windows_to_render_top_most = {nil, nil} windows_to_render_top_most[1] = (g.NavWindowingTarget and (bit32.band(g.NavWindowingTarget.Flags, ImGuiWindowFlags.NoBringToFrontOnFocus) == 0)) and g.NavWindowingTarget.RootWindowDockTree or nil windows_to_render_top_most[2] = g.NavWindowingTarget and g.NavWindowingListWindow or nil for _, window in g.Windows:iter() do if ImGui.IsWindowActiveAndVisible(window) and (bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) == 0) and window ~= windows_to_render_top_most[1] and window ~= windows_to_render_top_most[2] then AddRootWindowToDrawData(window) end end for n = 1, 2 do if windows_to_render_top_most[n] and ImGui.IsWindowActiveAndVisible(windows_to_render_top_most[n]) then -- NavWindowingTarget is always temporarily displayed as the top-most window AddRootWindowToDrawData(windows_to_render_top_most[n]) end end -- Draw software mouse cursor if requested by io.MouseDrawCursor flag if g.IO.MouseDrawCursor and g.MouseCursor ~= ImGuiMouseCursor.None then ImGui.RenderMouseCursor(g.IO.MousePos, g.Style.MouseCursorScale, g.MouseCursor, IM_COL32_WHITE, IM_COL32_BLACK, IM_COL32(0, 0, 0, 48)) end g.IO.MetricsRenderVertices = 0 g.IO.MetricsRenderIndices = 0 for _, viewport in g.Viewports:iter() do FlattenDrawDataIntoSingleLayer(viewport.DrawDataBuilder) if viewport.BgFgDrawLists[2] ~= nil and viewport.BgFgDrawListsLastTimeActive[2] == g.Time then ImGui.AddDrawListToDrawDataEx(viewport.DrawDataP, viewport.DrawDataBuilder.Layers[1], ImGui.GetForegroundDrawList(viewport)) end local draw_data = viewport.DrawDataP for _, draw_list in draw_data.CmdLists:iter() do draw_list:_PopUnusedDrawCmd() end g.IO.MetricsRenderVertices = g.IO.MetricsRenderVertices + draw_data.TotalVtxCount g.IO.MetricsRenderIndices = g.IO.MetricsRenderIndices + draw_data.TotalIdxCount end end --- @param text ImString --- @param text_end? int # Exclusive upper bound --- @param hide_text_after_double_hash? bool --- @param wrap_width? float --- @return ImVec2 @native function ImGui.CalcTextSize(text, text_end, hide_text_after_double_hash, wrap_width) return ImGui.CalcTextSizeEx(text, 1, text_end, hide_text_after_double_hash, wrap_width) end --- @param text ImString --- @param text_begin int --- @param text_end? int # Exclusive upper bound --- @param hide_text_after_double_hash? bool --- @param wrap_width? float --- @return ImVec2 @native function ImGui.CalcTextSizeEx(text, text_begin, text_end, hide_text_after_double_hash, wrap_width) if hide_text_after_double_hash == nil then hide_text_after_double_hash = false end if wrap_width == nil then wrap_width = -1.0 end local g = GImGui local text_display_end if hide_text_after_double_hash then text_display_end = ImGui.FindRenderedTextEnd(text, text_end) else text_display_end = text_end end local font = g.Font local font_size = g.FontSize if text == "" or (text_end and text_end <= text_begin) then return ImVec2(0.0, font_size) end local text_size = font:CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_begin, text_display_end, nil) text_size.x = ImCeilFast(text_size.x) return text_size end function ImGui.GetDrawData() local g = GImGui local viewport = g.Viewports[1] return viewport.DrawDataP.Valid and viewport.DrawDataP or nil end function ImGui.GetTime() return GImGui.Time end function ImGui.Shutdown() local g = GImGui IM_ASSERT_USER_ERROR(g.IO.BackendPlatformUserData == nil, "Forgot to shutdown Platform backend?") IM_ASSERT_USER_ERROR(g.IO.BackendRendererUserData == nil, "Forgot to shutdown Renderer backend?") for _, viewport in g.Viewports:iter() do -- IM_UNUSED(viewport) IM_ASSERT_USER_ERROR(viewport.RendererUserData == nil and viewport.PlatformUserData == nil and viewport.PlatformHandle == nil, "Backend or app forgot to call DestroyPlatformWindows()?") end for _, atlas in g.FontAtlases:iter() do ImGui.UnregisterFontAtlas(atlas) if atlas.OwnerContext == g then IM_ASSERT(atlas.RefCount == 0, "Destroying context owning a ImFontAtlas which is still used elsewhere!") if atlas.RefCount == 0 then atlas.Locked = false end end end g.DrawListSharedData.TempBuffer:clear() ImGui.DockContextShutdown(g) if not g.Initialized then return end -- TODO: g.MenusIdSubmittedThisFrame:clear() end --- @return string? function ImGui.GetClipboardText() local g = GImGui return g.PlatformIO.Platform_GetClipboardTextFn and g.PlatformIO.Platform_GetClipboardTextFn(g) or nil end --- @param text string function ImGui.SetClipboardText(text) local g = GImGui if g.PlatformIO.Platform_SetClipboardTextFn ~= nil then g.PlatformIO.Platform_SetClipboardTextFn(g, text) end end function ImGui.GetIO() return GImGui.IO end --- @return ImGuiPlatformIO function ImGui.GetPlatformIO() IM_ASSERT(GImGui ~= nil, "No current context. Did you call ImGui.CreateContext() and ImGui.SetCurrentContext()?") return GImGui.PlatformIO end function ImGui.GetMouseCursor() local g = GImGui return g.MouseCursor end --- @param cursor_type ImGuiMouseCursor function ImGui.SetMouseCursor(cursor_type) local g = GImGui g.MouseCursor = cursor_type end --- @param col_a ImU32 --- @param col_b ImU32 --- @return ImU32 function ImStd.ImAlphaBlendColors(col_a, col_b) local t = bit32.band(bit32.rshift(col_b, IM_COL32_A_SHIFT), 0xFF) / 255.0 local r = ImLerp(bit32.band(bit32.rshift(col_a, IM_COL32_R_SHIFT), 0xFF), bit32.band(bit32.rshift(col_b, IM_COL32_R_SHIFT), 0xFF), t) local g = ImLerp(bit32.band(bit32.rshift(col_a, IM_COL32_G_SHIFT), 0xFF), bit32.band(bit32.rshift(col_b, IM_COL32_G_SHIFT), 0xFF), t) local b = ImLerp(bit32.band(bit32.rshift(col_a, IM_COL32_B_SHIFT), 0xFF), bit32.band(bit32.rshift(col_b, IM_COL32_B_SHIFT), 0xFF), t) return IM_COL32(r, g, b, 0xFF) end --- @param in_col ImU32 --- @param out_col ImVec4? --- @return ImVec4? function ImGui.ColorConvertU32ToFloat4(in_col, out_col) local s = 1.0 / 255.0 local r = bit32.band(bit32.rshift(in_col, IM_COL32_R_SHIFT), 0xFF) * s local g = bit32.band(bit32.rshift(in_col, IM_COL32_G_SHIFT), 0xFF) * s local b = bit32.band(bit32.rshift(in_col, IM_COL32_B_SHIFT), 0xFF) * s local a = bit32.band(bit32.rshift(in_col, IM_COL32_A_SHIFT), 0xFF) * s if out_col then out_col[1] = r; out_col[2] = g; out_col[3] = b; out_col[4] = a else return ImVec4(r, g, b, a) end end --- @param in_col ImVec4 --- @return ImU32 local function F32ToU8Sat(v) if v < 0.0 then v = 0.0 elseif v > 1.0 then v = 1.0 end return math.floor(v * 255.0 + 0.5) end @native function ImGui.ColorConvertFloat4ToU32(in_col) return bit32.bor(F32ToU8Sat(in_col.x), bit32.lshift(F32ToU8Sat(in_col.y), 8), bit32.lshift(F32ToU8Sat(in_col.z), 16), bit32.lshift(F32ToU8Sat(in_col.w), 24)) end --- @param r float --- @param g float --- @param b float --- @return float, float, float @native function ImGui.ColorConvertRGBtoHSV(r, g, b) local K = 0.0 if g < b then g, b = b, g K = -1.0 end if r < g then r, g = g, r K = -2.0 / 6.0 - K end local chroma = r - (g < b and g or b) local out_h = ImFabs(K + (g - b) / (6.0 * chroma + 1e-20)) local out_s = chroma / (r + 1e-20) local out_v = r return out_h, out_s, out_v end --- @param h float --- @param s float --- @param v float --- @return float, float, float @native function ImGui.ColorConvertHSVtoRGB(h, s, v) if s == 0.0 then -- gray return v, v, v end h = ImFmod(h, 1.0) / (60.0 / 360.0) local i = math.floor(h) local f = h - i local p = v * (1.0 - s) local q = v * (1.0 - s * f) local t = v * (1.0 - s * (1.0 - f)) if i == 0 then return v, t, p elseif i == 1 then return q, v, p elseif i == 2 then return p, v, t elseif i == 3 then return p, q, v elseif i == 4 then return t, p, v else -- i == 5 or default return v, p, q end end --- @param clip_rect ImRect --- @param pos ImVec2 --- @param items_height float function ImGui.CalcClipRectVisibleItemsY(clip_rect, pos, items_height) local visible_start = ImMax(ImTrunc((clip_rect.Min.y - pos.y) / items_height), 0) local visible_end = ImMax(ImTrunc(ImCeil((clip_rect.Max.y - pos.y) / items_height)), visible_start) return visible_start + 1, visible_end + 1 end --------------------------------------------------------------------------------------- -- [SECTION] STYLING --------------------------------------------------------------------------------------- function ImGui.GetStyle() IM_ASSERT(GImGui ~= nil, "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?") return GImGui.Style end do local c = ImVec4() --- @param col ImGuiCol|ImVec4|ImU32 --- @param alpha_mul? float --- @param col_is_u32? bool # must be truthy if `col` is `ImU32` --- @return ImU32 @native function ImGui.GetColorU32(col, alpha_mul, col_is_u32) if alpha_mul == nil then alpha_mul = 1.0 end local style = GImGui.Style if not col_is_u32 then --- @cast col ImVec4|ImGuiCol local w if type(col) == "number" then col = style.Colors[col] w = col.w * style.Alpha * alpha_mul else --- @cast col ImVec4 w = col.w * style.Alpha end return bit32.bor(F32ToU8Sat(col.x), bit32.lshift(F32ToU8Sat(col.y), 8), bit32.lshift(F32ToU8Sat(col.z), 16), bit32.lshift(F32ToU8Sat(w), 24)) else --- @cast col ImU32 alpha_mul = alpha_mul * style.Alpha if alpha_mul >= 1.0 then return col end local a = bit32.rshift(bit32.band(col, IM_COL32_A_MASK), IM_COL32_A_SHIFT) a = math.floor(a * alpha_mul) -- We don't need to clamp 0..255 because alpha is in 0..1 range. return bit32.bor(bit32.band(col, bit32.bnot(IM_COL32_A_MASK)), bit32.lshift(a, IM_COL32_A_SHIFT)) end end end --- @param idx ImGuiCol --- @param col ImU32|ImVec4 function ImGui.PushStyleColor(idx, col) local g = GImGui local backup = ImGuiColorMod(idx, g.Style.Colors[idx]) g.ColorStack:push_back(backup) if g.DebugFlashStyleColorIdx ~= idx then if type(col) == "number" then ImGui.ColorConvertU32ToFloat4(col, g.Style.Colors[idx]) else --- @cast col ImVec4 ImVec4_Copy(g.Style.Colors[idx], col) end end end --- @param count? int function ImGui.PopStyleColor(count) if count == nil then count = 1 end local g = GImGui if g.ColorStack.Size < count then IM_ASSERT_USER_ERROR(false, "Calling PopStyleColor() too many times!") count = g.ColorStack.Size end while count > 0 do local backup = g.ColorStack:back() ImVec4_Copy(g.Style.Colors[backup.Col], backup.BackupValue) g.ColorStack:pop_back() count = count - 1 end end local GStyleVarsInfo = { ImGuiStyleVarInfo(1, ImGuiDataType.Float, "Alpha"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "DisabledAlpha"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "WindowPadding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "WindowRounding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "WindowBorderSize"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "WindowMinSize"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "WindowTitleAlign"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "ChildRounding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "ChildBorderSize"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "PopupRounding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "PopupBorderSize"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "FramePadding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "FrameRounding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "FrameBorderSize"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "ItemSpacing"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "ItemInnerSpacing"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "IndentSpacing"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "CellPadding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "ScrollbarSize"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "ScrollbarRounding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "ScrollbarPadding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "GrabMinSize"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "GrabRounding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "ImageRounding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "ImageBorderSize"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "TabRounding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "TabBorderSize"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "TabMinWidthBase"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "TabMinWidthShrink"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "TabBarBorderSize"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "TabBarOverlineSize"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "TableAngledHeadersAngle"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "TableAngledHeadersTextAlign"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "TreeLinesSize"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "TreeLinesRounding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "MenuItemRounding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "SelectableRounding"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "DragDropTargetRounding"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "ButtonTextAlign"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "SelectableTextAlign"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "SeparatorSize"), ImGuiStyleVarInfo(1, ImGuiDataType.Float, "SeparatorTextBorderSize"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "SeparatorTextAlign"), ImGuiStyleVarInfo(2, ImGuiDataType.Float, "SeparatorTextPadding") } --- @param idx ImGuiStyleVar --- @return ImGuiStyleVarInfo function ImGui.GetStyleVarInfo(idx) IM_ASSERT(idx >= 0 and idx < ImGuiStyleVar.COUNT) -- IM_STATIC_ASSERT(IM_COUNTOF(GStyleVarsInfo) == ImGuiStyleVar_COUNT) return GStyleVarsInfo[idx + 1] end --- @param idx ImGuiStyleVar --- @param val float|ImVec2 function ImGui.PushStyleVar(idx, val) local g = GImGui local var_info = ImGui.GetStyleVarInfo(idx) if type(val) == "number" then IM_ASSERT_USER_ERROR_RET(var_info.DataType == ImGuiDataType.Float and var_info.Count == 1, "Calling PushStyleVar() with wrong type!") else -- ImVec2 IM_ASSERT_USER_ERROR_RET(var_info.DataType == ImGuiDataType.Float and var_info.Count == 2, "Calling PushStyleVar() with wrong type!") end local var = g.Style[var_info.Key] g.StyleVarStack:push_back(ImGuiStyleMod(idx, var)) if type(val) == "number" then g.Style[var_info.Key] = val else -- copy into the style's own ImVec2: storing the caller's vector would alias it into the style forever var.x = val.x; var.y = val.y end end --- @param idx ImGuiStyleVar --- @param val_x float function ImGui.PushStyleVarX(idx, val_x) local g = GImGui local var_info = ImGui.GetStyleVarInfo(idx) IM_ASSERT_USER_ERROR_RET(var_info.DataType == ImGuiDataType.Float and var_info.Count == 2, "Calling PushStyleVarX() with wrong type!") local pvar = g.Style[var_info.Key] g.StyleVarStack:push_back(ImGuiStyleMod(idx, pvar)) pvar.x = val_x end --- @param idx ImGuiStyleVar --- @param val_y float function ImGui.PushStyleVarY(idx, val_y) local g = GImGui local var_info = ImGui.GetStyleVarInfo(idx) IM_ASSERT_USER_ERROR_RET(var_info.DataType == ImGuiDataType.Float and var_info.Count == 2, "Calling PushStyleVarY() with wrong type!") local pvar = g.Style[var_info.Key] g.StyleVarStack:push_back(ImGuiStyleMod(idx, pvar)) pvar.y = val_y end --- @param count? int function ImGui.PopStyleVar(count) if count == nil then count = 1 end local g = GImGui if g.StyleVarStack.Size < count then IM_ASSERT_USER_ERROR(0, "Calling PopStyleVar() too many times!") count = g.StyleVarStack.Size end while count > 0 do local backup = g.StyleVarStack:back() local var_info = ImGui.GetStyleVarInfo(backup.VarIdx) local data = g.Style[var_info.Key] if (var_info.DataType == ImGuiDataType.Float and var_info.Count == 1) then g.Style[var_info.Key] = backup.BackupVal[1] elseif (var_info.DataType == ImGuiDataType.Float and var_info.Count == 2) then data.x = backup.BackupVal[1]; data.y = backup.BackupVal[2] end g.StyleVarStack:pop_back() count = count - 1 end end --- @param wrap_local_pos_x float function ImGui.PushTextWrapPos(wrap_local_pos_x) -- ATTENTION: THIS IS IN LEGACY LOCAL SPACE. local g = GImGui local window = g.CurrentWindow window.DC.TextWrapPosStack:push_back(window.DC.TextWrapPos) window.DC.TextWrapPos = wrap_local_pos_x end function ImGui.PopTextWrapPos() local g = GImGui local window = g.CurrentWindow IM_ASSERT_USER_ERROR_RET(window.DC.TextWrapPosStack.Size > 0, "Calling PopTextWrapPos() too many times!") window.DC.TextWrapPos = window.DC.TextWrapPosStack:back() window.DC.TextWrapPosStack:pop_back() end --- @param window ImGuiWindow --- @param popup_hierarchy bool --- @param dock_hierarchy bool local function GetCombinedRootWindow(window, popup_hierarchy, dock_hierarchy) local last_window = nil while last_window ~= window do last_window = window window = window.RootWindow if popup_hierarchy then window = window.RootWindowPopupTree end if dock_hierarchy then window = window.RootWindowDockTree end end return window end --- @param window ImGuiWindow --- @param potential_parent ImGuiWindow --- @param popup_hierarchy bool --- @param dock_hierarchy bool function ImGui.IsWindowChildOf(window, potential_parent, popup_hierarchy, dock_hierarchy) local window_root = GetCombinedRootWindow(window, popup_hierarchy, dock_hierarchy) if window_root == potential_parent then return true end while window ~= nil do if window == potential_parent then return true end if window == window_root then -- end of chain return false end window = window.ParentWindow end return false end --- @param window ImGuiWindow function ImGui.IsWindowInBeginStack(window) local g = GImGui for n = g.CurrentWindowStack.Size, 1, -1 do if g.CurrentWindowStack.Data[n].Window == window then return true end end return false end --- @param window ImGuiWindow --- @param potential_parent ImGuiWindow function ImGui.IsWindowWithinBeginStackOf(window, potential_parent) if window.RootWindow == potential_parent then return true end while window ~= nil do if window == potential_parent then return true end window = window.ParentWindowInBeginStack end return false end --- @param potential_above ImGuiWindow --- @param potential_below ImGuiWindow --- @return bool function ImGui.IsWindowAbove(potential_above, potential_below) local g = GImGui -- It would be saner to ensure that display layer is always reflected in the g.Windows order, which would likely requires altering all manipulations of that array local display_layer_delta = GetWindowDisplayLayer(potential_above) - GetWindowDisplayLayer(potential_below) if display_layer_delta ~= 0 then return display_layer_delta > 0 end for i = g.Windows.Size, 1, -1 do local candidate_window = g.Windows.Data[i] if candidate_window == potential_above then return true end if candidate_window == potential_below then return false end end return false end -- Is current window hovered and hoverable (e.g. not blocked by a popup/modal)? See ImGuiHoveredFlags for options. --- @param flags? ImGuiHoveredFlags function ImGui.IsWindowHovered(flags) if flags == nil then flags = 0 end local g = GImGui IM_ASSERT_USER_ERROR(bit32.band(flags, bit32.bnot(ImGuiHoveredFlags.AllowedMaskForIsWindowHovered)) == 0, "Invalid flags for IsWindowHovered()!") local ref_window = g.HoveredWindow local cur_window = g.CurrentWindow if ref_window == nil then return false end if bit32.band(flags, ImGuiHoveredFlags.AnyWindow) == 0 then IM_ASSERT(cur_window) -- Not inside a Begin()/End() local popup_hierarchy = bit32.band(flags, ImGuiHoveredFlags.NoPopupHierarchy) == 0 local dock_hierarchy = bit32.band(flags, ImGuiHoveredFlags.DockHierarchy) ~= 0 if bit32.band(flags, ImGuiHoveredFlags.RootWindow) ~= 0 then cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy, dock_hierarchy) end local result if bit32.band(flags, ImGuiHoveredFlags.ChildWindows) ~= 0 then result = ImGui.IsWindowChildOf(ref_window, cur_window, popup_hierarchy, dock_hierarchy) else result = (ref_window == cur_window) end if not result then return false end end if not ImGui.IsWindowContentHoverable(ref_window, flags) then return false end if bit32.band(flags, ImGuiHoveredFlags.AllowWhenBlockedByActiveItem) == 0 then if g.ActiveId ~= 0 and not g.ActiveIdAllowOverlap and g.ActiveId ~= ref_window.MoveId then return false end end if bit32.band(flags, ImGuiHoveredFlags.ForTooltip) ~= 0 then flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipMouse) end if bit32.band(flags, ImGuiHoveredFlags.Stationary) ~= 0 and g.HoverWindowUnlockedStationaryId ~= ref_window.ID then return false end return true end -- Similar to IsWindowHovered() --- @param flags? ImGuiFocusedFlags function ImGui.IsWindowFocused(flags) if flags == nil then flags = 0 end local g = GImGui local ref_window = g.NavWindow local cur_window = g.CurrentWindow if ref_window == nil then return false end if bit32.band(flags, ImGuiFocusedFlags.AnyWindow) ~= 0 then return true end IM_ASSERT(cur_window) -- Not inside a Begin()/End() local popup_hierarchy = bit32.band(flags, ImGuiFocusedFlags.NoPopupHierarchy) == 0 local dock_hierarchy = bit32.band(flags, ImGuiFocusedFlags.DockHierarchy) ~= 0 if bit32.band(flags, ImGuiFocusedFlags.RootWindow) ~= 0 then cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy, dock_hierarchy) end if bit32.band(flags, ImGuiFocusedFlags.ChildWindows) ~= 0 then return ImGui.IsWindowChildOf(ref_window, cur_window, popup_hierarchy, dock_hierarchy) else return ref_window == cur_window end end --- @param window ImGuiWindow --- @param scale float local function ScaleWindow(window, scale) local origin = window.Viewport.Pos ImVec2_Copy(window.Pos, ImFloor((window.Pos - origin) * scale + origin)) ImVec2_Copy(window.Size, ImTrunc(window.Size * scale)) ImVec2_Copy(window.SizeFull, ImTrunc(window.SizeFull * scale)) ImVec2_Copy(window.ContentSize, ImTrunc(window.ContentSize * scale)) end --- @param viewport ImGuiViewportP --- @param scale float function ImGui.ScaleWindowsInViewport(viewport, scale) local g = GImGui -- IMGUI_DEBUG_LOG_VIEWPORT("[viewport] ScaleWindowsInViewport 0x%08X", viewport.ID) if viewport.Window then ScaleWindow(viewport.Window, scale) else for _, window in g.Windows:iter() do if window.Viewport == viewport then ScaleWindow(window, scale) end end end end --------------------------------------------------------------------------------------- -- [SECTION] SCROLLING --------------------------------------------------------------------------------------- --- @param target float --- @param snap_min float --- @param snap_max float --- @param snap_threshold float --- @param center_ratio float local function CalcScrollEdgeSnap(target, snap_min, snap_max, snap_threshold, center_ratio) if target <= snap_min + snap_threshold then return ImLerp(snap_min, target, center_ratio) end if target >= snap_max - snap_threshold then return ImLerp(target, snap_max, center_ratio) end return target end --- @param window ImGuiWindow function CalcNextScrollFromScrollTargetAndClamp(window) local scroll = ImVec2() ImVec2_Copy(scroll, window.Scroll) local decoration_size = ImVec2(window.DecoOuterSizeX1 + window.DecoInnerSizeX1 + window.DecoOuterSizeX2, window.DecoOuterSizeY1 + window.DecoInnerSizeY1 + window.DecoOuterSizeY2) for axis = ImGuiAxis.X, ImGuiAxis.Y do if window.ScrollTarget[axis] < FLT_MAX then local center_ratio = window.ScrollTargetCenterRatio[axis] local scroll_target = window.ScrollTarget[axis] if window.ScrollTargetEdgeSnapDist[axis] > 0.0 then local snap_min = 0.0 local snap_max = window.ScrollMax[axis] + window.SizeFull[axis] - decoration_size[axis] scroll_target = CalcScrollEdgeSnap(scroll_target, snap_min, snap_max, window.ScrollTargetEdgeSnapDist[axis], center_ratio) end scroll[axis] = scroll_target - center_ratio * (window.SizeFull[axis] - decoration_size[axis]) end scroll[axis] = ImRound64(ImMax(scroll[axis], 0.0)) if not window.Collapsed and not window.SkipItems then scroll[axis] = ImMin(scroll[axis], window.ScrollMax[axis]) end end return scroll end --- @param window ImGuiWindow --- @param item_rect ImRect --- @param flags? ImGuiScrollFlags function ImGui.ScrollToRectEx(window, item_rect, flags) if flags == nil then flags = 0 end local g = GImGui local scroll_rect = ImRect(window.InnerRect.Min - ImVec2(1, 1), window.InnerRect.Max + ImVec2(1, 1)) scroll_rect.Min.x = ImMin(scroll_rect.Min.x + window.DecoInnerSizeX1, scroll_rect.Max.x) scroll_rect.Min.y = ImMin(scroll_rect.Min.y + window.DecoInnerSizeY1, scroll_rect.Max.y) IM_ASSERT(bit32.band(flags, ImGuiScrollFlags.MaskX_) == 0 or ImIsPowerOfTwo(bit32.band(flags, ImGuiScrollFlags.MaskX_))) IM_ASSERT(bit32.band(flags, ImGuiScrollFlags.MaskY_) == 0 or ImIsPowerOfTwo(bit32.band(flags, ImGuiScrollFlags.MaskY_))) local in_flags = flags if bit32.band(flags, ImGuiScrollFlags.MaskX_) == 0 and window.ScrollbarX then flags = bit32.bor(flags, ImGuiScrollFlags.KeepVisibleEdgeX) end if bit32.band(flags, ImGuiScrollFlags.MaskY_) == 0 then flags = bit32.bor(flags, window.Appearing and ImGuiScrollFlags.AlwaysCenterY or ImGuiScrollFlags.KeepVisibleEdgeY) end local fully_visible_x = item_rect.Min.x >= scroll_rect.Min.x and item_rect.Max.x <= scroll_rect.Max.x local fully_visible_y = item_rect.Min.y >= scroll_rect.Min.y and item_rect.Max.y <= scroll_rect.Max.y local can_be_fully_visible_x = (item_rect:GetWidth() + g.Style.ItemSpacing.x * 2.0) <= scroll_rect:GetWidth() or (window.AutoFitFramesX > 0) or bit32.band(window.Flags, ImGuiWindowFlags.AlwaysAutoResize) ~= 0 local can_be_fully_visible_y = (item_rect:GetHeight() + g.Style.ItemSpacing.y * 2.0) <= scroll_rect:GetHeight() or (window.AutoFitFramesY > 0) or bit32.band(window.Flags, ImGuiWindowFlags.AlwaysAutoResize) ~= 0 if bit32.band(flags, ImGuiScrollFlags.KeepVisibleEdgeX) ~= 0 and not fully_visible_x then if item_rect.Min.x < scroll_rect.Min.x or not can_be_fully_visible_x then ImGui.SetScrollFromPosX(window, item_rect.Min.x - g.Style.ItemSpacing.x - window.Pos.x, 0.0) elseif item_rect.Max.x >= scroll_rect.Max.x then ImGui.SetScrollFromPosX(window, item_rect.Max.x + g.Style.ItemSpacing.x - window.Pos.x, 1.0) end elseif (bit32.band(flags, ImGuiScrollFlags.KeepVisibleCenterX) ~= 0 and not fully_visible_x) or bit32.band(flags, ImGuiScrollFlags.AlwaysCenterX) ~= 0 then if can_be_fully_visible_x then ImGui.SetScrollFromPosX(window, ImTrunc((item_rect.Min.x + item_rect.Max.x) * 0.5) - window.Pos.x, 0.5) else ImGui.SetScrollFromPosX(window, item_rect.Min.x - window.Pos.x, 0.0) end end if bit32.band(flags, ImGuiScrollFlags.KeepVisibleEdgeY) ~= 0 and not fully_visible_y then if item_rect.Min.y < scroll_rect.Min.y or not can_be_fully_visible_y then ImGui.SetScrollFromPosY(window, item_rect.Min.y - g.Style.ItemSpacing.y - window.Pos.y, 0.0) elseif item_rect.Max.y >= scroll_rect.Max.y then ImGui.SetScrollFromPosY(window, item_rect.Max.y + g.Style.ItemSpacing.y - window.Pos.y, 1.0) end elseif (bit32.band(flags, ImGuiScrollFlags.KeepVisibleCenterY) ~= 0 and not fully_visible_y) or bit32.band(flags, ImGuiScrollFlags.AlwaysCenterY) ~= 0 then if can_be_fully_visible_y then ImGui.SetScrollFromPosY(window, ImTrunc((item_rect.Min.y + item_rect.Max.y) * 0.5) - window.Pos.y, 0.5) else ImGui.SetScrollFromPosY(window, item_rect.Min.y - window.Pos.y, 0.0) end end local next_scroll = CalcNextScrollFromScrollTargetAndClamp(window) local delta_scroll = next_scroll - window.Scroll if bit32.band(flags, ImGuiScrollFlags.NoScrollParent) == 0 and bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 then if bit32.band(in_flags, bit32.bor(ImGuiScrollFlags.AlwaysCenterX, ImGuiScrollFlags.KeepVisibleCenterX)) ~= 0 then in_flags = bit32.bor(bit32.band(in_flags, bit32.bnot(ImGuiScrollFlags.MaskX_)), ImGuiScrollFlags.KeepVisibleEdgeX) end if bit32.band(in_flags, bit32.bor(ImGuiScrollFlags.AlwaysCenterY, ImGuiScrollFlags.KeepVisibleCenterY)) ~= 0 then in_flags = bit32.bor(bit32.band(in_flags, bit32.bnot(ImGuiScrollFlags.MaskY_)), ImGuiScrollFlags.KeepVisibleEdgeY) end local parent_delta = ImGui.ScrollToRectEx(window.ParentWindow, ImRect(item_rect.Min - delta_scroll, item_rect.Max - delta_scroll), in_flags) delta_scroll = delta_scroll + parent_delta end return delta_scroll end --- @return float function ImGui.GetScrollMaxY() local window = GImGui.CurrentWindow return window.ScrollMax.y end function ImGui.GetScrollMaxX() return GImGui.CurrentWindow.ScrollMax.x end function ImGui.GetScrollX() return GImGui.CurrentWindow.Scroll.x end function ImGui.GetScrollY() return GImGui.CurrentWindow.Scroll.y end --- @param flags? ImGuiScrollFlags function ImGui.ScrollToItem(flags) local g = GImGui ImGui.ScrollToRectEx(g.CurrentWindow, g.LastItemData.NavRect, flags or 0) end --- @param window ImGuiWindow --- @param item_rect ImRect --- @param flags? ImGuiScrollFlags function ImGui.ScrollToRect(window, item_rect, flags) ImGui.ScrollToRectEx(window, item_rect, flags or 0) end --- @param center_x_ratio? float function ImGui.SetScrollHereX(center_x_ratio) if center_x_ratio == nil then center_x_ratio = 0.5 end local g = GImGui local window = g.CurrentWindow local spacing_x = ImMax(window.WindowPadding.x, g.Style.ItemSpacing.x) local target_pos_x = ImLerp(g.LastItemData.Rect.Min.x - spacing_x, g.LastItemData.Rect.Max.x + spacing_x, center_x_ratio) ImGui.SetScrollFromPosX(window, target_pos_x - window.Pos.x, center_x_ratio) window.ScrollTargetEdgeSnapDist.x = ImMax(0.0, window.WindowPadding.x - spacing_x) end --- @param center_y_ratio? float function ImGui.SetScrollHereY(center_y_ratio) if center_y_ratio == nil then center_y_ratio = 0.5 end local g = GImGui local window = g.CurrentWindow local spacing_y = ImMax(window.WindowPadding.y, g.Style.ItemSpacing.y) local target_pos_y = ImLerp(window.DC.CursorPosPrevLine.y - spacing_y, window.DC.CursorPosPrevLine.y + window.DC.PrevLineSize.y + spacing_y, center_y_ratio) ImGui.SetScrollFromPosY(window, target_pos_y - window.Pos.y, center_y_ratio) window.ScrollTargetEdgeSnapDist.y = ImMax(0.0, window.WindowPadding.y - spacing_y) end -- Overloads: SetScrollX(scroll_x) / SetScrollX(window, scroll_x) --- @param window ImGuiWindow|float --- @param scroll_x float? function ImGui.SetScrollX(window, scroll_x) if type(window) == "number" then window, scroll_x = GImGui.CurrentWindow, window end window.ScrollTarget.x = scroll_x window.ScrollTargetCenterRatio.x = 0.0 window.ScrollTargetEdgeSnapDist.x = 0.0 end --- @param window ImGuiWindow --- @param scroll_y float function ImGui.SetScrollY(window, scroll_y) if type(window) == "number" then window, scroll_y = GImGui.CurrentWindow, window end window.ScrollTarget.y = scroll_y window.ScrollTargetCenterRatio.y = 0.0 window.ScrollTargetEdgeSnapDist.y = 0.0 end --- @param window ImGuiWindow --- @param local_x float --- @param center_x_ratio float function ImGui.SetScrollFromPosX(window, local_x, center_x_ratio) if type(window) == "number" then window, local_x, center_x_ratio = GImGui.CurrentWindow, window, local_x end if center_x_ratio == nil then center_x_ratio = 0.5 end IM_ASSERT(center_x_ratio >= 0.0 and center_x_ratio <= 1.0) window.ScrollTarget.x = IM_TRUNC(local_x - window.DecoOuterSizeX1 - window.DecoInnerSizeX1 + window.Scroll.x) window.ScrollTargetCenterRatio.x = center_x_ratio window.ScrollTargetEdgeSnapDist.x = 0.0 end --- @param window ImGuiWindow --- @param local_y float --- @param center_y_ratio float function ImGui.SetScrollFromPosY(window, local_y, center_y_ratio) if type(window) == "number" then window, local_y, center_y_ratio = GImGui.CurrentWindow, window, local_y end if center_y_ratio == nil then center_y_ratio = 0.5 end IM_ASSERT(center_y_ratio >= 0.0 and center_y_ratio <= 1.0) window.ScrollTarget.y = IM_TRUNC(local_y - window.DecoOuterSizeY1 - window.DecoInnerSizeY1 + window.Scroll.y) window.ScrollTargetCenterRatio.y = center_y_ratio window.ScrollTargetEdgeSnapDist.y = 0.0 end --------------------------------------------------------------------------------------- -- [SECTION] TOOLTIPS --------------------------------------------------------------------------------------- --- @param tooltip_flags ImGuiTooltipFlags --- @param extra_window_flags ImGuiWindowFlags function ImGui.BeginTooltipEx(tooltip_flags, extra_window_flags) local g = GImGui local is_dragdrop_tooltip = g.DragDropWithinSource or g.DragDropWithinTarget if is_dragdrop_tooltip then local is_touchscreen = (g.IO.MouseSource == ImGuiMouseSource.TouchScreen) if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasPos) == 0 then local tooltip_pos if is_touchscreen then tooltip_pos = g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_TOUCH * g.Style.MouseCursorScale else tooltip_pos = g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_MOUSE * g.Style.MouseCursorScale end local tooltip_pivot = is_touchscreen and TOOLTIP_DEFAULT_PIVOT_TOUCH or ImVec2(0.0, 0.0) ImGui.SetNextWindowPos(tooltip_pos, ImGuiCond.None, tooltip_pivot) end local bg_alpha = g.Style.Colors[ImGuiCol.PopupBg].w * 0.60 ImGui.SetNextWindowBgAlpha(bg_alpha) tooltip_flags = bit32.bor(tooltip_flags, ImGuiTooltipFlags.OverridePrevious) end if (bit32.band(tooltip_flags, ImGuiTooltipFlags.OverridePrevious) ~= 0) and g.TooltipPreviousWindow ~= nil and g.TooltipPreviousWindow.Active and not ImGui.IsWindowInBeginStack(g.TooltipPreviousWindow) then -- IMGUI_DEBUG_LOG("[tooltip] '%s' already active, using +1 for this frame\n", window_name) ImGui.SetWindowHiddenAndSkipItemsForCurrentFrame(g.TooltipPreviousWindow) g.TooltipOverrideCount = g.TooltipOverrideCount + 1 end local window_name_template = is_dragdrop_tooltip and "##Tooltip_DragDrop_%02d" or "##Tooltip_%02d" local window_name = ImFormatString(window_name_template, g.TooltipOverrideCount) local flags = bit32.bor(ImGuiWindowFlags.Tooltip, ImGuiWindowFlags.NoInputs, ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoMove, ImGuiWindowFlags.NoResize, ImGuiWindowFlags.NoSavedSettings, ImGuiWindowFlags.AlwaysAutoResize) ImGui.Begin(window_name, nil, bit32.bor(flags, extra_window_flags)) return true end function ImGui.BeginTooltip() return ImGui.BeginTooltipEx(ImGuiTooltipFlags.None, ImGuiWindowFlags.None) end function ImGui.BeginItemTooltip() if not ImGui.IsItemHovered(ImGuiHoveredFlags.ForTooltip) then return false end return ImGui.BeginTooltipEx(ImGuiTooltipFlags.None, ImGuiWindowFlags.None) end function ImGui.EndTooltip() IM_ASSERT(bit32.band(ImGui.GetCurrentWindowRead().Flags, ImGuiWindowFlags.Tooltip) ~= 0) -- Mismatched BeginTooltip()/EndTooltip() calls ImGui.End() end --- @param fmt string --- @param ... any function ImGui.SetTooltip(fmt, ...) if not ImGui.BeginTooltipEx(ImGuiTooltipFlags.OverridePrevious, ImGuiWindowFlags.None) then return end ImGui.TextV(fmt, ...) ImGui.EndTooltip() end --- @param fmt string --- @param ... any function ImGui.SetItemTooltip(fmt, ...) if ImGui.IsItemHovered(ImGuiHoveredFlags.ForTooltip) then ImGui.SetTooltip(fmt, ...) end end --------------------------------------------------------------------------------------- -- [SECTION] POPUPS --------------------------------------------------------------------------------------- --- @param window? ImGuiWindow function ImGui.FindBlockingModal(window) local g = GImGui if g.OpenPopupStack.Size <= 0 then return nil end for i = 1, g.OpenPopupStack.Size do repeat local popup_data = g.OpenPopupStack.Data[i] local popup_window = popup_data.Window if popup_window == nil or bit32.band(popup_window.Flags, ImGuiWindowFlags.Modal) == 0 then break end if not popup_window.Active and not popup_window.WasActive then break end if window == nil then return popup_window end if ImGui.IsWindowWithinBeginStackOf(window, popup_window) then break end do return popup_window end until true end return nil end --- @param id ImGuiID|string --- @param popup_flags? ImGuiPopupFlags function ImGui.OpenPopup(id, popup_flags) if popup_flags == nil then popup_flags = 0 end if type(id) == "number" then return ImGui.OpenPopupEx(id, popup_flags) else --- @cast id string local g = GImGui local _id = g.CurrentWindow:GetID(id) IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopup(\"%s\" -> 0x%08X)", id, _id) return ImGui.OpenPopupEx(_id, popup_flags) end end --- @param id ImGuiID --- @param popup_flags ImGuiPopupFlags function ImGui.OpenPopupEx(id, popup_flags) local g = GImGui local parent_window = g.CurrentWindow local current_stack_size = g.BeginPopupStack.Size if bit32.band(popup_flags, ImGuiPopupFlags.NoOpenOverExistingPopup) ~= 0 then if ImGui.IsPopupOpen(0, ImGuiPopupFlags.AnyPopupId) then return false end end local popup_ref = ImGuiPopupData() popup_ref.PopupId = id popup_ref.Window = nil popup_ref.RestoreNavWindow = g.NavWindow -- When popup closes focus may be restored to NavWindow (depend on window type). popup_ref.OpenFrameCount = g.FrameCount popup_ref.OpenParentId = parent_window.IDStack:back() ImVec2_Copy(popup_ref.OpenPopupPos, ImGui.NavCalcPreferredRefPos(ImGuiWindowFlags.Popup)) if ImGui.IsMousePosValid(g.IO.MousePos) then ImVec2_Copy(popup_ref.OpenMousePos, g.IO.MousePos) else ImVec2_Copy(popup_ref.OpenMousePos, popup_ref.OpenPopupPos) end -- IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopupEx(0x%08X)", id) if g.OpenPopupStack.Size < current_stack_size + 1 then g.OpenPopupStack:push_back(popup_ref) return true else -- Gently handle the user mistakenly calling OpenPopup() every frames: it is likely a programming mistake! -- However, if we were to run the regular code path, the ui would become completely unusable because the popup will always be -- in hidden-while-calculating-size state _while_ claiming focus. Which is extremely confusing situation for the programmer. -- Instead, for successive frames calls to OpenPopup(), we silently avoid reopening even if ImGuiPopupFlags.NoReopen is not specified. local keep_existing = false if g.OpenPopupStack.Data[current_stack_size + 1].PopupId == id then if (g.OpenPopupStack.Data[current_stack_size + 1].OpenFrameCount == g.FrameCount - 1) or (bit32.band(popup_flags, ImGuiPopupFlags.NoReopen) ~= 0) then keep_existing = true end end if keep_existing then -- No reopen g.OpenPopupStack.Data[current_stack_size + 1].OpenFrameCount = popup_ref.OpenFrameCount else -- Reopen: close child popups if any, then flag popup for open/reopen (set position, focus, init navigation) ImGui.ClosePopupToLevel(current_stack_size, true) g.OpenPopupStack:push_back(popup_ref) end -- When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow(). -- This is equivalent to what ClosePopupToLevel() does. -- if (g.OpenPopupStack[current_stack_size + 1].PopupId == id) -- FocusWindow(parent_window); return not keep_existing end end -- When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. -- This function closes any popups that are over 'ref_window'. --- @param ref_window? ImGuiWindow --- @param restore_focus_to_window_under_popup bool function ImGui.ClosePopupsOverWindow(ref_window, restore_focus_to_window_under_popup) local g = GImGui if g.OpenPopupStack.Size == 0 then return end -- Don't close our own child popup windows. -- IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupsOverWindow(\"%s\") restore_under=%d", ref_window and ref_window.Name or "", restore_focus_to_window_under_popup) local popup_count_to_keep = 0 if ref_window then -- Find the highest popup which is a descendant of the reference window (generally reference window = NavWindow) local ref_window_is_descendent_of_popup local stop_8041 = false while popup_count_to_keep < g.OpenPopupStack.Size do repeat local popup = g.OpenPopupStack.Data[popup_count_to_keep + 1] if not popup.Window then break end IM_ASSERT(bit32.band(popup.Window.Flags, ImGuiWindowFlags.Popup) ~= 0) -- Trim the stack unless the popup is a direct parent of the reference window (the reference window is often the NavWindow) -- - Clicking/Focusing Window2 won't close Popup1: -- Window -> Popup1 -> Window2(Ref) -- - Clicking/focusing Popup1 will close Popup2 and Popup3: -- Window -> Popup1(Ref) -> Popup2 -> Popup3 -- - Each popups may contain child windows, which is why we compare ->RootWindow! -- Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child -- We step through every popup from bottom to top to validate their position relative to reference window. ref_window_is_descendent_of_popup = false for n = popup_count_to_keep, g.OpenPopupStack.Size - 1 do local popup_window = g.OpenPopupStack.Data[n + 1].Window if popup_window and ImGui.IsWindowWithinBeginStackOf(ref_window, popup_window) then ref_window_is_descendent_of_popup = true break end end if not ref_window_is_descendent_of_popup then stop_8041 = true; break end until true if stop_8041 then break end popup_count_to_keep = popup_count_to_keep + 1 end end if popup_count_to_keep < g.OpenPopupStack.Size then -- This test is not required but it allows to set a convenient breakpoint on the statement below -- IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupsOverWindow(\"%s\")", ref_window and ref_window.Name or "") ImGui.ClosePopupToLevel(popup_count_to_keep, restore_focus_to_window_under_popup) end end --- @param remaining int # 0-based --- @param restore_focus_to_window_under_popup bool function ImGui.ClosePopupToLevel(remaining, restore_focus_to_window_under_popup) local g = GImGui -- IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupToLevel(%d), restore_under=%d", remaining, restore_focus_to_window_under_popup) IM_ASSERT(remaining >= 0 and remaining < g.OpenPopupStack.Size) -- if bit32.band(g.DebugLogFlags, ImGuiDebugLogFlags.EventPopup) ~= 0 then -- for n = remaining + 1, g.OpenPopupStack.Size do -- local popup = g.OpenPopupStack.Data[n] -- IMGUI_DEBUG_LOG_POPUP("[popup] - Closing PopupID 0x%08X Window \"%s\"", popup.PopupId, popup.Window and popup.Window.Name or nil) -- end -- end -- Trim open popup stack -- FIXME: is it safer to full copy here? local prev_popup = g.OpenPopupStack.Data[remaining + 1] g.OpenPopupStack:resize(remaining) -- Restore focus (unless popup window was not yet submitted, and didn't have a chance to take focus anyhow. See #7325 for an edge case) if restore_focus_to_window_under_popup and prev_popup.Window then local popup_window = prev_popup.Window local focus_window if bit32.band(popup_window.Flags, ImGuiWindowFlags.ChildMenu) ~= 0 then focus_window = popup_window.ParentWindow else focus_window = prev_popup.RestoreNavWindow end if focus_window and not focus_window.WasActive then ImGui.FocusTopMostWindowUnderOne(popup_window, nil, nil, ImGuiFocusRequestFlags.RestoreFocusedChild) -- Fallback else local focus_flags = (g.NavLayer == ImGuiNavLayer.Main) and ImGuiFocusRequestFlags.RestoreFocusedChild or ImGuiFocusRequestFlags.None ImGui.FocusWindow(focus_window, focus_flags) end end end -- Close the popup we have Begin-ed into function ImGui.CloseCurrentPopup() local g = GImGui local popup_idx = g.BeginPopupStack.Size if popup_idx < 1 or popup_idx > g.OpenPopupStack.Size or g.BeginPopupStack.Data[popup_idx].PopupId ~= g.OpenPopupStack.Data[popup_idx].PopupId then return end -- Closing a menu closes its top-most parent popup (unless a modal) while popup_idx > 1 do local popup_window = g.OpenPopupStack.Data[popup_idx].Window local parent_popup_window = g.OpenPopupStack.Data[popup_idx - 1].Window local close_parent = false if popup_window and bit32.band(popup_window.Flags, ImGuiWindowFlags.ChildMenu) ~= 0 then if parent_popup_window and not (bit32.band(parent_popup_window.Flags, ImGuiWindowFlags.MenuBar) ~= 0) then close_parent = true end end if not close_parent then break end popup_idx = popup_idx - 1 end -- IMGUI_DEBUG_LOG_POPUP("[popup] CloseCurrentPopup %d -> %d\n", g.BeginPopupStack.Size, popup_idx) ImGui.ClosePopupToLevel(popup_idx - 1, true) -- A common pattern is to close a popup when selecting a menu item/selectable that will open another window. -- To improve this usage pattern, we avoid nav highlight for a single frame in the parent window. -- Similarly, we could avoid mouse hover highlight in this window but it is less visually problematic. local window = g.NavWindow if window then window.DC.NavHideHighlightOneFrame = true end end --- @param id ImGuiID --- @param extra_window_flags ImGuiWindowFlags function ImGui.BeginPopupEx(id, extra_window_flags) local g = GImGui if not ImGui.IsPopupOpen(id, ImGuiPopupFlags.None) then g.NextWindowData:ClearFlags() return false end IM_ASSERT(bit32.band(extra_window_flags, ImGuiWindowFlags.ChildMenu) == 0) -- Use BeginPopupMenuEx() local name = ImFormatString("##Popup_%08x", id) -- No recycling, so we can close/open during the same frame local is_open _, is_open = ImGui.Begin(name, nil, bit32.bor(extra_window_flags, ImGuiWindowFlags.Popup, ImGuiWindowFlags.NoDocking)) if not is_open then -- NB: Begin can return false when the popup is completely clipped (e.g. zero size display) ImGui.EndPopup() end -- g.CurrentWindow.FocusRouteParentWindow = g.CurrentWindow.ParentWindowInBeginStack return is_open end --- @param id ImGuiID --- @param label any --- @param extra_window_flags ImGuiWindowFlags function ImGui.BeginPopupMenuEx(id, label, extra_window_flags) local g = GImGui if not ImGui.IsPopupOpen(id, ImGuiPopupFlags.None) then g.NextWindowData:ClearFlags() return false end if bit32.band(extra_window_flags, ImGuiWindowFlags.ChildWindow) ~= 0 and bit32.band(extra_window_flags, ImGuiWindowFlags.AlwaysAutoResize) ~= 0 then ImGui.SetNextWindowChildFlags(ImGuiChildFlags.AlwaysAutoResize, true) end IM_ASSERT(bit32.band(extra_window_flags, ImGuiWindowFlags.ChildMenu) ~= 0) local name = ImFormatString("%s###Menu_%02d", label, g.BeginMenuDepth) local is_open _, is_open = ImGui.Begin(name, nil, bit32.bor(extra_window_flags, ImGuiWindowFlags.Popup)) if not is_open then ImGui.EndPopup() end return is_open end --- @param str_id string --- @param flags? ImGuiWindowFlags function ImGui.BeginPopup(str_id, flags) if flags == nil then flags = 0 end local g = GImGui if g.OpenPopupStack.Size <= g.BeginPopupStack.Size then -- Early out for performance g.NextWindowData:ClearFlags() -- We behave like Begin() and need to consume those values return false end flags = bit32.bor(flags, ImGuiWindowFlags.AlwaysAutoResize, ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoSavedSettings) local id = g.CurrentWindow:GetID(str_id) return ImGui.BeginPopupEx(id, flags) end function ImGui.EndPopup() local g = GImGui local window = g.CurrentWindow IM_ASSERT_USER_ERROR_RET((bit32.band(window.Flags, ImGuiWindowFlags.Popup) ~= 0) and (g.BeginPopupStack.Size > 0), "Calling EndPopup() in wrong window!") -- Make all menus and popups wrap around for now, may need to expose that policy (e.g. focus scope could include wrap/loop policy flags used by new move requests) if g.NavWindow == window then ImGui.NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags.LoopY) end -- Child-popups don't need to be laid out local backup_within_end_popup_id = g.WithinEndPopupID local backup_within_end_child_id = g.WithinEndChildID g.WithinEndPopupID = window.ID if bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 then g.WithinEndChildID = window.ID end ImGui.End() g.WithinEndPopupID = backup_within_end_popup_id g.WithinEndChildID = backup_within_end_child_id end --- @param ref_pos ImVec2 --- @param size ImVec2 --- @param last_dir ImGuiDir --- @param r_outer ImRect --- @param r_avoid ImRect --- @param policy ImGuiPopupPositionPolicy --- @return ImVec2 # Best Window Pos For Popup --- @return int # Updated last_dir --- @nodiscard function ImGui.FindBestWindowPosForPopupEx(ref_pos, size, last_dir, r_outer, r_avoid, policy) local base_pos_clamped -- Combo Box policy (we want a connecting edge) if policy == ImGuiPopupPositionPolicy.ComboBox then local dir_preferred_order = {ImGuiDir.Down, ImGuiDir.Right, ImGuiDir.Left, ImGuiDir.Up} for n = (last_dir ~= ImGuiDir.None) and 0 or 1, ImGuiDir.COUNT do repeat local dir = (n == 0) and last_dir or dir_preferred_order[n] if n ~= 0 and dir == last_dir then -- Already tried this direction? break end local pos if dir == ImGuiDir.Down then pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y) -- Below, Toward Right (default) elseif dir == ImGuiDir.Right then pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y) -- Above, Toward Right elseif dir == ImGuiDir.Left then pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y) -- Below, Toward Left elseif dir == ImGuiDir.Up then pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y) -- Above, Toward Left end if not r_outer:Contains(ImRect(pos, pos + size)) then break end last_dir = dir do return pos, last_dir end until true end end -- Tooltip and Default popup policy -- (Always first try the direction we used on the last frame, if any) if policy == ImGuiPopupPositionPolicy.Tooltip or policy == ImGuiPopupPositionPolicy.Default then local dir_preferred_order = {ImGuiDir.Right, ImGuiDir.Down, ImGuiDir.Up, ImGuiDir.Left} for n = (last_dir ~= ImGuiDir.None) and 0 or 1, ImGuiDir.COUNT do repeat local dir = (n == 0) and last_dir or dir_preferred_order[n] if n ~= 0 and dir == last_dir then break end local avail_w = ((dir == ImGuiDir.Left) and r_avoid.Min.x or r_outer.Max.x) - ((dir == ImGuiDir.Right) and r_avoid.Max.x or r_outer.Min.x) local avail_h = ((dir == ImGuiDir.Up) and r_avoid.Min.y or r_outer.Max.y) - ((dir == ImGuiDir.Down) and r_avoid.Max.y or r_outer.Min.y) -- If there's not enough room on one axis, there's no point in positioning on a side on this axis (e.g. when not enough width, use a top/bottom position to maximize available width) if avail_w < size.x and (dir == ImGuiDir.Left or dir == ImGuiDir.Right) then break end if avail_h < size.y and (dir == ImGuiDir.Up or dir == ImGuiDir.Down) then break end base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size) local pos = ImVec2() pos.x = (dir == ImGuiDir.Left) and (r_avoid.Min.x - size.x) or ((dir == ImGuiDir.Right) and r_avoid.Max.x or base_pos_clamped.x) pos.y = (dir == ImGuiDir.Up) and (r_avoid.Min.y - size.y) or ((dir == ImGuiDir.Down) and r_avoid.Max.y or base_pos_clamped.y) -- Clamp top-left corner of popup pos.x = ImMax(pos.x, r_outer.Min.x) pos.y = ImMax(pos.y, r_outer.Min.y) last_dir = dir do return pos, last_dir end until true end end -- Fallback when not enough room: last_dir = ImGuiDir.None -- For tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible. if policy == ImGuiPopupPositionPolicy.Tooltip then return ref_pos + ImVec2(2, 2), last_dir end -- Otherwise try to keep within display local pos = ImVec2(ref_pos.x, ref_pos.y) pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x) pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y) return pos, last_dir end --- @param window ImGuiWindow --- @return ImRect --- @nodiscard function ImGui.GetPopupAllowedExtentRect(window) local g = GImGui local r_screen = ImRect() if window.ViewportAllowPlatformMonitorExtend >= 1 then -- Extent with be in the frame of reference of the given viewport (so Min is likely to be negative here) local monitor = g.PlatformIO.Monitors.Data[window.ViewportAllowPlatformMonitorExtend] ImVec2_Copy(r_screen.Min, monitor.WorkPos) ImVec2_Copy(r_screen.Max, monitor.WorkPos + monitor.WorkSize) else -- Use the full viewport area (not work area) for popups r_screen = window.Viewport:GetMainRect() end local padding = g.Style.DisplaySafeAreaPadding r_screen:Expand(ImVec2((r_screen:GetWidth() > padding.x * 2) and -padding.x or 0.0, (r_screen:GetHeight() > padding.y * 2) and -padding.y or 0.0)) return r_screen end --- @param window ImGuiWindow --- @return ImVec2 --- @nodiscard function ImGui.FindBestWindowPosForPopup(window) local g = GImGui local r_outer = ImGui.GetPopupAllowedExtentRect(window) if bit32.band(window.Flags, ImGuiWindowFlags.ChildMenu) ~= 0 then -- Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds. -- This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. local parent_window = window.ParentWindow local horizontal_overlap = g.Style.ItemInnerSpacing.x -- We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x). local r_avoid if parent_window.DC.MenuBarAppending then r_avoid = ImRect(-FLT_MAX, parent_window.ClipRect.Min.y, FLT_MAX, parent_window.ClipRect.Max.y) -- Avoid parent menu-bar. If we wanted multi-line menu-bar, we may instead want to have the calling window setup e.g. a NextWindowData.PosConstraintAvoidRect field else r_avoid = ImRect(parent_window.Pos.x + horizontal_overlap, -FLT_MAX, parent_window.Pos.x + parent_window.Size.x - horizontal_overlap - parent_window.ScrollbarSizes.x, FLT_MAX) end local pos pos, window.AutoPosLastDirection = ImGui.FindBestWindowPosForPopupEx(window.Pos, window.Size, window.AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy.Default) return pos end if bit32.band(window.Flags, ImGuiWindowFlags.Popup) ~= 0 then local pos pos, window.AutoPosLastDirection = ImGui.FindBestWindowPosForPopupEx(window.Pos, window.Size, window.AutoPosLastDirection, r_outer, ImRect(window.Pos, window.Pos), ImGuiPopupPositionPolicy.Default) -- Ideally we'd disable r_avoid here return pos end if bit32.band(window.Flags, ImGuiWindowFlags.Tooltip) ~= 0 then -- Position tooltip (always follows mouse + clamp within outer boundaries) -- FIXME: -- - Too many paths. One problem is that FindBestWindowPosForPopupEx() doesn't allow passing a suggested position (so touch screen path doesn't use it by default). -- - Drag and drop tooltips are not using this path either: BeginTooltipEx() manually sets their position. -- - Require some tidying up. In theory we could handle both cases in same location, but requires a bit of shuffling -- as drag and drop tooltips are calling SetNextWindowPos() leading to 'window_pos_set_by_api' being set in Begin(). IM_ASSERT(g.CurrentWindow == window) local scale = g.Style.MouseCursorScale local ref_pos = ImGui.NavCalcPreferredRefPos(ImGuiWindowFlags.Tooltip) if g.IO.MouseSource == ImGuiMouseSource.TouchScreen and ImGui.NavCalcPreferredRefPosSource(ImGuiWindowFlags.Tooltip) == ImGuiInputSource.Mouse then local tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_TOUCH * scale - TOOLTIP_DEFAULT_PIVOT_TOUCH * window.Size if r_outer:Contains(ImRect(tooltip_pos, tooltip_pos + window.Size)) then return tooltip_pos end end local tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_MOUSE * scale local r_avoid if g.NavCursorVisible and g.NavHighlightItemUnderNav and not g.IO.ConfigNavMoveSetMousePos then r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8) else r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * scale, ref_pos.y + 24 * scale) -- FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. end local pos pos, window.AutoPosLastDirection = ImGui.FindBestWindowPosForPopupEx(tooltip_pos, window.Size, window.AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy.Tooltip) return pos end IM_ASSERT(false) return window.Pos end --- @param flags ImGuiPopupFlags --- @return ImGuiMouseButton function ImGui.GetMouseButtonFromPopupFlags(flags) IM_ASSERT(bit32.band(flags, ImGuiPopupFlags.InvalidMask_) == 0) if bit32.band(flags, ImGuiPopupFlags.MouseButtonMask_) ~= 0 then return bit32.rshift(bit32.band(flags, ImGuiPopupFlags.MouseButtonMask_), ImGuiPopupFlags.MouseButtonShift_) - 1 end return ImGuiMouseButton.Right -- Default == 1 end --- @param popup_flags ImGuiPopupFlags --- @param id ImGuiID @native function ImGui.IsPopupOpenRequestForItem(popup_flags, id) local g = GImGui local mouse_button = ImGui.GetMouseButtonFromPopupFlags(popup_flags) if ImGui.IsMouseReleased(mouse_button) and ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup) then return true end if g.NavOpenContextMenuItemId == id and (ImGui.IsItemFocused() or id == g.CurrentWindow.MoveId) then return true end return false end --- @param str_id? string --- @param popup_flags? ImGuiPopupFlags @native function ImGui.OpenPopupOnItemClick(str_id, popup_flags) if popup_flags == nil then popup_flags = 0 end local g = GImGui if ImGui.IsPopupOpenRequestForItem(popup_flags, g.LastItemData.ID) then local window = g.CurrentWindow local id if str_id then id = window:GetID(str_id) else id = g.LastItemData.ID end IM_ASSERT(id ~= 0) return ImGui.OpenPopupEx(id, popup_flags) end return false end --- @param str_id? string --- @param popup_flags? ImGuiPopupFlags --- @return bool function ImGui.BeginPopupContextItem(str_id, popup_flags) if popup_flags == nil then popup_flags = 0 end local g = GImGui local window = g.CurrentWindow if window.SkipItems then return false end local id if str_id then id = window:GetID(str_id) else id = g.LastItemData.ID end IM_ASSERT(id ~= 0) if ImGui.IsPopupOpenRequestForItem(popup_flags, g.LastItemData.ID) then ImGui.OpenPopupEx(id, popup_flags) end return ImGui.BeginPopupEx(id, bit32.bor(ImGuiWindowFlags.AlwaysAutoResize, ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoSavedSettings)) end --------------------------------------------------------------------------------------- -- [SECTION] NAVIGATION --------------------------------------------------------------------------------------- --- @param id ImGuiID function ImGui.PushFocusScope(id) local g = GImGui local data = ImGuiFocusScopeData() data.ID = id data.WindowID = g.CurrentWindow.ID g.FocusScopeStack:push_back(data) g.CurrentFocusScopeId = id end function ImGui.PopFocusScope() local g = GImGui -- TODO: IM_ASSERT_USER_ERROR_RET(g.FocusScopeStack.Size > g.StackSizesInBeginForCurrentWindow.SizeOfFocusScopeStack, "Calling PopFocusScope() too many times!") g.FocusScopeStack:pop_back() if g.FocusScopeStack.Size > 0 then g.CurrentFocusScopeId = g.FocusScopeStack:back().ID else g.CurrentFocusScopeId = 0 end end --- @param focus_scope_id ImGuiID function ImGui.IsInNavFocusRoute(focus_scope_id) local g = GImGui if g.NavFocusScopeId == focus_scope_id then return true end for _, focus_scope in g.NavFocusRoute:iter() do if focus_scope.ID == focus_scope_id then return true end end return false end --- @param focus_scope_id ImGuiID function ImGui.SetNavFocusScope(focus_scope_id) local g = GImGui g.NavFocusScopeId = focus_scope_id g.NavFocusRoute:resize(0) if focus_scope_id == 0 then return end IM_ASSERT(g.NavWindow ~= nil) if focus_scope_id == g.CurrentFocusScopeId then for n = g.FocusScopeStack.Size, 1, -1 do local data = g.FocusScopeStack.Data[n] if data.WindowID ~= g.CurrentWindow.ID then break end g.NavFocusRoute:push_back(data) end elseif focus_scope_id == g.NavWindow.NavRootFocusScopeId then local data = ImGuiFocusScopeData() data.ID = focus_scope_id data.WindowID = g.NavWindow.ID g.NavFocusRoute:push_back(data) else return end local window = g.NavWindow.ParentWindowForFocusRoute while window ~= nil do local data = ImGuiFocusScopeData() data.ID = window.NavRootFocusScopeId data.WindowID = window.ID g.NavFocusRoute:push_back(data) window = window.ParentWindowForFocusRoute end IM_ASSERT(g.NavFocusRoute.Size < 100) end function ImGui.SetItemDefaultFocus() local g = GImGui local window = g.CurrentWindow if not window.Appearing then return end if g.NavWindow ~= window.RootWindowForNav or (not g.NavInitRequest and g.NavInitResult.ID == 0) or g.NavLayer ~= window.DC.NavLayerCurrent then return end g.NavInitRequest = false ImGui.NavApplyItemToResult(g.NavInitResult) ImGui.NavUpdateAnyRequestFlag() if not window.ClipRect:Contains(g.LastItemData.Rect) then ImGui.ScrollToRectEx(window, g.LastItemData.Rect, ImGuiScrollFlags.None) end end function ImGui.SetNavCursorVisibleAfterMove() local g = GImGui if g.NavWindow and (bit32.band(g.NavWindow.Flags, ImGuiWindowFlags.NoNavInputs) ~= 0) then g.NavCursorVisible = false elseif g.NavInputSource == ImGuiInputSource.Keyboard and (bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard) == 0) then g.NavCursorVisible = false elseif g.NavInputSource == ImGuiInputSource.Gamepad and (bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.NavEnableGamepad) == 0) then g.NavCursorVisible = false elseif g.IO.ConfigNavCursorVisibleAuto then g.NavCursorVisible = true end g.NavHighlightItemUnderNav = true g.NavMousePosDirty = true end --- @param window? ImGuiWindow function ImGui.SetNavWindow(window) local g = GImGui if g.NavWindow ~= window then -- IMGUI_DEBUG_LOG_FOCUS("[focus] SetNavWindow(\"%s\")", window and window.Name or "") g.NavWindow = window g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid end g.NavInitRequest = false g.NavMoveSubmitted = false g.NavMoveScoringItems = false ImGui.NavUpdateAnyRequestFlag() end --- @param axis ImGuiAxis function ImGui.NavClearPreferredPosForAxis(axis) local g = GImGui g.NavWindow.RootWindowForNav.NavPreferredScoringPosRel[g.NavLayer][axis] = FLT_MAX end --- @param id ImGuiID --- @param nav_layer ImGuiNavLayer --- @param focus_scope_id ImGuiID --- @param rect_rel ImRect function ImGui.SetNavID(id, nav_layer, focus_scope_id, rect_rel) local g = GImGui IM_ASSERT(g.NavWindow ~= nil) IM_ASSERT(nav_layer == ImGuiNavLayer.Main or nav_layer == ImGuiNavLayer.Menu) g.NavId = id g.NavLayer = nav_layer ImGui.SetNavFocusScope(focus_scope_id) g.NavWindow.NavLastIds[nav_layer] = id ImRect_Copy(g.NavWindow.NavRectRel[nav_layer], rect_rel) ImGui.NavClearPreferredPosForAxis(ImGuiAxis.X) ImGui.NavClearPreferredPosForAxis(ImGuiAxis.Y) end --- @param nav_window ImGuiWindow function ImGui.NavSaveLastChildNavWindowIntoParent(nav_window) local parent = nav_window while parent and parent.RootWindow ~= parent and bit32.band(parent.Flags, bit32.bor(ImGuiWindowFlags.Popup, ImGuiWindowFlags.ChildMenu)) == 0 do parent = parent.ParentWindow end if parent and parent ~= nav_window then parent.NavLastChildNavWindow = nav_window end end --- @param window ImGuiWindow function ImGui.NavRestoreLastChildNavWindow(window) if window.NavLastChildNavWindow and window.NavLastChildNavWindow.WasActive then return window.NavLastChildNavWindow end return window end --- @param layer ImGuiNavLayer function ImGui.NavRestoreLayer(layer) local g = GImGui if layer == ImGuiNavLayer.Main then local prev_nav_window = g.NavWindow g.NavWindow = ImGui.NavRestoreLastChildNavWindow(g.NavWindow) g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid end local window = g.NavWindow if window.NavLastIds[layer] ~= 0 then ImGui.SetNavID(window.NavLastIds[layer], layer, 0, window.NavRectRel[layer]) else g.NavLayer = layer ImGui.NavInitWindow(window, true) end end --- @param id ImGuiID function ImGui.NavHighlightActivated(id) local g = GImGui g.NavHighlightActivatedId = id g.NavHighlightActivatedTimer = NAV_ACTIVATE_HIGHLIGHT_TIMER end function ImGui.NavUpdateAnyRequestFlag() local g = GImGui g.NavAnyRequest = g.NavMoveScoringItems or g.NavInitRequest or (IMGUI_DEBUG_NAV_SCORING and g.NavWindow ~= nil) if g.NavAnyRequest then IM_ASSERT(g.NavWindow ~= nil) end end --- @param window ImGuiWindow --- @param force_reinit bool function ImGui.NavInitWindow(window, force_reinit) local g = GImGui IM_ASSERT(window == g.NavWindow) if bit32.band(window.Flags, ImGuiWindowFlags.NoNavInputs) ~= 0 then g.NavId = 0 ImGui.SetNavFocusScope(window.NavRootFocusScopeId) return end local init_for_nav = false if window == window.RootWindow or bit32.band(window.Flags, ImGuiWindowFlags.Popup) ~= 0 or window.NavLastIds[0] == 0 or force_reinit then init_for_nav = true end -- IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from NavInitWindow(), init_for_nav=%d, window=\"%s\", layer=%d", init_for_nav, window->Name, g.NavLayer) if init_for_nav then ImGui.SetNavID(0, g.NavLayer, window.NavRootFocusScopeId, ImRect()) g.NavInitRequest = true g.NavInitRequestFromMove = false g.NavInitResult.ID = 0 ImGui.NavUpdateAnyRequestFlag() else g.NavId = window.NavLastIds[0] ImGui.SetNavFocusScope(window.NavRootFocusScopeId) end end --- @param window_type ImGuiWindowFlags --- @return ImGuiInputSource function ImGui.NavCalcPreferredRefPosSource(window_type) local g = GImGui local window = g.NavWindow local activated_shortcut = g.ActiveId ~= 0 and g.ActiveIdFromShortcut and g.ActiveId == g.LastItemData.ID if (bit32.band(window_type, ImGuiWindowFlags.Popup) ~= 0) and activated_shortcut then return ImGuiInputSource.Keyboard end if not g.NavCursorVisible or not g.NavHighlightItemUnderNav or not window then return ImGuiInputSource.Mouse else return ImGuiInputSource.Keyboard -- or Nav in general end end --- @param window_type ImGuiWindowFlags --- @return ImVec2 --- @nodiscard function ImGui.NavCalcPreferredRefPos(window_type) local g = GImGui local window = g.NavWindow local source = ImGui.NavCalcPreferredRefPosSource(window_type) if source == ImGuiInputSource.Mouse then -- Mouse (we need a fallback in case the mouse becomes invalid after being used) -- The +1.0f offset when stored by OpenPopupEx() allows reopening this or another popup (same or another mouse button) while not moving the mouse, it is pretty standard. -- In theory we could move that +1.0f offset in OpenPopupEx() local p = ImGui.IsMousePosValid(g.IO.MousePos) and g.IO.MousePos or g.MouseLastValidPos return ImVec2(p.x + 1.0, p.y) else -- When navigation is active and mouse is disabled, pick a position around the bottom left of the currently navigated item local activated_shortcut = g.ActiveId ~= 0 and g.ActiveIdFromShortcut and g.ActiveId == g.LastItemData.ID local ref_rect = ImRect() if activated_shortcut and (bit32.band(window_type, ImGuiWindowFlags.Popup) ~= 0) then ImRect_Copy(ref_rect, g.LastItemData.NavRect) elseif window ~= nil then ImRect_Copy(ref_rect, ImGui.WindowRectRelToAbs(window, window.NavRectRel[g.NavLayer])) end -- Take account of upcoming scrolling (maybe set mouse pos should be done in EndFrame?) if window ~= nil and window.LastFrameActive ~= g.FrameCount and (window.ScrollTarget.x ~= FLT_MAX or window.ScrollTarget.y ~= FLT_MAX) then local next_scroll = CalcNextScrollFromScrollTargetAndClamp(window) ref_rect:Translate(window.Scroll - next_scroll) end local pos = ImVec2(ref_rect.Min.x + ImMin(g.Style.FramePadding.x * 4, ref_rect:GetWidth()), ref_rect.Max.y - ImMin(g.Style.FramePadding.y, ref_rect:GetHeight())) if window ~= nil then local viewport = window.Viewport if viewport then pos = ImClamp(pos, viewport.Pos, viewport.Pos + viewport.Size) end end return ImTrunc(pos) -- ImTrunc() is important because non-integer mouse position application in backend might be lossy and result in undesirable non-zero delta. end end --- @param axis ImGuiAxis --- @return float function ImGui.GetNavTweakPressedAmount(axis) local g = GImGui local repeat_delay, repeat_rate = ImGui.GetTypematicRepeatRate(ImGuiInputFlags.RepeatRateNavTweak) local key_less, key_more if g.NavInputSource == ImGuiInputSource.Gamepad then key_less = (axis == ImGuiAxis.X) and ImGuiKey.GamepadDpadLeft or ImGuiKey.GamepadDpadUp key_more = (axis == ImGuiAxis.X) and ImGuiKey.GamepadDpadRight or ImGuiKey.GamepadDpadDown else key_less = (axis == ImGuiAxis.X) and ImGuiKey.LeftArrow or ImGuiKey.UpArrow key_more = (axis == ImGuiAxis.X) and ImGuiKey.RightArrow or ImGuiKey.DownArrow end local amount = ImGui.GetKeyPressedAmount(key_more, repeat_delay, repeat_rate) - ImGui.GetKeyPressedAmount(key_less, repeat_delay, repeat_rate) if amount ~= 0.0 and ImGui.IsKeyDown(key_less) and ImGui.IsKeyDown(key_more) then -- Cancel when opposite directions are held, regardless of repeat phase amount = 0.0 end return amount end do local nav_gamepad_keys_to_change_source = { ImGuiKey.GamepadFaceRight, ImGuiKey.GamepadFaceLeft, ImGuiKey.GamepadFaceUp, ImGuiKey.GamepadFaceDown, ImGuiKey.GamepadDpadRight, ImGuiKey.GamepadDpadLeft, ImGuiKey.GamepadDpadUp, ImGuiKey.GamepadDpadDown } local nav_keyboard_keys_to_change_source = { ImGuiKey.Space, ImGuiKey.Enter, ImGuiKey.Escape, ImGuiKey.RightArrow, ImGuiKey.LeftArrow, ImGuiKey.UpArrow, ImGuiKey.DownArrow } --- @param focus_change_dir int function ImGui.NavUpdateWindowingTarget(focus_change_dir) local g = GImGui IM_ASSERT(g.NavWindowingTarget) if bit32.band(g.NavWindowingTarget.Flags, ImGuiWindowFlags.Modal) ~= 0 then return end local i_current = ImGui.FindWindowFocusIndex(g.NavWindowingTarget) local window_target = ImGui.FindWindowNavFocusable(i_current + focus_change_dir, 0, focus_change_dir) if window_target == nil then window_target = ImGui.FindWindowNavFocusable((focus_change_dir < 0) and g.WindowsFocusOrder.Size or 1, i_current, focus_change_dir) end if window_target then g.NavWindowingTarget = window_target g.NavWindowingTargetAnim = window_target ImVec2_CopyV(g.NavWindowingAccumDeltaPos, 0.0, 0.0) ImVec2_CopyV(g.NavWindowingAccumDeltaSize, 0.0, 0.0) end g.NavWindowingToggleLayer = false end --- @param apply_focus_window ImGuiWindow function ImGui.NavUpdateWindowingApplyFocus(apply_focus_window) local g = GImGui if g.NavWindow == nil or apply_focus_window ~= g.NavWindow.RootWindow then local previous_viewport = g.NavWindow and g.NavWindow.Viewport or nil ImGui.ClearActiveID() ImGui.SetNavCursorVisibleAfterMove() ImGui.ClosePopupsOverWindow(apply_focus_window, false) ImGui.FocusWindow(apply_focus_window, ImGuiFocusRequestFlags.RestoreFocusedChild) IM_ASSERT(g.NavWindow ~= nil) apply_focus_window = g.NavWindow if apply_focus_window.NavLastIds[ImGuiNavLayer.Main] == 0 then ImGui.NavInitWindow(apply_focus_window, false) end if apply_focus_window.DC.NavLayersActiveMaskNext == bit32.lshift(1, ImGuiNavLayer.Menu) then g.NavLayer = ImGuiNavLayer.Menu end if apply_focus_window.Viewport ~= previous_viewport and g.PlatformIO.Platform_SetWindowFocus then g.PlatformIO.Platform_SetWindowFocus(apply_focus_window.Viewport) end end g.NavWindowingTarget = nil end function ImGui.NavUpdateWindowing() local g = GImGui local io = g.IO local apply_focus_window = nil local apply_toggle_layer = false local modal_window = ImGui.GetTopMostPopupModal() local allow_windowing = (modal_window == nil) if not allow_windowing then g.NavWindowingTarget = nil end if g.NavWindowingTargetAnim and g.NavWindowingTarget == nil then g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - io.DeltaTime * 10.0, 0.0) if g.DimBgRatio <= 0.0 and g.NavWindowingHighlightAlpha <= 0.0 then g.NavWindowingTargetAnim = nil end end local owner_id = ImHashStr("##NavUpdateWindowing") local nav_gamepad_active = bit32.band(io.ConfigFlags, ImGuiConfigFlags.NavEnableGamepad) ~= 0 and bit32.band(io.BackendFlags, ImGuiBackendFlags.HasGamepad) ~= 0 local nav_keyboard_active = bit32.band(io.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard) ~= 0 local keyboard_next_window = allow_windowing and g.ConfigNavWindowingKeyNext ~= 0 and ImGui.Shortcut(g.ConfigNavWindowingKeyNext, bit32.bor(ImGuiInputFlags.Repeat, ImGuiInputFlags.RouteAlways), owner_id) local keyboard_prev_window = allow_windowing and g.ConfigNavWindowingKeyPrev ~= 0 and ImGui.Shortcut(g.ConfigNavWindowingKeyPrev, bit32.bor(ImGuiInputFlags.Repeat, ImGuiInputFlags.RouteAlways), owner_id) local start_toggling_with_gamepad = nav_gamepad_active and g.NavWindowingTarget == nil and ImGui.Shortcut(ImGuiKey.NavGamepadMenu, ImGuiInputFlags.RouteAlways, owner_id) local start_windowing_with_gamepad = allow_windowing and start_toggling_with_gamepad local start_windowing_with_keyboard = allow_windowing and g.NavWindowingTarget == nil and (keyboard_next_window or keyboard_prev_window) local just_started_windowing_from_null_focus = false if start_toggling_with_gamepad then g.NavWindowingToggleLayer = true g.NavWindowingToggleKey = ImGuiKey.NavGamepadMenu g.NavWindowingInputSource = ImGuiInputSource.Gamepad g.NavInputSource = ImGuiInputSource.Gamepad end if start_windowing_with_gamepad or start_windowing_with_keyboard then local window = (g.NavWindow and ImGui.IsWindowNavFocusable(g.NavWindow)) and g.NavWindow or ImGui.FindWindowNavFocusable(g.WindowsFocusOrder.Size, 0, -1) if window then if start_windowing_with_keyboard or g.ConfigNavWindowingWithGamepad then g.NavWindowingTarget = window.RootWindow g.NavWindowingTargetAnim = window.RootWindow end g.NavWindowingTimer = 0.0 g.NavWindowingHighlightAlpha = 0.0 ImVec2_CopyV(g.NavWindowingAccumDeltaPos, 0.0, 0.0) ImVec2_CopyV(g.NavWindowingAccumDeltaSize, 0.0, 0.0) g.NavWindowingInputSource = start_windowing_with_keyboard and ImGuiInputSource.Keyboard or ImGuiInputSource.Gamepad g.NavInputSource = g.NavWindowingInputSource if g.NavWindow == nil then just_started_windowing_from_null_focus = true end if keyboard_next_window or keyboard_prev_window then ImGui.SetKeyOwnersForKeyChord(bit32.band(bit32.bor(g.ConfigNavWindowingKeyNext, g.ConfigNavWindowingKeyPrev), ImGuiMod_Mask_), owner_id) end end end if (g.NavWindowingTarget or g.NavWindowingToggleLayer) and g.NavWindowingInputSource == ImGuiInputSource.Gamepad then if g.NavWindowingTarget ~= nil then g.NavWindowingTimer = g.NavWindowingTimer + io.DeltaTime g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05)) local focus_change_dir = (ImGui.IsKeyPressedEx(ImGuiKey.GamepadL1, ImGuiInputFlags.None, ImGuiKeyOwner_Any) and 1 or 0) - (ImGui.IsKeyPressedEx(ImGuiKey.GamepadR1, ImGuiInputFlags.None, ImGuiKeyOwner_Any) and 1 or 0) if focus_change_dir ~= 0 and not just_started_windowing_from_null_focus then ImGui.NavUpdateWindowingTarget(focus_change_dir) g.NavWindowingHighlightAlpha = 1.0 end end if not ImGui.IsKeyDown(ImGuiKey.NavGamepadMenu) then g.NavWindowingToggleLayer = g.NavWindowingToggleLayer and (g.NavWindowingHighlightAlpha < 1.0) if g.NavWindowingToggleLayer and g.NavWindow then apply_toggle_layer = true elseif not g.NavWindowingToggleLayer then apply_focus_window = g.NavWindowingTarget end g.NavWindowingTarget = nil g.NavWindowingToggleLayer = false end end if g.NavWindowingTarget and g.NavWindowingInputSource == ImGuiInputSource.Keyboard then local shared_mods = bit32.band( bit32.band((g.ConfigNavWindowingKeyNext ~= 0) and g.ConfigNavWindowingKeyNext or ImGuiMod_Mask_, (g.ConfigNavWindowingKeyPrev ~= 0) and g.ConfigNavWindowingKeyPrev or ImGuiMod_Mask_), ImGuiMod_Mask_) IM_ASSERT(shared_mods ~= 0) g.NavWindowingTimer = g.NavWindowingTimer + io.DeltaTime g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05)) if (keyboard_next_window or keyboard_prev_window) and not just_started_windowing_from_null_focus then ImGui.NavUpdateWindowingTarget(keyboard_next_window and -1 or 1) elseif bit32.band(io.KeyMods, shared_mods) ~= shared_mods then apply_focus_window = g.NavWindowingTarget end end local windowing_toggle_keys = { ImGuiKey.LeftAlt, ImGuiKey.RightAlt } local windowing_toggle_layer_start = false if g.NavWindow and bit32.band(g.NavWindow.Flags, ImGuiWindowFlags.NoNavInputs) == 0 then for _, windowing_toggle_key in ipairs(windowing_toggle_keys) do if nav_keyboard_active and ImGui.IsKeyPressedEx(windowing_toggle_key, ImGuiInputFlags.None, ImGuiKeyOwner_NoOwner) then windowing_toggle_layer_start = true g.NavWindowingToggleLayer = true g.NavWindowingToggleKey = windowing_toggle_key g.NavWindowingInputSource = ImGuiInputSource.Keyboard g.NavInputSource = ImGuiInputSource.Keyboard break end end end if g.NavWindowingToggleLayer and g.NavWindowingInputSource == ImGuiInputSource.Keyboard then if io.InputQueueCharacters.Size > 0 or io.KeyCtrl or io.KeyShift or io.KeySuper then g.NavWindowingToggleLayer = false elseif windowing_toggle_layer_start == false and g.LastKeyboardKeyPressTime == g.Time then g.NavWindowingToggleLayer = false elseif ImGui.TestKeyOwner(g.NavWindowingToggleKey, ImGuiKeyOwner_NoOwner) == false or ImGui.TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_NoOwner) == false then g.NavWindowingToggleLayer = false end if ImGui.IsKeyReleased(g.NavWindowingToggleKey) and g.NavWindowingToggleLayer then if g.ActiveId == 0 or g.ActiveIdAllowOverlap then if ImGui.IsMousePosValid(io.MousePos) == ImGui.IsMousePosValid(io.MousePosPrev) then apply_toggle_layer = true end end end if not ImGui.IsKeyDown(g.NavWindowingToggleKey) then g.NavWindowingToggleLayer = false end end if g.NavWindowingTarget and bit32.band(g.NavWindowingTarget.Flags, ImGuiWindowFlags.NoMove) == 0 then local nav_move_dir if g.NavInputSource == ImGuiInputSource.Keyboard and not io.KeyShift then nav_move_dir = ImGui.GetKeyMagnitude2d(ImGuiKey.LeftArrow, ImGuiKey.RightArrow, ImGuiKey.UpArrow, ImGuiKey.DownArrow) end if g.NavInputSource == ImGuiInputSource.Gamepad then nav_move_dir = ImGui.GetKeyMagnitude2d(ImGuiKey.GamepadLStickLeft, ImGuiKey.GamepadLStickRight, ImGuiKey.GamepadLStickUp, ImGuiKey.GamepadLStickDown) end if nav_move_dir and (nav_move_dir.x ~= 0.0 or nav_move_dir.y ~= 0.0) then local NAV_MOVE_SPEED = 800.0 local move_step = NAV_MOVE_SPEED * io.DeltaTime * ImGui.GetScale() ImVec2_Copy(g.NavWindowingAccumDeltaPos, g.NavWindowingAccumDeltaPos + nav_move_dir * move_step) g.NavHighlightItemUnderNav = true local accum_floored = ImTrunc(g.NavWindowingAccumDeltaPos) if accum_floored.x ~= 0.0 or accum_floored.y ~= 0.0 then local moving_window = g.NavWindowingTarget.RootWindowDockTree ImGui.SetWindowPos(moving_window, moving_window.Pos + accum_floored, ImGuiCond.Always) ImVec2_Copy(g.NavWindowingAccumDeltaPos, g.NavWindowingAccumDeltaPos - accum_floored) end end end if apply_focus_window then ImGui.NavUpdateWindowingApplyFocus(apply_focus_window) end if apply_toggle_layer and g.NavWindow then ImGui.ClearActiveID() local new_nav_window = g.NavWindow while new_nav_window.ParentWindow and bit32.band(new_nav_window.DC.NavLayersActiveMask, bit32.lshift(1, ImGuiNavLayer.Menu)) == 0 and bit32.band(new_nav_window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 and bit32.band(new_nav_window.Flags, bit32.bor(ImGuiWindowFlags.Popup, ImGuiWindowFlags.ChildMenu)) == 0 do new_nav_window = new_nav_window.ParentWindow end if new_nav_window ~= g.NavWindow then local old_nav_window = g.NavWindow ImGui.FocusWindow(new_nav_window) new_nav_window.NavLastChildNavWindow = old_nav_window end local new_nav_layer = (bit32.band(g.NavWindow.DC.NavLayersActiveMask, bit32.lshift(1, ImGuiNavLayer.Menu)) ~= 0) and bit32.bxor(g.NavLayer, 1) or ImGuiNavLayer.Main if new_nav_layer ~= g.NavLayer then local preserve_layer_1_nav_id = (new_nav_window.DockNodeAsHost ~= nil) if new_nav_layer == ImGuiNavLayer.Menu and not preserve_layer_1_nav_id then g.NavWindow.NavLastIds[new_nav_layer] = 0 end ImGui.NavRestoreLayer(new_nav_layer) ImGui.SetNavCursorVisibleAfterMove() end end end function ImGui.NavUpdateContextMenuRequest() local g = GImGui local io = g.IO g.NavOpenContextMenuItemId = 0 g.NavOpenContextMenuWindowId = 0 local nav_keyboard_active = bit32.band(io.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard) ~= 0 local nav_gamepad_active = bit32.band(io.ConfigFlags, ImGuiConfigFlags.NavEnableGamepad) ~= 0 if (not nav_keyboard_active and not nav_gamepad_active) or g.NavWindow == nil then return end local request = false request = request or (nav_keyboard_active and (ImGui.IsKeyReleased(ImGuiKey.Menu, ImGuiKeyOwner_NoOwner) or (ImGui.IsKeyPressedEx(ImGuiKey.F10, ImGuiInputFlags.None, ImGuiKeyOwner_NoOwner) and io.KeyMods == ImGuiMod_Shift))) request = request or (nav_gamepad_active and ImGui.IsKeyPressedEx(ImGuiKey.NavGamepadContextMenu, ImGuiInputFlags.None, ImGuiKeyOwner_NoOwner)) if not request then return end g.NavOpenContextMenuItemId = g.NavId g.NavOpenContextMenuWindowId = g.NavWindow.ID if g.NavId == g.NavWindow:GetID("#CLOSE") or g.NavId == g.NavWindow:GetID("#COLLAPSE") then g.NavOpenContextMenuItemId = g.NavWindow.MoveId end g.NavInputSource = ImGuiInputSource.Keyboard ImGui.SetNavCursorVisibleAfterMove() end --- @param move_dir ImGuiDir --- @param clip_dir ImGuiDir --- @param move_flags ImGuiNavMoveFlags --- @param scroll_flags ImGuiScrollFlags function ImGui.NavMoveRequestSubmit(move_dir, clip_dir, move_flags, scroll_flags) local g = GImGui IM_ASSERT(g.NavWindow ~= nil) if bit32.band(move_flags, ImGuiNavMoveFlags.IsTabbing) ~= 0 then move_flags = bit32.bor(move_flags, ImGuiNavMoveFlags.AllowCurrentNavId) end g.NavMoveSubmitted = true; g.NavMoveScoringItems = true g.NavMoveDir = move_dir g.NavMoveDirForDebug = move_dir g.NavMoveClipDir = clip_dir g.NavMoveFlags = move_flags g.NavMoveScrollFlags = scroll_flags g.NavMoveForwardToNextFrame = false g.NavMoveKeyMods = (bit32.band(move_flags, ImGuiNavMoveFlags.FocusApi) ~= 0) and 0 or g.IO.KeyMods g.NavMoveResultLocal:Clear() g.NavMoveResultLocalVisible:Clear() g.NavMoveResultOther:Clear() g.NavTabbingCounter = 0 g.NavTabbingResultFirst:Clear() ImGui.NavUpdateAnyRequestFlag() end --- @param r ImRect --- @param preferred_pos_rel ImVec2 --- @param move_dir ImGuiDir --- @param move_flags ImGuiNavMoveFlags function ImGui.NavBiasScoringRect(r, preferred_pos_rel, move_dir, move_flags) local g = GImGui local rel_to_abs_offset = g.NavWindow.DC.CursorStartPos if bit32.band(move_flags, ImGuiNavMoveFlags.Forwarded) == 0 then if preferred_pos_rel.x == FLT_MAX then preferred_pos_rel.x = ImMin(r.Min.x + 1.0, r.Max.x) - rel_to_abs_offset.x end if preferred_pos_rel.y == FLT_MAX then preferred_pos_rel.y = r:GetCenter().y - rel_to_abs_offset.y end end if (move_dir == ImGuiDir.Up or move_dir == ImGuiDir.Down) and preferred_pos_rel.x ~= FLT_MAX then local new_x = preferred_pos_rel.x + rel_to_abs_offset.x r.Min.x = new_x; r.Max.x = new_x elseif (move_dir == ImGuiDir.Left or move_dir == ImGuiDir.Right) and preferred_pos_rel.y ~= FLT_MAX then local new_y = preferred_pos_rel.y + rel_to_abs_offset.y r.Min.y = new_y; r.Max.y = new_y end end --- @return float function ImGui.NavUpdatePageUpPageDown() local g = GImGui local window = g.NavWindow if bit32.band(window.Flags, ImGuiWindowFlags.NoNavInputs) ~= 0 or g.NavWindowingTarget ~= nil then return 0.0 end local page_up_held = ImGui.IsKeyDown(ImGuiKey.PageUp, ImGuiKeyOwner_NoOwner) local page_down_held = ImGui.IsKeyDown(ImGuiKey.PageDown, ImGuiKeyOwner_NoOwner) local home_pressed = ImGui.IsKeyPressedEx(ImGuiKey.Home, ImGuiInputFlags.Repeat, ImGuiKeyOwner_NoOwner) local end_pressed = ImGui.IsKeyPressedEx(ImGuiKey.End, ImGuiInputFlags.Repeat, ImGuiKeyOwner_NoOwner) if page_up_held == page_down_held and home_pressed == end_pressed then return 0.0 end if g.NavLayer ~= ImGuiNavLayer.Main then ImGui.NavRestoreLayer(ImGuiNavLayer.Main) end if bit32.band(window.DC.NavLayersActiveMask, bit32.lshift(1, ImGuiNavLayer.Main)) == 0 and window.DC.NavWindowHasScrollY then if ImGui.IsKeyPressedEx(ImGuiKey.PageUp, ImGuiInputFlags.Repeat, ImGuiKeyOwner_NoOwner) then ImGui.SetScrollY(window, window.Scroll.y - window.InnerRect:GetHeight()) elseif ImGui.IsKeyPressedEx(ImGuiKey.PageDown, ImGuiInputFlags.Repeat, ImGuiKeyOwner_NoOwner) then ImGui.SetScrollY(window, window.Scroll.y + window.InnerRect:GetHeight()) elseif home_pressed then ImGui.SetScrollY(window, 0.0) elseif end_pressed then ImGui.SetScrollY(window, window.ScrollMax.y) end else local nav_rect_rel = window.NavRectRel[g.NavLayer] local page_offset_y = ImMax(0.0, window.InnerRect:GetHeight() - window.FontRefSize * 1.0 + nav_rect_rel:GetHeight()) local nav_scoring_rect_offset_y = 0.0 if ImGui.IsKeyPressedEx(ImGuiKey.PageUp, ImGuiInputFlags.Repeat, ImGuiKeyOwner_NoOwner) then nav_scoring_rect_offset_y = -page_offset_y g.NavMoveDir = ImGuiDir.Down g.NavMoveClipDir = ImGuiDir.Up g.NavMoveFlags = bit32.bor(ImGuiNavMoveFlags.AllowCurrentNavId, ImGuiNavMoveFlags.IsPageMove) elseif ImGui.IsKeyPressedEx(ImGuiKey.PageDown, ImGuiInputFlags.Repeat, ImGuiKeyOwner_NoOwner) then nav_scoring_rect_offset_y = page_offset_y g.NavMoveDir = ImGuiDir.Up g.NavMoveClipDir = ImGuiDir.Down g.NavMoveFlags = bit32.bor(ImGuiNavMoveFlags.AllowCurrentNavId, ImGuiNavMoveFlags.IsPageMove) elseif home_pressed then nav_rect_rel.Min.y = 0.0; nav_rect_rel.Max.y = 0.0 if nav_rect_rel:IsInverted() then nav_rect_rel.Min.x = 0.0; nav_rect_rel.Max.x = 0.0 end g.NavMoveDir = ImGuiDir.Down g.NavMoveFlags = bit32.bor(ImGuiNavMoveFlags.AllowCurrentNavId, ImGuiNavMoveFlags.ScrollToEdgeY) elseif end_pressed then nav_rect_rel.Min.y = window.ContentSize.y; nav_rect_rel.Max.y = window.ContentSize.y if nav_rect_rel:IsInverted() then nav_rect_rel.Min.x = 0.0; nav_rect_rel.Max.x = 0.0 end g.NavMoveDir = ImGuiDir.Up g.NavMoveFlags = bit32.bor(ImGuiNavMoveFlags.AllowCurrentNavId, ImGuiNavMoveFlags.ScrollToEdgeY) end return nav_scoring_rect_offset_y end return 0.0 end function ImGui.NavUpdateCreateMoveRequest() local g = GImGui local io = g.IO local window = g.NavWindow local nav_gamepad_active = bit32.band(io.ConfigFlags, ImGuiConfigFlags.NavEnableGamepad) ~= 0 and bit32.band(io.BackendFlags, ImGuiBackendFlags.HasGamepad) ~= 0 local nav_keyboard_active = bit32.band(io.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard) ~= 0 if g.NavMoveForwardToNextFrame and window ~= nil then IM_ASSERT(g.NavMoveDir ~= ImGuiDir.None and g.NavMoveClipDir ~= ImGuiDir.None) IM_ASSERT(bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.Forwarded) ~= 0) else g.NavMoveDir = ImGuiDir.None g.NavMoveFlags = ImGuiNavMoveFlags.None g.NavMoveScrollFlags = ImGuiScrollFlags.None if window and not g.NavWindowingTarget and bit32.band(window.Flags, ImGuiWindowFlags.NoNavInputs) == 0 then local repeat_mode = bit32.bor(ImGuiInputFlags.Repeat, ImGuiInputFlags.RepeatRateNavMove) if not ImGui.IsActiveIdUsingNavDir(ImGuiDir.Left) and ((nav_gamepad_active and ImGui.IsKeyPressedEx(ImGuiKey.GamepadDpadLeft, repeat_mode, ImGuiKeyOwner_NoOwner)) or (nav_keyboard_active and ImGui.IsKeyPressedEx(ImGuiKey.LeftArrow, repeat_mode, ImGuiKeyOwner_NoOwner))) then g.NavMoveDir = ImGuiDir.Left end if not ImGui.IsActiveIdUsingNavDir(ImGuiDir.Right) and ((nav_gamepad_active and ImGui.IsKeyPressedEx(ImGuiKey.GamepadDpadRight, repeat_mode, ImGuiKeyOwner_NoOwner)) or (nav_keyboard_active and ImGui.IsKeyPressedEx(ImGuiKey.RightArrow, repeat_mode, ImGuiKeyOwner_NoOwner))) then g.NavMoveDir = ImGuiDir.Right end if not ImGui.IsActiveIdUsingNavDir(ImGuiDir.Up) and ((nav_gamepad_active and ImGui.IsKeyPressedEx(ImGuiKey.GamepadDpadUp, repeat_mode, ImGuiKeyOwner_NoOwner)) or (nav_keyboard_active and ImGui.IsKeyPressedEx(ImGuiKey.UpArrow, repeat_mode, ImGuiKeyOwner_NoOwner))) then g.NavMoveDir = ImGuiDir.Up end if not ImGui.IsActiveIdUsingNavDir(ImGuiDir.Down) and ((nav_gamepad_active and ImGui.IsKeyPressedEx(ImGuiKey.GamepadDpadDown, repeat_mode, ImGuiKeyOwner_NoOwner)) or (nav_keyboard_active and ImGui.IsKeyPressedEx(ImGuiKey.DownArrow, repeat_mode, ImGuiKeyOwner_NoOwner))) then g.NavMoveDir = ImGuiDir.Down end end g.NavMoveClipDir = g.NavMoveDir ImRect_Copy(g.NavScoringNoClipRect, ImRect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX)) end local scoring_page_offset_y = 0.0 if window and g.NavMoveDir == ImGuiDir.None and nav_keyboard_active then scoring_page_offset_y = ImGui.NavUpdatePageUpPageDown() end g.NavMoveForwardToNextFrame = false if g.NavMoveDir ~= ImGuiDir.None then ImGui.NavMoveRequestSubmit(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags) end if g.NavMoveSubmitted and g.NavId == 0 then g.NavInitRequest = true; g.NavInitRequestFromMove = true g.NavInitResult.ID = 0 if g.IO.ConfigNavCursorVisibleAuto then g.NavCursorVisible = true end end if g.NavMoveSubmitted and g.NavInputSource == ImGuiInputSource.Gamepad and g.NavLayer == ImGuiNavLayer.Main and window ~= nil then local clamp_x = bit32.band(g.NavMoveFlags, bit32.bor(ImGuiNavMoveFlags.LoopX, ImGuiNavMoveFlags.WrapX)) == 0 local clamp_y = bit32.band(g.NavMoveFlags, bit32.bor(ImGuiNavMoveFlags.LoopY, ImGuiNavMoveFlags.WrapY)) == 0 local inner_rect_rel = ImGui.WindowRectAbsToRel(window, ImRect(ImVec2(window.InnerRect.Min.x - 1, window.InnerRect.Min.y - 1), ImVec2(window.InnerRect.Max.x + 1, window.InnerRect.Max.y + 1))) local next_scroll = CalcNextScrollFromScrollTargetAndClamp(window) inner_rect_rel:Translate(ImVec2(next_scroll.x - window.Scroll.x, next_scroll.y - window.Scroll.y)) if (clamp_x or clamp_y) and not inner_rect_rel:Contains(window.NavRectRel[g.NavLayer]) then local pad_x = ImMin(inner_rect_rel:GetWidth(), window.FontRefSize * 0.5) local pad_y = ImMin(inner_rect_rel:GetHeight(), window.FontRefSize * 0.5) inner_rect_rel.Min.x = clamp_x and (inner_rect_rel.Min.x + pad_x) or -FLT_MAX inner_rect_rel.Max.x = clamp_x and (inner_rect_rel.Max.x - pad_x) or FLT_MAX inner_rect_rel.Min.y = clamp_y and (inner_rect_rel.Min.y + pad_y) or -FLT_MAX inner_rect_rel.Max.y = clamp_y and (inner_rect_rel.Max.y - pad_y) or FLT_MAX window.NavRectRel[g.NavLayer]:ClipWithFull(inner_rect_rel) g.NavId = 0 end end local scoring_rect = ImRect() if window ~= nil then local nav_rect_rel = ImRect() if not window.NavRectRel[g.NavLayer]:IsInverted() then ImRect_Copy(nav_rect_rel, window.NavRectRel[g.NavLayer]) end scoring_rect = ImGui.WindowRectRelToAbs(window, nav_rect_rel) if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.IsPageMove) ~= 0 then if window.InnerRect:Contains(scoring_rect) then g.NavMoveFlags = bit32.bor(g.NavMoveFlags, ImGuiNavMoveFlags.AlsoScoreVisibleSet) end ImRect_Copy(g.NavScoringNoClipRect, scoring_rect) scoring_rect:TranslateY(scoring_page_offset_y) g.NavScoringNoClipRect:Add(scoring_rect) end if g.NavMoveSubmitted then ImGui.NavBiasScoringRect(scoring_rect, window.RootWindowForNav.NavPreferredScoringPosRel[g.NavLayer], g.NavMoveDir, g.NavMoveFlags) end IM_ASSERT(not scoring_rect:IsInverted()) end ImRect_Copy(g.NavScoringRect, scoring_rect) end function ImGui.NavUpdateCreateTabbingRequest() local g = GImGui local window = g.NavWindow IM_ASSERT(g.NavMoveDir == ImGuiDir.None) if window == nil or g.NavWindowingTarget ~= nil or bit32.band(window.Flags, ImGuiWindowFlags.NoNavInputs) ~= 0 or not g.ConfigNavEnableTabbing then return end local tab_pressed = ImGui.IsKeyPressedEx(ImGuiKey.Tab, ImGuiInputFlags.Repeat, ImGuiKeyOwner_NoOwner) and not g.IO.KeyCtrl and not g.IO.KeyAlt if not tab_pressed then return end local nav_keyboard_active = bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard) ~= 0 if nav_keyboard_active then g.NavTabbingDir = g.IO.KeyShift and -1 or ((g.NavCursorVisible == false and g.ActiveId == 0) and 0 or 1) else g.NavTabbingDir = g.IO.KeyShift and -1 or ((g.ActiveId == 0) and 0 or 1) end local move_flags = bit32.bor(ImGuiNavMoveFlags.IsTabbing, ImGuiNavMoveFlags.Activate) local scroll_flags = window.Appearing and bit32.bor(ImGuiScrollFlags.KeepVisibleEdgeX, ImGuiScrollFlags.AlwaysCenterY) or bit32.bor(ImGuiScrollFlags.KeepVisibleEdgeX, ImGuiScrollFlags.KeepVisibleEdgeY) local clip_dir = (g.NavTabbingDir < 0) and ImGuiDir.Up or ImGuiDir.Down ImGui.NavMoveRequestSubmit(ImGuiDir.None, clip_dir, move_flags, scroll_flags) g.NavTabbingCounter = -1 end function ImGui.NavUpdate() local g = GImGui local io = g.IO io.WantSetMousePos = false local nav_gamepad_active = bit32.band(io.ConfigFlags, ImGuiConfigFlags.NavEnableGamepad) ~= 0 and bit32.band(io.BackendFlags, ImGuiBackendFlags.HasGamepad) ~= 0 if nav_gamepad_active and g.NavInputSource ~= ImGuiInputSource.Gamepad then for _, key in ipairs(nav_gamepad_keys_to_change_source) do if ImGui.IsKeyDown(key) then g.NavInputSource = ImGuiInputSource.Gamepad end end end local nav_keyboard_active = bit32.band(io.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard) ~= 0 if nav_keyboard_active and g.NavInputSource ~= ImGuiInputSource.Keyboard then for _, key in ipairs(nav_keyboard_keys_to_change_source) do if ImGui.IsKeyDown(key) then g.NavInputSource = ImGuiInputSource.Keyboard end end end g.NavJustMovedToId = 0 g.NavJustMovedToFocusScopeId = 0 g.NavJustMovedFromFocusScopeId = 0 if g.NavInitResult.ID ~= 0 then ImGui.NavInitRequestApplyResult() end g.NavInitRequest = false g.NavInitRequestFromMove = false g.NavInitResult.ID = 0 if g.NavMoveSubmitted then ImGui.NavMoveRequestApplyResult() end g.NavTabbingCounter = 0 g.NavMoveSubmitted = false g.NavMoveScoringItems = false if g.NavCursorHideFrames > 0 then g.NavCursorHideFrames = g.NavCursorHideFrames - 1 if g.NavCursorHideFrames == 0 then g.NavCursorVisible = true end end local set_mouse_pos = false if g.NavMousePosDirty and g.NavIdIsAlive then if g.NavCursorVisible and g.NavHighlightItemUnderNav and g.NavWindow then set_mouse_pos = true end end g.NavMousePosDirty = false IM_ASSERT(g.NavLayer == ImGuiNavLayer.Main or g.NavLayer == ImGuiNavLayer.Menu) if g.NavWindow then ImGui.NavSaveLastChildNavWindowIntoParent(g.NavWindow) end if g.NavWindow and g.NavWindow.NavLastChildNavWindow ~= nil and g.NavLayer == ImGuiNavLayer.Main then g.NavWindow.NavLastChildNavWindow = nil end ImGui.NavUpdateWindowing() io.NavActive = (nav_keyboard_active or nav_gamepad_active) and g.NavWindow and bit32.band(g.NavWindow.Flags, ImGuiWindowFlags.NoNavInputs) == 0 io.NavVisible = (io.NavActive and g.NavId ~= 0 and g.NavCursorVisible) or (g.NavWindowingTarget ~= nil) ImGui.NavUpdateCancelRequest() ImGui.NavUpdateContextMenuRequest() g.NavActivateId = 0 g.NavActivateDownId = 0 g.NavActivatePressedId = 0 g.NavActivateFlags = ImGuiActivateFlags.None if g.NavId ~= 0 and g.NavCursorVisible and not g.NavWindowingTarget and g.NavWindow and bit32.band(g.NavWindow.Flags, ImGuiWindowFlags.NoNavInputs) == 0 then local activate_down = (nav_keyboard_active and ImGui.IsKeyDown(ImGuiKey.Space, ImGuiKeyOwner_NoOwner)) or (nav_gamepad_active and ImGui.IsKeyDown(ImGuiKey.NavGamepadActivate, ImGuiKeyOwner_NoOwner)) local activate_pressed = activate_down and ((nav_keyboard_active and ImGui.IsKeyPressedEx(ImGuiKey.Space, ImGuiInputFlags.None, ImGuiKeyOwner_NoOwner)) or (nav_gamepad_active and ImGui.IsKeyPressedEx(ImGuiKey.NavGamepadActivate, ImGuiInputFlags.None, ImGuiKeyOwner_NoOwner))) local input_pressed_keyboard = nav_keyboard_active and (ImGui.IsKeyPressedEx(ImGuiKey.Enter, ImGuiInputFlags.None, ImGuiKeyOwner_NoOwner) or ImGui.IsKeyPressedEx(ImGuiKey.KeypadEnter, ImGuiInputFlags.None, ImGuiKeyOwner_NoOwner)) local input_pressed_gamepad = false if activate_down and nav_gamepad_active and ImGui.IsKeyDown(ImGuiKey.NavGamepadActivate, ImGuiKeyOwner_NoOwner) and bit32.band(g.NavIdItemFlags, ImGuiItemFlags.Inputable) ~= 0 then if ImGui.GetKeyData(g, ImGuiKey.NavGamepadActivate).DownDurationPrev < NAV_ACTIVATE_INPUT_WITH_GAMEPAD_DELAY and ImGui.GetKeyData(g, ImGuiKey.NavGamepadActivate).DownDuration >= NAV_ACTIVATE_INPUT_WITH_GAMEPAD_DELAY then input_pressed_gamepad = true end end if g.ActiveId == 0 and activate_pressed then g.NavActivateId = g.NavId g.NavActivateFlags = ImGuiActivateFlags.PreferTweak end if (g.ActiveId == 0 or g.ActiveId == g.NavId) and (input_pressed_keyboard or input_pressed_gamepad) then g.NavActivateId = g.NavId g.NavActivateFlags = ImGuiActivateFlags.PreferInput end if (g.ActiveId == 0 or g.ActiveId == g.NavId) and (activate_down or input_pressed_keyboard or input_pressed_gamepad) then g.NavActivateDownId = g.NavId end if (g.ActiveId == 0 or g.ActiveId == g.NavId) and (activate_pressed or input_pressed_keyboard or input_pressed_gamepad) then g.NavActivatePressedId = g.NavId ImGui.NavHighlightActivated(g.NavId) end end if g.NavWindow and bit32.band(g.NavWindow.Flags, ImGuiWindowFlags.NoNavInputs) ~= 0 then g.NavCursorVisible = false elseif g.IO.ConfigNavCursorVisibleAlways and g.NavCursorHideFrames == 0 then g.NavCursorVisible = true end if g.NavActivateId ~= 0 then IM_ASSERT(g.NavActivateDownId == g.NavActivateId) end if g.NavHighlightActivatedTimer > 0.0 then g.NavHighlightActivatedTimer = ImMax(0.0, g.NavHighlightActivatedTimer - io.DeltaTime) end if g.NavHighlightActivatedTimer == 0.0 then g.NavHighlightActivatedId = 0 end if g.NavNextActivateId ~= 0 then g.NavActivateId = g.NavNextActivateId g.NavActivateDownId = g.NavNextActivateId g.NavActivatePressedId = g.NavNextActivateId g.NavActivateFlags = g.NavNextActivateFlags end g.NavNextActivateId = 0 ImGui.NavUpdateCreateMoveRequest() if g.NavMoveDir == ImGuiDir.None then ImGui.NavUpdateCreateTabbingRequest() end ImGui.NavUpdateAnyRequestFlag() g.NavIdIsAlive = false if g.NavWindow and bit32.band(g.NavWindow.Flags, ImGuiWindowFlags.NoNavInputs) == 0 and not g.NavWindowingTarget then local window = g.NavWindow local scroll_speed = IM_ROUND(window.FontRefSize * 100 * io.DeltaTime) local move_dir = g.NavMoveDir if window.DC.NavLayersActiveMask == 0x00 and window.DC.NavWindowHasScrollY and move_dir ~= ImGuiDir.None then if move_dir == ImGuiDir.Left or move_dir == ImGuiDir.Right then local delta = (move_dir == ImGuiDir.Left) and -1.0 or 1.0 ImGui.SetScrollX(window, ImTrunc(window.Scroll.x + delta * scroll_speed)) end if move_dir == ImGuiDir.Up or move_dir == ImGuiDir.Down then local delta = (move_dir == ImGuiDir.Up) and -1.0 or 1.0 ImGui.SetScrollY(window, ImTrunc(window.Scroll.y + delta * scroll_speed)) end end if nav_gamepad_active then local scroll_dir = ImGui.GetKeyMagnitude2d(ImGuiKey.GamepadLStickLeft, ImGuiKey.GamepadLStickRight, ImGuiKey.GamepadLStickUp, ImGuiKey.GamepadLStickDown) local tweak_factor = 1.0 if ImGui.IsKeyDown(ImGuiKey.NavGamepadTweakSlow) then tweak_factor = 1.0 / 10.0 elseif ImGui.IsKeyDown(ImGuiKey.NavGamepadTweakFast) then tweak_factor = 10.0 end if scroll_dir.x ~= 0.0 and window.ScrollbarX then ImGui.SetScrollX(window, ImTrunc(window.Scroll.x + scroll_dir.x * scroll_speed * tweak_factor)) end if scroll_dir.y ~= 0.0 then ImGui.SetScrollY(window, ImTrunc(window.Scroll.y + scroll_dir.y * scroll_speed * tweak_factor)) end end end if not nav_keyboard_active and not nav_gamepad_active then g.NavCursorVisible = false g.NavHighlightItemUnderNav = false set_mouse_pos = false end if set_mouse_pos and io.ConfigNavMoveSetMousePos and bit32.band(io.BackendFlags, ImGuiBackendFlags.HasSetMousePos) ~= 0 then ImGui.TeleportMousePos(ImGui.NavCalcPreferredRefPos(ImGuiWindowFlags.Popup)) end g.NavScoringDebugCount = 0 end end function ImGui.NavUpdateCancelRequest() local g = GImGui local nav_gamepad_active = bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.NavEnableGamepad) ~= 0 and bit32.band(g.IO.BackendFlags, ImGuiBackendFlags.HasGamepad) ~= 0 local nav_keyboard_active = bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard) ~= 0 if not (nav_keyboard_active and ImGui.IsKeyPressedEx(ImGuiKey.Escape, 0, ImGuiKeyOwner_NoOwner)) and not (nav_gamepad_active and ImGui.IsKeyPressedEx(ImGuiKey.NavGamepadCancel, 0, ImGuiKeyOwner_NoOwner)) then return end -- IMGUI_DEBUG_LOG_NAV("[nav] NavUpdateCancelRequest()") if g.ActiveId ~= 0 then ImGui.ClearActiveID() elseif g.NavLayer ~= ImGuiNavLayer.Main then ImGui.NavRestoreLayer(ImGuiNavLayer.Main) ImGui.SetNavCursorVisibleAfterMove() elseif g.NavWindow and g.NavWindow ~= g.NavWindow.RootWindow and bit32.band(g.NavWindow.RootWindowForNav.Flags, ImGuiWindowFlags.Popup) == 0 and g.NavWindow.RootWindowForNav.ParentWindow then local child_window = g.NavWindow.RootWindowForNav local parent_window = child_window.ParentWindow IM_ASSERT(child_window.ChildId ~= 0) ImGui.FocusWindow(parent_window) ImGui.SetNavID(child_window.ChildId, ImGuiNavLayer.Main, 0, ImGui.WindowRectAbsToRel(parent_window, child_window:Rect())) ImGui.SetNavCursorVisibleAfterMove() elseif g.OpenPopupStack.Size > 0 and g.OpenPopupStack:back().Window ~= nil and bit32.band(g.OpenPopupStack:back().Window.Flags, ImGuiWindowFlags.Modal) == 0 then ImGui.ClosePopupToLevel(g.OpenPopupStack.Size - 1, true) else if g.IO.ConfigNavEscapeClearFocusItem or g.IO.ConfigNavEscapeClearFocusWindow then if g.NavWindow and bit32.band(g.NavWindow.Flags, ImGuiWindowFlags.Popup) ~= 0 then g.NavWindow.NavLastIds[0] = 0 end end if g.IO.ConfigNavEscapeClearFocusItem or g.IO.ConfigNavEscapeClearFocusWindow then g.NavId = 0 end if g.IO.ConfigNavEscapeClearFocusWindow then ImGui.FocusWindow(nil) end end end function ImGui.NavMoveRequestButNoResultYet() local g = GImGui return g.NavMoveScoringItems and g.NavMoveResultLocal.ID == 0 and g.NavMoveResultOther.ID == 0 end local function ImGetDirQuadrantFromDelta(dx, dy) if ImFabs(dx) > ImFabs(dy) then return (dx > 0.0) and ImGuiDir.Right or ImGuiDir.Left end return (dy > 0.0) and ImGuiDir.Down or ImGuiDir.Up end local function NavScoreItemDistInterval(cand_min, cand_max, curr_min, curr_max) if cand_max < curr_min then return cand_max - curr_min end if curr_max < cand_min then return cand_min - curr_max end return 0.0 end --- @param result ImGuiNavItemData --- @param nav_bb ImRect --- @return bool function ImGui.NavScoreItem(result, nav_bb) local g = GImGui local window = g.CurrentWindow if g.NavLayer ~= window.DC.NavLayerCurrent then return false end local cand = ImRect() ImRect_Copy(cand, nav_bb) local curr = g.NavScoringRect g.NavScoringDebugCount = g.NavScoringDebugCount + 1 if window.ParentWindow == g.NavWindow then IM_ASSERT(bit32.band(bit32.bor(window.ChildFlags, g.NavWindow.ChildFlags), ImGuiChildFlags.NavFlattened) ~= 0) if not window.ClipRect:Overlaps(cand) then return false end cand:ClipWithFull(window.ClipRect) end local dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x) local dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2), ImLerp(cand.Min.y, cand.Max.y, 0.8), ImLerp(curr.Min.y, curr.Max.y, 0.2), ImLerp(curr.Min.y, curr.Max.y, 0.8)) if dby ~= 0.0 and dbx ~= 0.0 then dbx = (dbx / 1000.0) + ((dbx > 0.0) and 1.0 or -1.0) end local dist_box = ImFabs(dbx) + ImFabs(dby) local dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x) local dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y) local dist_center = ImFabs(dcx) + ImFabs(dcy) local quadrant local dax = 0.0 local day = 0.0 local dist_axial = 0.0 if dbx ~= 0.0 or dby ~= 0.0 then dax = dbx day = dby dist_axial = dist_box quadrant = ImGetDirQuadrantFromDelta(dbx, dby) elseif dcx ~= 0.0 or dcy ~= 0.0 then dax = dcx day = dcy dist_axial = dist_center quadrant = ImGetDirQuadrantFromDelta(dcx, dcy) else quadrant = (g.LastItemData.ID < g.NavId) and ImGuiDir.Left or ImGuiDir.Right end local move_dir = g.NavMoveDir local new_best = false if quadrant == move_dir then if dist_box < result.DistBox then result.DistBox = dist_box result.DistCenter = dist_center return true end if dist_box == result.DistBox then if dist_center < result.DistCenter then result.DistCenter = dist_center new_best = true elseif dist_center == result.DistCenter then if (((move_dir == ImGuiDir.Up or move_dir == ImGuiDir.Down) and dby) or dbx) < 0.0 then new_best = true end end end end if result.DistBox == FLT_MAX and dist_axial < result.DistAxial then if g.NavLayer == ImGuiNavLayer.Menu and bit32.band(g.NavWindow.Flags, ImGuiWindowFlags.ChildMenu) == 0 then if (move_dir == ImGuiDir.Left and dax < 0.0) or (move_dir == ImGuiDir.Right and dax > 0.0) or (move_dir == ImGuiDir.Up and day < 0.0) or (move_dir == ImGuiDir.Down and day > 0.0) then result.DistAxial = dist_axial new_best = true end end end return new_best end --- @param result ImGuiNavItemData function ImGui.NavApplyItemToResult(result) local g = GImGui local window = g.CurrentWindow result.Window = window result.ID = g.LastItemData.ID result.FocusScopeId = g.CurrentFocusScopeId result.ItemFlags = g.LastItemData.ItemFlags ImRect_Copy(result.RectRel, ImGui.WindowRectAbsToRel(window, g.LastItemData.NavRect)) if bit32.band(result.ItemFlags, ImGuiItemFlags.HasSelectionUserData) ~= 0 then IM_ASSERT(g.NextItemData.SelectionUserData ~= ImGuiSelectionUserData_Invalid) result.SelectionUserData = g.NextItemData.SelectionUserData end end --- @param result ImGuiNavItemData function ImGui.NavMoveRequestResolveWithLastItem(result) local g = GImGui g.NavMoveScoringItems = false ImGui.NavApplyItemToResult(result) ImGui.NavUpdateAnyRequestFlag() end --- @param id ImGuiID --- @param item_flags ImGuiItemFlags --- @param move_flags ImGuiNavMoveFlags function ImGui.NavProcessItemForTabbingRequest(id, item_flags, move_flags) local g = GImGui if bit32.band(move_flags, ImGuiNavMoveFlags.FocusApi) == 0 then if g.NavLayer ~= g.CurrentWindow.DC.NavLayerCurrent then return end if g.NavFocusScopeId ~= g.CurrentFocusScopeId then return end end local can_stop if bit32.band(move_flags, ImGuiNavMoveFlags.FocusApi) ~= 0 then can_stop = true else can_stop = bit32.band(item_flags, ImGuiItemFlags.NoTabStop) == 0 and (bit32.band(g.IO.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard) ~= 0 or bit32.band(item_flags, ImGuiItemFlags.Inputable) ~= 0) end local result = g.NavMoveResultLocal if g.NavTabbingDir == 1 then if can_stop and g.NavTabbingResultFirst.ID == 0 then ImGui.NavApplyItemToResult(g.NavTabbingResultFirst) end if can_stop and g.NavTabbingCounter > 0 then g.NavTabbingCounter = g.NavTabbingCounter - 1 if g.NavTabbingCounter == 0 then ImGui.NavMoveRequestResolveWithLastItem(result) end elseif g.NavId == id then g.NavTabbingCounter = 1 end elseif g.NavTabbingDir == -1 then if g.NavId == id then if result.ID ~= 0 then g.NavMoveScoringItems = false ImGui.NavUpdateAnyRequestFlag() end elseif can_stop then ImGui.NavApplyItemToResult(result) end elseif g.NavTabbingDir == 0 then if can_stop and g.NavId == id then ImGui.NavMoveRequestResolveWithLastItem(result) end if can_stop and g.NavTabbingResultFirst.ID == 0 then ImGui.NavApplyItemToResult(g.NavTabbingResultFirst) end end end function ImGui.NavProcessItem() local g = GImGui local window = g.CurrentWindow local id = g.LastItemData.ID local item_flags = g.LastItemData.ItemFlags local nav_bb = ImRect() ImRect_Copy(nav_bb, g.LastItemData.NavRect) if window.DC.NavIsScrollPushableX == false then nav_bb.Min.x = ImClamp(nav_bb.Min.x, window.ClipRect.Min.x, window.ClipRect.Max.x) nav_bb.Max.x = ImClamp(nav_bb.Max.x, window.ClipRect.Min.x, window.ClipRect.Max.x) end if g.NavInitRequest and g.NavLayer == window.DC.NavLayerCurrent and bit32.band(item_flags, ImGuiItemFlags.Disabled) == 0 then local candidate_for_nav_default_focus = bit32.band(item_flags, ImGuiItemFlags.NoNavDefaultFocus) == 0 if candidate_for_nav_default_focus or g.NavInitResult.ID == 0 then ImGui.NavApplyItemToResult(g.NavInitResult) end if candidate_for_nav_default_focus then g.NavInitRequest = false ImGui.NavUpdateAnyRequestFlag() end end if g.NavMoveScoringItems and bit32.band(item_flags, ImGuiItemFlags.Disabled) == 0 then if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.FocusApi) ~= 0 or bit32.band(window.Flags, ImGuiWindowFlags.NoNavInputs) == 0 then local is_tabbing = bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.IsTabbing) ~= 0 if is_tabbing then ImGui.NavProcessItemForTabbingRequest(id, item_flags, g.NavMoveFlags) elseif g.NavId ~= id or bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.AllowCurrentNavId) ~= 0 then local result = (window == g.NavWindow) and g.NavMoveResultLocal or g.NavMoveResultOther if ImGui.NavScoreItem(result, nav_bb) then ImGui.NavApplyItemToResult(result) end local VISIBLE_RATIO = 0.70 if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.AlsoScoreVisibleSet) ~= 0 then local r = window.InnerRect if r:Overlaps(nav_bb) then if ImClamp(nav_bb.Max.y, r.Min.y, r.Max.y) - ImClamp(nav_bb.Min.y, r.Min.y, r.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO then if ImGui.NavScoreItem(g.NavMoveResultLocalVisible, nav_bb) then ImGui.NavApplyItemToResult(g.NavMoveResultLocalVisible) end end end end end end end if g.NavId == id then if g.NavWindow ~= window then ImGui.SetNavWindow(window) end g.NavLayer = window.DC.NavLayerCurrent ImGui.SetNavFocusScope(g.CurrentFocusScopeId) g.NavFocusScopeId = g.CurrentFocusScopeId g.NavIdIsAlive = true g.NavIdItemFlags = item_flags if bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.HasSelectionUserData) ~= 0 then IM_ASSERT(g.NextItemData.SelectionUserData ~= ImGuiSelectionUserData_Invalid) g.NavLastValidSelectionUserData = g.NextItemData.SelectionUserData end window.NavRectRel[window.DC.NavLayerCurrent] = ImGui.WindowRectAbsToRel(window, nav_bb) end end function ImGui.NavInitRequestApplyResult() local g = GImGui if not g.NavWindow then return end local result = g.NavInitResult if g.NavId ~= result.ID then g.NavJustMovedFromFocusScopeId = g.NavFocusScopeId g.NavJustMovedToId = result.ID g.NavJustMovedToFocusScopeId = result.FocusScopeId g.NavJustMovedToKeyMods = 0 g.NavJustMovedToIsTabbing = false g.NavJustMovedToHasSelectionData = bit32.band(result.ItemFlags, ImGuiItemFlags.HasSelectionUserData) ~= 0 end ImGui.SetNavID(result.ID, g.NavLayer, result.FocusScopeId, result.RectRel) g.NavIdIsAlive = true if result.SelectionUserData ~= ImGuiSelectionUserData_Invalid then g.NavLastValidSelectionUserData = result.SelectionUserData end if g.NavInitRequestFromMove then ImGui.SetNavCursorVisibleAfterMove() end end function ImGui.NavMoveRequestApplyResult() local g = GImGui local result if g.NavMoveResultLocal.ID ~= 0 then result = g.NavMoveResultLocal elseif g.NavMoveResultOther.ID ~= 0 then result = g.NavMoveResultOther else result = nil end if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.IsTabbing) ~= 0 and result == nil then if (g.NavTabbingCounter == 1 or g.NavTabbingDir == 0) and g.NavTabbingResultFirst.ID ~= 0 then result = g.NavTabbingResultFirst end end local axis = (g.NavMoveDir == ImGuiDir.Up or g.NavMoveDir == ImGuiDir.Down) and ImGuiAxis.Y or ImGuiAxis.X if result == nil then if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.IsTabbing) ~= 0 then g.NavMoveFlags = bit32.bor(g.NavMoveFlags, ImGuiNavMoveFlags.NoSetNavCursorVisible) end if g.NavId ~= 0 and bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.NoSetNavCursorVisible) == 0 then ImGui.SetNavCursorVisibleAfterMove() end ImGui.NavClearPreferredPosForAxis(axis) return end if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.AlsoScoreVisibleSet) ~= 0 and g.NavMoveResultLocalVisible.ID ~= 0 and g.NavMoveResultLocalVisible.ID ~= g.NavId then result = g.NavMoveResultLocalVisible end if result ~= g.NavMoveResultOther and g.NavMoveResultOther.ID ~= 0 and g.NavMoveResultOther.Window.ParentWindow == g.NavWindow then if g.NavMoveResultOther.DistBox < result.DistBox or (g.NavMoveResultOther.DistBox == result.DistBox and g.NavMoveResultOther.DistCenter < result.DistCenter) then result = g.NavMoveResultOther end end IM_ASSERT(g.NavWindow and result.Window) if g.NavLayer == ImGuiNavLayer.Main then local rect_abs = ImGui.WindowRectRelToAbs(result.Window, result.RectRel) ImGui.ScrollToRectEx(result.Window, rect_abs, g.NavMoveScrollFlags) if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.ScrollToEdgeY) ~= 0 then local scroll_target = (g.NavMoveDir == ImGuiDir.Up) and result.Window.ScrollMax.y or 0.0 ImGui.SetScrollY(result.Window, scroll_target) end end if g.NavWindow ~= result.Window then g.NavWindow = result.Window g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid end if g.ActiveId ~= result.ID and bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.NoClearActiveId) == 0 then ImGui.ClearActiveID() end if (g.NavId ~= result.ID or bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.IsPageMove) ~= 0) and bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.NoSelect) == 0 then g.NavJustMovedFromFocusScopeId = g.NavFocusScopeId g.NavJustMovedToId = result.ID g.NavJustMovedToFocusScopeId = result.FocusScopeId g.NavJustMovedToKeyMods = g.NavMoveKeyMods g.NavJustMovedToIsTabbing = bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.IsTabbing) ~= 0 g.NavJustMovedToHasSelectionData = bit32.band(result.ItemFlags, ImGuiItemFlags.HasSelectionUserData) ~= 0 end local root_for_nav = g.NavWindow.RootWindowForNav local preferred_scoring_pos_rel = ImVec2() ImVec2_Copy(preferred_scoring_pos_rel, root_for_nav.NavPreferredScoringPosRel[g.NavLayer]) ImGui.SetNavID(result.ID, g.NavLayer, result.FocusScopeId, result.RectRel) if result.SelectionUserData ~= ImGuiSelectionUserData_Invalid then g.NavLastValidSelectionUserData = result.SelectionUserData end if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.IsTabbing) == 0 then preferred_scoring_pos_rel[axis] = result.RectRel:GetCenter()[axis] ImVec2_Copy(root_for_nav.NavPreferredScoringPosRel[g.NavLayer], preferred_scoring_pos_rel) end if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.IsTabbing) ~= 0 and bit32.band(result.ItemFlags, ImGuiItemFlags.Inputable) == 0 then g.NavMoveFlags = bit32.band(g.NavMoveFlags, bit32.bnot(ImGuiNavMoveFlags.Activate)) end if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.Activate) ~= 0 then g.NavNextActivateId = result.ID g.NavNextActivateFlags = ImGuiActivateFlags.None if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.FocusApi) ~= 0 then g.NavNextActivateFlags = bit32.bor(g.NavNextActivateFlags, ImGuiActivateFlags.FromFocusApi) end if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.IsTabbing) ~= 0 then g.NavNextActivateFlags = bit32.bor(g.NavNextActivateFlags, ImGuiActivateFlags.PreferInput, ImGuiActivateFlags.TryToPreserveState, ImGuiActivateFlags.FromTabbing) end end if bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.NoSetNavCursorVisible) == 0 then ImGui.SetNavCursorVisibleAfterMove() end end function ImGui.NavUpdateCurrentWindowIsScrollPushableX() local g = GImGui local window = g.CurrentWindow window.DC.NavIsScrollPushableX = (g.CurrentTable == nil and window.DC.CurrentColumns == nil) end --- @param result ImGuiNavItemData --- @param tree_node_data ImGuiTreeNodeStackData function ImGui.NavMoveRequestResolveWithPastTreeNode(result, tree_node_data) local g = GImGui g.NavMoveScoringItems = false g.LastItemData.ID = tree_node_data.ID g.LastItemData.ItemFlags = bit32.band(tree_node_data.ItemFlags, bit32.bnot(ImGuiItemFlags.HasSelectionUserData)) ImRect_Copy(g.LastItemData.NavRect, tree_node_data.NavRect) ImGui.NavApplyItemToResult(result) ImGui.NavClearPreferredPosForAxis(ImGuiAxis.Y) ImGui.NavUpdateAnyRequestFlag() end function ImGui.NavMoveRequestCancel() local g = GImGui g.NavMoveSubmitted = false g.NavMoveScoringItems = false ImGui.NavUpdateAnyRequestFlag() end --- @param move_dir ImGuiDir --- @param clip_dir ImGuiDir --- @param move_flags ImGuiNavMoveFlags --- @param scroll_flags ImGuiScrollFlags function ImGui.NavMoveRequestForward(move_dir, clip_dir, move_flags, scroll_flags) local g = GImGui IM_ASSERT(g.NavMoveForwardToNextFrame == false) ImGui.NavMoveRequestCancel() g.NavMoveForwardToNextFrame = true g.NavMoveDir = move_dir g.NavMoveClipDir = clip_dir g.NavMoveFlags = bit32.bor(move_flags, ImGuiNavMoveFlags.Forwarded) g.NavMoveScrollFlags = scroll_flags end --- @param window ImGuiWindow --- @param wrap_flags ImGuiNavMoveFlags function ImGui.NavMoveRequestTryWrapping(window, wrap_flags) local g = GImGui IM_ASSERT(bit32.band(wrap_flags, ImGuiNavMoveFlags.WrapMask_) ~= 0 and bit32.band(wrap_flags, bit32.bnot(ImGuiNavMoveFlags.WrapMask_)) == 0) if g.NavWindow == window and g.NavMoveScoringItems and g.NavLayer == window.DC.NavLayerCurrent then g.NavMoveFlags = bit32.bor(bit32.band(g.NavMoveFlags, bit32.bnot(ImGuiNavMoveFlags.WrapMask_)), wrap_flags) end end --------------------------------------------------------------------------------------- -- [SECTION] DRAG AND DROP --------------------------------------------------------------------------------------- function ImGui.IsDragDropPayloadBeingAccepted() local g = GImGui return g.DragDropActive and g.DragDropAcceptIdPrev ~= 0 end --------------------------------------------------------------------------------------- -- [SECTION] LOCALIZATION --------------------------------------------------------------------------------------- --- @param entries ImGuiLocEntry[] --- @param count int function ImGui.LocalizeRegisterEntries(entries, count) local g = GImGui for n = 1, count do g.LocalizationTable[entries[n].Key] = entries[n].Text end end --------------------------------------------------------------------------------------- -- [SECTION] VIEWPORTS, PLATFORM WINDOWS --------------------------------------------------------------------------------------- function MT.ImGuiViewport:GetDebugName() local viewport = self return viewport.Window and viewport.Window.Name or "n/a" end function MT.ImGuiPlatformIO:ClearPlatformHandlers() self.Platform_GetClipboardTextFn = nil; self.Platform_SetClipboardTextFn = nil self.Platform_ClipboardUserData = nil self.Platform_OpenInShellFn = nil; self.Platform_OpenInShellUserData = nil self.Platform_SetImeDataFn = nil; self.Platform_ImeUserData = nil self.Platform_CreateWindow = nil; self.Platform_DestroyWindow = nil; self.Platform_ShowWindow = nil self.Platform_SetWindowPos = nil; self.Platform_SetWindowSize = nil self.Platform_GetWindowPos = nil; self.Platform_GetWindowSize = nil; self.Platform_GetWindowFramebufferScale = nil self.Platform_SetWindowFocus = nil; self.Platform_GetWindowFocus = nil; self.Platform_GetWindowMinimized = nil self.Platform_SetWindowTitle = nil; self.Platform_SetWindowAlpha = nil; self.Platform_UpdateWindow = nil self.Platform_RenderWindow = nil; self.Platform_SwapBuffers = nil; self.Platform_GetWindowDpiScale = nil self.Platform_OnChangedViewport = nil; self.Platform_GetWindowWorkAreaInsets = nil; self.Platform_CreateVkSurface = nil end function MT.ImGuiPlatformIO:ClearRendererHandlers() self.Renderer_TextureMaxWidth = 0; self.Renderer_TextureMaxHeight = 0 self.Renderer_RenderState = nil self.Renderer_CreateWindow = nil; self.Renderer_DestroyWindow = nil self.Renderer_SetWindowSize = nil self.Renderer_RenderWindow = nil; self.Renderer_SwapBuffers = nil end --- @return ImGuiViewport function ImGui.GetMainViewport() local g = GImGui return g.Viewports[1] end --- @param viewport_id ImGuiID --- @return ImGuiViewport? function ImGui.FindViewportByID(viewport_id) local g = GImGui for _, viewport in g.Viewports:iter() do if viewport.ID == viewport_id then return viewport end end return nil end --- @param current_window? ImGuiWindow --- @param viewport? ImGuiViewportP function ImGui.SetCurrentViewport(current_window, viewport) local g = GImGui if viewport then viewport.LastFrameActive = g.FrameCount end if g.CurrentViewport == viewport then return end g.CurrentDpiScale = viewport and viewport.DpiScale or 1.0 g.CurrentViewport = viewport IM_ASSERT(g.CurrentDpiScale > 0.0 and g.CurrentDpiScale < 99.0) -- Typical correct values would be between 1.0f and 4.0f -- IMGUI_DEBUG_LOG_VIEWPORT("[viewport] SetCurrentViewport changed '%s' 0x%08X", current_window and current_window.Name or "NULL", viewport and viewport.ID or 0) if g.IO.ConfigDpiScaleFonts then g.Style.FontScaleDpi = g.CurrentDpiScale end -- Notify platform layer of viewport changes -- FIXME-DPI: This is only currently used for experimenting with handling of multiple DPI if g.CurrentViewport and g.PlatformIO.Platform_OnChangedViewport then g.PlatformIO.Platform_OnChangedViewport(g.CurrentViewport) end end --- @param window ImGuiWindow --- @param viewport ImGuiViewportP function ImGui.SetWindowViewport(window, viewport) -- Abandon viewport if window.ViewportOwned and window.Viewport.Window == window then window.Viewport.Size = ImVec2(0.0, 0.0) end window.Viewport = viewport window.ViewportId = viewport.ID window.ViewportOwned = (viewport.Window == window) end --- @param window ImGuiWindow --- @return bool function ImGui.GetWindowAlwaysWantOwnViewport(window) -- Tooltips and menus are not automatically forced into their own viewport when the NoMerge flag is set, however the multiplication of viewports makes them more likely to protrude and create their own. local g = GImGui if g.IO.ConfigViewportsNoAutoMerge or (bit32.band(window.WindowClass.ViewportFlagsOverrideSet, ImGuiViewportFlags.NoAutoMerge) ~= 0) then if bit32.band(g.ConfigFlagsCurrFrame, ImGuiConfigFlags.ViewportsEnable) ~= 0 then if not window.DockIsActive then if bit32.band(window.Flags, bit32.bor(ImGuiWindowFlags.ChildWindow, ImGuiWindowFlags.ChildMenu, ImGuiWindowFlags.Tooltip)) == 0 then if bit32.band(window.Flags, ImGuiWindowFlags.Popup) == 0 or bit32.band(window.Flags, ImGuiWindowFlags.Modal) ~= 0 then return true end end end end end return false end --- Heuristic, see #8948: depends on how backends handle OS-level parenting. --- Due to how parent viewport stack is layed out, note that IsViewportAbove(a,b) isn't always the same as !IsViewportAbove(b,a). --- @param potential_above ImGuiViewportP --- @param potential_below ImGuiViewportP --- @return bool function ImGui.IsViewportAbove(potential_above, potential_below) -- If ImGuiBackendFlags.HasParentViewport if set, ->ParentViewport chain should be accurate. local g = GImGui if bit32.band(g.IO.BackendFlags, ImGuiBackendFlags.HasParentViewport) ~= 0 then local v = potential_above --[[@as ImGuiViewport]] while v ~= nil and v.ParentViewport do if v.ParentViewport == potential_below then return true end v = v.ParentViewport end else if potential_above.ParentViewport == potential_below then return true end end if potential_above.LastFocusedStampCount > potential_below.LastFocusedStampCount then return true end return false end --- @param window ImGuiWindow --- @param viewport_dst ImGuiViewportP function ImGui.UpdateTryMergeWindowIntoHostViewport(window, viewport_dst) local g = GImGui IM_ASSERT(window == window.RootWindowDockTree) local viewport_src = window.Viewport -- Current viewport if viewport_src == viewport_dst then return false end if bit32.band(viewport_dst.Flags, ImGuiViewportFlags.CanHostOtherWindows) == 0 then return false end if bit32.band(viewport_dst.Flags, ImGuiViewportFlags.IsMinimized) ~= 0 then return false end if not viewport_dst:GetMainRect():Contains(window:Rect()) then return false end if ImGui.GetWindowAlwaysWantOwnViewport(window) then return false end for _, viewport_obstructing in g.Viewports:iter() do repeat if viewport_obstructing == viewport_src or viewport_obstructing == viewport_dst or not viewport_obstructing.PlatformWindowCreated then break end if viewport_obstructing:GetMainRect():Overlaps(window:Rect()) then if ImGui.IsViewportAbove(viewport_obstructing, viewport_dst) then if viewport_src == nil or ImGui.IsViewportAbove(viewport_src, viewport_obstructing) then return false -- viewport_obstructing is between viewport_src and viewport_dst -> Cannot merge. end end end until true end -- Move to the existing viewport, Move child/hosted windows as well (FIXME-OPT: iterate child) IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Window '%s' merge into Viewport 0X%08X", window.Name, viewport_dst.ID) if window.ViewportOwned then for n = 1, g.Windows.Size do if g.Windows.Data[n].Viewport == viewport_src then ImGui.SetWindowViewport(g.Windows.Data[n], viewport_dst) end end end ImGui.SetWindowViewport(window, viewport_dst) if bit32.band(window.Flags, ImGuiWindowFlags.NoBringToFrontOnFocus) == 0 then ImGui.BringWindowToDisplayFront(window) end return true end -- FIXME: handle 0 to N host viewports --- @param window ImGuiWindow --- @return bool function ImGui.UpdateTryMergeWindowIntoHostViewports(window) local g = GImGui return ImGui.UpdateTryMergeWindowIntoHostViewport(window, g.Viewports.Data[1]) end --- Translate Dear ImGui windows when a Host Viewport has been moved --- (This additionally keeps windows at the same place when ImGuiConfigFlags.ViewportsEnable is toggled!) --- @param viewport ImGuiViewportP --- @param old_pos ImVec2 --- @param new_pos ImVec2 --- @param old_size ImVec2 --- @param new_size ImVec2 function ImGui.TranslateWindowsInViewport(viewport, old_pos, new_pos, old_size, new_size) local g = GImGui -- IMGUI_DEBUG_LOG_VIEWPORT("[viewport] TranslateWindowsInViewport 0x%08X", viewport.ID) IM_ASSERT(viewport.Window == nil and (bit32.band(viewport.Flags, ImGuiViewportFlags.CanHostOtherWindows) ~= 0)) -- 1) We test if ImGuiConfigFlags.ViewportsEnable was just toggled, which allows us to conveniently -- translate imgui windows from OS-window-local to absolute coordinates or vice-versa. -- 2) If it's not going to fit into the new size, keep it at same absolute position. -- One problem with this is that most Win32 applications doesn't update their render while dragging, -- and so the window will appear to teleport when releasing the mouse. local translate_all_windows = (bit32.band(g.ConfigFlagsCurrFrame, ImGuiConfigFlags.ViewportsEnable) ~= bit32.band(g.ConfigFlagsLastFrame, ImGuiConfigFlags.ViewportsEnable)) local test_still_fit_rect = ImRect(old_pos, old_pos + old_size) local delta_pos = new_pos - old_pos for _, window in g.Windows:iter() do -- FIXME-OPT if translate_all_windows or (window.Viewport == viewport and (old_size == new_size or test_still_fit_rect:Contains(window:Rect()))) then ImGui.TranslateWindow(window, delta_pos) end end end -- If the backend doesn't support ImGuiBackendFlags_HasMouseHoveredViewport or doesn't honor ImGuiViewportFlags_NoInputs for it, we do a search ourselves. -- A) It won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window. -- B) It requires Platform_GetWindowFocus to be implemented by backend. --- @param mouse_platform_pos ImVec2 --- @return ImGuiViewportP? function ImGui.FindHoveredViewportFromPlatformWindowStack(mouse_platform_pos) local g = GImGui local best_candidate = nil for _, viewport in g.Viewports:iter() do if bit32.band(viewport.Flags, bit32.bor(ImGuiViewportFlags.NoInputs, ImGuiViewportFlags.IsMinimized)) == 0 and viewport:GetMainRect():Contains(mouse_platform_pos) then if best_candidate == nil or best_candidate.LastFocusedStampCount < viewport.LastFocusedStampCount then if viewport.PlatformWindowCreated then best_candidate = viewport end end end end return best_candidate end function ImGui.UpdateViewportsNewFrame() local g = GImGui IM_ASSERT(g.PlatformIO.Viewports.Size <= g.Viewports.Size) -- Update Minimized status (we need it first in order to decide if we'll apply Pos/Size of the main viewport) -- Update Focused status local viewports_enabled = (bit32.band(g.ConfigFlagsCurrFrame, ImGuiConfigFlags.ViewportsEnable) ~= 0) if viewports_enabled then local focused_viewport = nil for i = 1, g.Viewports.Size do local viewport = g.Viewports.Data[i] local platform_funcs_available = viewport.PlatformWindowCreated if g.PlatformIO.Platform_GetWindowMinimized and platform_funcs_available then local is_minimized = g.PlatformIO.Platform_GetWindowMinimized(viewport) if is_minimized then viewport.Flags = bit32.bor(viewport.Flags, ImGuiViewportFlags.IsMinimized) else viewport.Flags = bit32.band(viewport.Flags, bit32.bnot(ImGuiViewportFlags.IsMinimized)) end end -- Update our implicit z-order knowledge of platform windows, which is used when the backend cannot provide io.MouseHoveredViewport. -- When setting Platform_GetWindowFocus, it is expected that the platform backend can handle calls without crashing if it doesn't have data stored. if g.PlatformIO.Platform_GetWindowFocus and platform_funcs_available then local is_focused = g.PlatformIO.Platform_GetWindowFocus(viewport) if is_focused then viewport.Flags = bit32.bor(viewport.Flags, ImGuiViewportFlags.IsFocused) else viewport.Flags = bit32.band(viewport.Flags, bit32.bnot(ImGuiViewportFlags.IsFocused)) end if is_focused then focused_viewport = viewport end end end -- Focused viewport has changed? if focused_viewport and g.PlatformLastFocusedViewportId ~= focused_viewport.ID then IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Focused viewport changed %08X -> %08X '%s', attempting to apply our focus.", g.PlatformLastFocusedViewportId, focused_viewport.ID, focused_viewport.Window and focused_viewport.Window.Name or "n/a") local prev_focused_viewport = ImGui.FindViewportByID(g.PlatformLastFocusedViewportId) local prev_focused_has_been_destroyed = (prev_focused_viewport == nil) or (not prev_focused_viewport.PlatformWindowCreated) -- Store a tag so we can infer z-order easily from all our windows -- We compare PlatformLastFocusedViewportId so newly created viewports with _NoFocusOnAppearing flag -- will keep the front most stamp instead of losing it back to their parent viewport. if focused_viewport.LastFocusedStampCount ~= g.ViewportFocusedStampCount then g.ViewportFocusedStampCount = g.ViewportFocusedStampCount + 1 focused_viewport.LastFocusedStampCount = g.ViewportFocusedStampCount end g.PlatformLastFocusedViewportId = focused_viewport.ID -- Focus associated dear imgui window -- - if focus didn't happen with a click within imgui boundaries, e.g. Clicking platform title bar. (#6299) -- - if focus didn't happen because we destroyed another window (#6462) -- FIXME: perhaps 'FocusTopMostWindowUnderOne()' can handle the 'focused_window.Window ~= nil' case as well. local apply_imgui_focus_on_focused_viewport = not ImGui.IsAnyMouseDown() and not prev_focused_has_been_destroyed if apply_imgui_focus_on_focused_viewport and g.IO.ConfigViewportsPlatformFocusSetsImGuiFocus then focused_viewport.LastFocusedHadNavWindow = focused_viewport.LastFocusedHadNavWindow or (g.NavWindow ~= nil and g.NavWindow.Viewport == focused_viewport) -- Update so a window changing viewport won't lose focus. local focus_request_flags = bit32.bor(ImGuiFocusRequestFlags.UnlessBelowModal, ImGuiFocusRequestFlags.RestoreFocusedChild) if focused_viewport.Window ~= nil then ImGui.FocusWindow(focused_viewport.Window, focus_request_flags) elseif focused_viewport.LastFocusedHadNavWindow then ImGui.FocusTopMostWindowUnderOne(nil, nil, focused_viewport, focus_request_flags) -- Focus top most in viewport else ImGui.FocusWindow(nil, focus_request_flags) -- No window had focus last time viewport was focused end end end if focused_viewport then focused_viewport.LastFocusedHadNavWindow = (g.NavWindow ~= nil) and (g.NavWindow.Viewport == focused_viewport) end end -- Create/update main viewport with current platform position. -- FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this more consistent. local main_viewport = g.Viewports.Data[1] IM_ASSERT(main_viewport.ID == IMGUI_VIEWPORT_DEFAULT_ID) IM_ASSERT(main_viewport.Window == nil) local main_viewport_pos if viewports_enabled then main_viewport_pos = g.PlatformIO.Platform_GetWindowPos(main_viewport) else main_viewport_pos = ImVec2(0.0, 0.0) end local main_viewport_size = g.IO.DisplaySize local main_viewport_framebuffer_scale = g.IO.DisplayFramebufferScale if viewports_enabled and (bit32.band(main_viewport.Flags, ImGuiViewportFlags.IsMinimized) ~= 0) then ImVec2_Copy(main_viewport_pos, main_viewport.Pos) -- Preserve last pos/size when minimized (FIXME: We don't do the same for Size outside of the viewport path) ImVec2_Copy(main_viewport_size, main_viewport.Size) main_viewport_framebuffer_scale = main_viewport.FramebufferScale end ImGui.AddUpdateViewport(nil, IMGUI_VIEWPORT_DEFAULT_ID, main_viewport_pos, main_viewport_size, bit32.bor(ImGuiViewportFlags.OwnedByApp, ImGuiViewportFlags.CanHostOtherWindows)) local memory_compact_start_time = (g.GcCompactAll or g.IO.ConfigMemoryCompactTimer < 0.0) and FLT_MAX or (g.Time - g.IO.ConfigMemoryCompactTimer) g.CurrentDpiScale = 0.0 g.CurrentViewport = nil g.MouseViewport = nil local n = 1 while n <= g.Viewports.Size do local viewport = g.Viewports.Data[n] viewport.Idx = n -- Erase unused viewports if n > 1 and viewport.LastFrameActive < g.FrameCount - 2 then ImGui.DestroyViewport(viewport) -- n stays the same because we removed an element else local platform_funcs_available = viewport.PlatformWindowCreated if viewports_enabled then -- Update Position and Size (from Platform Window to ImGui) if requested. -- We do it early in the frame instead of waiting for UpdatePlatformWindows() to avoid a frame of lag when moving/resizing using OS facilities. if (bit32.band(viewport.Flags, ImGuiViewportFlags.IsMinimized) == 0) and platform_funcs_available then -- Viewport->WorkPos and WorkSize will be updated below if viewport.PlatformRequestMove then ImVec2_Copy(viewport.Pos, g.PlatformIO.Platform_GetWindowPos(viewport)) ImVec2_Copy(viewport.LastPlatformPos, viewport.Pos) end if viewport.PlatformRequestResize then viewport.Size = g.PlatformIO.Platform_GetWindowSize(viewport) ImVec2_Copy(viewport.LastPlatformSize, viewport.Size) end if g.PlatformIO.Platform_GetWindowFramebufferScale ~= nil then viewport.FramebufferScale = g.PlatformIO.Platform_GetWindowFramebufferScale(viewport) end end end -- Update/copy monitor info ImGui.UpdateViewportPlatformMonitor(viewport) -- Lock down space taken by menu bars and status bars + query initial insets from backend -- Setup initial value for functions like BeginMainMenuBar(), DockSpaceOverViewport() etc. viewport.WorkInsetMin = viewport.BuildWorkInsetMin viewport.WorkInsetMax = viewport.BuildWorkInsetMax viewport.BuildWorkInsetMin = ImVec2(0.0, 0.0) viewport.BuildWorkInsetMax = ImVec2(0.0, 0.0) if g.PlatformIO.Platform_GetWindowWorkAreaInsets ~= nil and platform_funcs_available then local insets = g.PlatformIO.Platform_GetWindowWorkAreaInsets(viewport) IM_ASSERT(insets.x >= 0.0 and insets.y >= 0.0 and insets.z >= 0.0 and insets.w >= 0.0) viewport.BuildWorkInsetMin = ImVec2(insets.x, insets.y) viewport.BuildWorkInsetMax = ImVec2(insets.z, insets.w) end viewport:UpdateWorkRect() for dl_n = 1, 2 do -- IM_COUNTOF(viewport->BgFgDrawLists) if viewport.BgFgDrawListsLastTimeActive[dl_n] < memory_compact_start_time and viewport.BgFgDrawLists[dl_n] ~= nil then IM_DELETE(viewport.BgFgDrawLists, dl_n) end end -- Reset alpha every frame. Users of transparency (docking) needs to request a lower alpha back. viewport.Alpha = 1.0 -- Translate Dear ImGui windows when a Host Viewport has been moved -- (This additionally keeps windows at the same place when ImGuiConfigFlags.ViewportsEnable is toggled!) local viewport_delta_pos = viewport.Pos - viewport.LastPos if (bit32.band(viewport.Flags, ImGuiViewportFlags.CanHostOtherWindows) ~= 0) and (viewport_delta_pos.x ~= 0.0 or viewport_delta_pos.y ~= 0.0) then ImGui.TranslateWindowsInViewport(viewport, viewport.LastPos, viewport.Pos, viewport.LastSize, viewport.Size) end -- Update DPI scale local new_dpi_scale if g.PlatformIO.Platform_GetWindowDpiScale and platform_funcs_available then new_dpi_scale = g.PlatformIO.Platform_GetWindowDpiScale(viewport) elseif viewport.PlatformMonitor ~= -1 then new_dpi_scale = g.PlatformIO.Monitors.Data[viewport.PlatformMonitor].DpiScale else new_dpi_scale = (viewport.DpiScale ~= 0.0) and viewport.DpiScale or 1.0 end IM_ASSERT(new_dpi_scale > 0.0 and new_dpi_scale < 99.0) -- Typical correct values would be between 1.0f and 4.0f if viewport.DpiScale ~= 0.0 and new_dpi_scale ~= viewport.DpiScale then local scale_factor = new_dpi_scale / viewport.DpiScale if g.IO.ConfigDpiScaleViewports then ImGui.ScaleWindowsInViewport(viewport, scale_factor) end -- if viewport == ImGui.GetMainViewport() then -- g.PlatformInterface.SetWindowSize(viewport, viewport.Size * scale_factor) -- end -- Scale our window moving pivot so that the window will rescale roughly around the mouse position. -- FIXME-VIEWPORT: This currently creates a resizing feedback loop when a window is straddling a DPI transition border. -- (Minor: since our sizes do not perfectly linearly scale, deferring the click offset scale until we know the actual window scale ratio may get us slightly more precise mouse positioning.) -- if g.MovingWindow ~= nil and g.MovingWindow.Viewport == viewport then -- g.ActiveIdClickOffset = ImTrunc(g.ActiveIdClickOffset * scale_factor) -- end end viewport.DpiScale = new_dpi_scale n = n + 1 end end -- Update fallback monitor g.PlatformMonitorsFullWorkRect = ImRect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX) if g.PlatformIO.Monitors.Size == 0 then local monitor = g.FallbackMonitor ImVec2_Copy(monitor.MainPos, main_viewport.Pos) ImVec2_Copy(monitor.MainSize, main_viewport.Size) ImVec2_Copy(monitor.WorkPos, main_viewport.WorkPos) ImVec2_Copy(monitor.WorkSize, main_viewport.WorkSize) monitor.DpiScale = main_viewport.DpiScale g.PlatformMonitorsFullWorkRect:Add(monitor.WorkPos) g.PlatformMonitorsFullWorkRect:Add(monitor.WorkPos + monitor.WorkSize) else g.FallbackMonitor = g.PlatformIO.Monitors.Data[1] end for i = 1, g.PlatformIO.Monitors.Size do local monitor = g.PlatformIO.Monitors.Data[i] g.PlatformMonitorsFullWorkRect:Add(monitor.WorkPos) g.PlatformMonitorsFullWorkRect:Add(monitor.WorkPos + monitor.WorkSize) end if not viewports_enabled then g.MouseViewport = main_viewport return end -- Mouse handling: decide on the actual mouse viewport for this frame between the active/focused viewport and the hovered viewport. -- Note that 'viewport_hovered' should skip over any viewport that has the ImGuiViewportFlags.NoInputs flags set. local viewport_hovered = nil if bit32.band(g.IO.BackendFlags, ImGuiBackendFlags.HasMouseHoveredViewport) ~= 0 then viewport_hovered = g.IO.MouseHoveredViewport and ImGui.FindViewportByID(g.IO.MouseHoveredViewport) or nil if viewport_hovered and (bit32.band(viewport_hovered.Flags, ImGuiViewportFlags.NoInputs) ~= 0) then viewport_hovered = ImGui.FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos) -- Backend failed to handle _NoInputs viewport: revert to our fallback. end else -- If the backend doesn't know how to honor ImGuiViewportFlags.NoInputs, we do a search ourselves. Note that this search: -- A) won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window. -- B) won't take account of how the backend apply parent<>child relationship to secondary viewports, which affects their Z order. -- C) uses LastFocusedStampCount as a flawed replacement for the last time a window was focused (we could/should fix that by introducing Focus functions in PlatformIO) viewport_hovered = ImGui.FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos) end if viewport_hovered ~= nil then g.MouseLastHoveredViewport = viewport_hovered elseif g.MouseLastHoveredViewport == nil then g.MouseLastHoveredViewport = g.Viewports.Data[1] end -- Update mouse reference viewport -- (when moving a window we aim at its viewport, but this will be overwritten below if we go in drag and drop mode) -- (MovingViewport->Viewport will be nil in the rare situation where the window disappared while moving, set UpdateMouseMovingWindowNewFrame() for details) if g.MovingWindow and g.MovingWindow.Viewport then g.MouseViewport = g.MovingWindow.Viewport else g.MouseViewport = g.MouseLastHoveredViewport end -- When dragging something, always refer to the last hovered viewport. -- - when releasing a moving window we will revert to aiming behind (at viewport_hovered) -- - when we are between viewports, our dragged preview will tend to show in the last viewport _even_ if we don't have tooltips in their viewports (when lacking monitor info) -- - consider the case of holding on a menu item to browse child menus: even thou a mouse button is held, there's no active id because menu items only react on mouse release. -- FIXME-VIEWPORT: This is essentially broken, when ImGuiBackendFlags.HasMouseHoveredViewport is set we want to trust when viewport_hovered==nil and use that. local is_mouse_dragging_with_an_expected_destination = g.DragDropActive if is_mouse_dragging_with_an_expected_destination and viewport_hovered == nil then viewport_hovered = g.MouseLastHoveredViewport end if is_mouse_dragging_with_an_expected_destination or g.ActiveId == 0 or not ImGui.IsAnyMouseDown() then if viewport_hovered ~= nil and viewport_hovered ~= g.MouseViewport and (bit32.band(viewport_hovered.Flags, ImGuiViewportFlags.NoInputs) == 0) then g.MouseViewport = viewport_hovered end end IM_ASSERT(g.MouseViewport ~= nil) end function ImGui.UpdateViewportsEndFrame() local g = GImGui g.PlatformIO.Viewports:resize(0) for i = 1, g.Viewports.Size do repeat local viewport = g.Viewports.Data[i] ImVec2_Copy(viewport.LastPos, ImVec2(viewport.Pos.x, viewport.Pos.y)) ImVec2_Copy(viewport.LastSize, ImVec2(viewport.Size.x, viewport.Size.y)) if (viewport.LastFrameActive < g.FrameCount or viewport.Size.x <= 0.0 or viewport.Size.y <= 0.0) and (i > 1) then break end if viewport.Window and not ImGui.IsWindowActiveAndVisible(viewport.Window) then break end if i > 1 then IM_ASSERT(viewport.Window ~= nil) end g.PlatformIO.Viewports:push_back(viewport) until true end g.Viewports.Data[1]:ClearRequestFlags() -- Clear main viewport flags because UpdatePlatformWindows() won't do it and may not even be called end --- @param window? ImGuiWindow --- @param id ImGuiID --- @param pos ImVec2 --- @param size ImVec2 --- @param flags ImGuiViewportFlags --- @return ImGuiViewportP function ImGui.AddUpdateViewport(window, id, pos, size, flags) local g = GImGui IM_ASSERT(id ~= 0) flags = bit32.bor(flags, ImGuiViewportFlags.IsPlatformWindow) if window ~= nil then local window_can_use_inputs = (bit32.band(window.Flags, bit32.bor(ImGuiWindowFlags.NoMouseInputs, ImGuiWindowFlags.NoNavInputs)) == 0) if g.MovingWindow and g.MovingWindow.RootWindowDockTree == window then flags = bit32.bor(flags, ImGuiViewportFlags.NoInputs, ImGuiViewportFlags.NoFocusOnAppearing) end if not window_can_use_inputs then flags = bit32.bor(flags, ImGuiViewportFlags.NoInputs) end if bit32.band(window.Flags, ImGuiWindowFlags.NoFocusOnAppearing) ~= 0 then flags = bit32.bor(flags, ImGuiViewportFlags.NoFocusOnAppearing) end end local viewport = ImGui.FindViewportByID(id) if viewport then --- @cast viewport ImGuiViewportP -- Always update for main viewport as we are already pulling correct platform pos/size (see #4900) local prev_pos = ImVec2(viewport.Pos.x, viewport.Pos.y) local prev_size = ImVec2(viewport.Size.x, viewport.Size.y) if not viewport.PlatformRequestMove or viewport.ID == IMGUI_VIEWPORT_DEFAULT_ID then ImVec2_Copy(viewport.Pos, pos) end if not viewport.PlatformRequestResize or viewport.ID == IMGUI_VIEWPORT_DEFAULT_ID then ImVec2_Copy(viewport.Size, size) end -- Preserve existing flags viewport.Flags = bit32.bor(flags, bit32.band(viewport.Flags, bit32.bor(ImGuiViewportFlags.IsMinimized, ImGuiViewportFlags.IsFocused))) if prev_pos.x ~= viewport.Pos.x or prev_pos.y ~= viewport.Pos.y or prev_size.x ~= viewport.Size.x or prev_size.y ~= viewport.Size.y then ImGui.UpdateViewportPlatformMonitor(viewport) end else -- New viewport viewport = ImGuiViewportP() viewport.ID = id viewport.Idx = g.Viewports.Size ImVec2_Copy(viewport.Pos, pos) ImVec2_Copy(viewport.LastPos, pos) ImVec2_Copy(viewport.Size, size) ImVec2_Copy(viewport.LastSize, size) viewport.Flags = flags ImGui.UpdateViewportPlatformMonitor(viewport) g.Viewports:push_back(viewport) g.ViewportCreatedCount = g.ViewportCreatedCount + 1 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Add Viewport %08X '%s'", id, window and window.Name or "") -- We assume the window becomes front-most (even when ImGuiViewportFlags.NoFocusOnAppearing is used). -- This is useful for our platform z-order heuristic when io.MouseHoveredViewport is not available. g.ViewportFocusedStampCount = g.ViewportFocusedStampCount + 1 viewport.LastFocusedStampCount = g.ViewportFocusedStampCount -- We normally setup for all viewports in NewFrame() but here need to handle the mid-frame creation of a new viewport. -- We need to extend the fullscreen clip rect so the OverlayDrawList clip is correct for that the first frame g.DrawListSharedData.ClipRectFullscreen.x = ImMin(g.DrawListSharedData.ClipRectFullscreen.x, viewport.Pos.x) g.DrawListSharedData.ClipRectFullscreen.y = ImMin(g.DrawListSharedData.ClipRectFullscreen.y, viewport.Pos.y) g.DrawListSharedData.ClipRectFullscreen.z = ImMax(g.DrawListSharedData.ClipRectFullscreen.z, viewport.Pos.x + viewport.Size.x) g.DrawListSharedData.ClipRectFullscreen.w = ImMax(g.DrawListSharedData.ClipRectFullscreen.w, viewport.Pos.y + viewport.Size.y) -- Store initial DpiScale before the OS platform window creation, based on expected monitor data. -- This is so we can select an appropriate font size on the first frame of our window lifetime viewport.DpiScale = ImGui.GetViewportPlatformMonitor(viewport).DpiScale end viewport.Window = window viewport.LastFrameActive = g.FrameCount viewport:UpdateWorkRect() IM_ASSERT(window == nil or viewport.ID == window.ID) if window ~= nil then window.ViewportOwned = true end return viewport end --- @param viewport ImGuiViewportP function ImGui.DestroyViewport(viewport) -- Clear references to this viewport in windows (window->ViewportId becomes the master data) local g = GImGui for i = 1, g.Windows.Size do local window = g.Windows.Data[i] if window.Viewport == viewport then window.Viewport = nil window.ViewportOwned = false end end if viewport == g.MouseLastHoveredViewport then g.MouseLastHoveredViewport = nil end -- Destroy IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Delete Viewport %08X '%s'", viewport.ID, viewport.Window and viewport.Window.Name or "n/a") ImGui.DestroyPlatformWindow(viewport) -- In most circumstances the platform window will already be destroyed here. IM_ASSERT(not g.PlatformIO.Viewports:contains(viewport)) IM_ASSERT(g.Viewports.Data[viewport.Idx] == viewport) g.Viewports:erase(viewport.Idx) -- IM_DELETE(viewport) end --- @param window ImGuiWindow function ImGui.WindowSelectViewport(window) local g = GImGui local flags = window.Flags window.ViewportAllowPlatformMonitorExtend = -1 -- Restore main viewport if multi-viewport is not supported by the backend local main_viewport = ImGui.GetMainViewport() --[[@as ImGuiViewportP]] if bit32.band(g.ConfigFlagsCurrFrame, ImGuiConfigFlags.ViewportsEnable) == 0 then ImGui.SetWindowViewport(window, main_viewport) return end window.ViewportOwned = false -- Appearing popups reset their viewport so they can inherit again if bit32.band(flags, bit32.bor(ImGuiWindowFlags.Popup, ImGuiWindowFlags.Tooltip)) ~= 0 and window.Appearing then window.Viewport = nil window.ViewportId = 0 end if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasViewport) == 0 then -- By default inherit from parent window if window.Viewport == nil and window.ParentWindow and (not window.ParentWindow.IsFallbackWindow or window.ParentWindow.WasActive) then window.Viewport = window.ParentWindow.Viewport end -- Attempt to restore saved viewport id (= window that hasn't been activated yet), try to restore the viewport based on saved 'window->ViewportPos' restored from .ini file if window.Viewport == nil and window.ViewportId ~= 0 then window.Viewport = ImGui.FindViewportByID(window.ViewportId) --[[@as ImGuiViewportP]] if window.Viewport == nil and window.ViewportPos.x ~= FLT_MAX and window.ViewportPos.y ~= FLT_MAX then window.Viewport = ImGui.AddUpdateViewport(window, window.ID, window.ViewportPos, window.Size, ImGuiViewportFlags.None) end end end local lock_viewport = false if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasViewport) ~= 0 then -- Code explicitly request a viewport window.Viewport = ImGui.FindViewportByID(g.NextWindowData.ViewportId) --[[@as ImGuiViewportP]] window.ViewportId = g.NextWindowData.ViewportId -- Store ID even if Viewport isn't resolved yet. if window.Viewport and bit32.band(window.Flags, ImGuiWindowFlags.DockNodeHost) ~= 0 and window.Viewport.Window ~= nil then window.Viewport.Window = window window.Viewport.ID = window.ID window.ViewportId = window.ID -- Overwrite ID (always owned by node) end lock_viewport = true elseif bit32.band(flags, ImGuiWindowFlags.ChildWindow) ~= 0 or bit32.band(flags, ImGuiWindowFlags.ChildMenu) ~= 0 then -- Always inherit viewport from parent window if window.DockNode and window.DockNode.HostWindow then IM_ASSERT(window.DockNode.HostWindow.Viewport == window.ParentWindow.Viewport) end window.Viewport = window.ParentWindow.Viewport elseif window.DockNode and window.DockNode.HostWindow then -- This covers the "always inherit viewport from parent window" case for when a window reattach to a node that was just created mid-frame window.Viewport = window.DockNode.HostWindow.Viewport elseif bit32.band(flags, ImGuiWindowFlags.Tooltip) ~= 0 then window.Viewport = g.MouseViewport elseif ImGui.GetWindowAlwaysWantOwnViewport(window) then window.Viewport = ImGui.AddUpdateViewport(window, window.ID, window.Pos, window.Size, ImGuiViewportFlags.None) elseif g.MovingWindow and g.MovingWindow.RootWindowDockTree == window and ImGui.IsMousePosValid() then if window.Viewport ~= nil and window.Viewport.Window == window then window.Viewport = ImGui.AddUpdateViewport(window, window.ID, window.Pos, window.Size, ImGuiViewportFlags.None) end else -- Merge into host viewport? -- We cannot test window->ViewportOwned as it set lower in the function. -- Testing (g.ActiveId == 0 || g.ActiveIdAllowOverlap) to avoid merging during a short-term widget interaction. Main intent was to avoid during resize (see #4212) local try_to_merge_into_host_viewport = (window.Viewport and window == window.Viewport.Window and (g.ActiveId == 0 or g.ActiveIdAllowOverlap)) if try_to_merge_into_host_viewport then ImGui.UpdateTryMergeWindowIntoHostViewports(window) end end -- Fallback: merge in default viewport if z-order matches, otherwise create a new viewport if window.Viewport == nil then --- @cast main_viewport ImGuiViewportP if not ImGui.UpdateTryMergeWindowIntoHostViewport(window, main_viewport) then window.Viewport = ImGui.AddUpdateViewport(window, window.ID, window.Pos, window.Size, ImGuiViewportFlags.None) end end -- Mark window as allowed to protrude outside of its viewport and into the current monitor if not lock_viewport then if bit32.band(flags, bit32.bor(ImGuiWindowFlags.Tooltip, ImGuiWindowFlags.Popup)) ~= 0 then -- We need to take account of the possibility that mouse may become invalid. -- Popups/Tooltip always set ViewportAllowPlatformMonitorExtend so GetWindowAllowedExtentRect() will return full monitor bounds. local mouse_ref = bit32.band(flags, ImGuiWindowFlags.Tooltip) ~= 0 and g.IO.MousePos or g.BeginPopupStack.Data[g.BeginPopupStack.Size].OpenMousePos local use_mouse_ref = not g.NavCursorVisible or not g.NavHighlightItemUnderNav or not g.NavWindow local mouse_valid = ImGui.IsMousePosValid(mouse_ref) if (window.Appearing or bit32.band(flags, bit32.bor(ImGuiWindowFlags.Tooltip, ImGuiWindowFlags.ChildMenu)) ~= 0) and (not use_mouse_ref or mouse_valid) then local pos_to_use = (use_mouse_ref and mouse_valid) and mouse_ref or ImGui.NavCalcPreferredRefPos(window.Flags) window.ViewportAllowPlatformMonitorExtend = ImGui.FindPlatformMonitorForPos(pos_to_use) else window.ViewportAllowPlatformMonitorExtend = window.Viewport.PlatformMonitor end elseif window.Viewport and window ~= window.Viewport.Window and window.Viewport.Window and bit32.band(flags, ImGuiWindowFlags.ChildWindow) == 0 and window.DockNode == nil then -- When called from Begin() we don't have access to a proper version of the Hidden flag yet, so we replicate this code. local will_be_visible = (window.DockIsActive and not window.DockTabIsVisible) and false or true if bit32.band(window.Flags, ImGuiWindowFlags.DockNodeHost) ~= 0 and window.Viewport.LastFrameActive < g.FrameCount and will_be_visible then -- Steal/transfer ownership IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Window '%s' steal Viewport %08X from Window '%s'", window.Name, window.Viewport.ID, window.Viewport.Window.Name) window.Viewport.Window = window window.Viewport.ID = window.ID window.Viewport.LastNameHash = 0 elseif not ImGui.UpdateTryMergeWindowIntoHostViewports(window) then -- Merge? -- New viewport window.Viewport = ImGui.AddUpdateViewport(window, window.ID, window.Pos, window.Size, ImGuiViewportFlags.NoFocusOnAppearing) end elseif window.ViewportAllowPlatformMonitorExtend < 0 and bit32.band(flags, ImGuiWindowFlags.ChildWindow) == 0 then -- Regular (non-child, non-popup) windows by default are also allowed to protrude -- Child windows are kept contained within their parent. window.ViewportAllowPlatformMonitorExtend = window.Viewport.PlatformMonitor end end -- Update flags window.ViewportOwned = (window == window.Viewport.Window) window.ViewportId = window.Viewport.ID -- If the OS window has a title bar, hide our imgui title bar -- if window.ViewportOwned and bit32.band(window.Viewport.Flags, ImGuiViewportFlags.NoDecoration) == 0 then -- window.Flags = bit32.bor(window.Flags, ImGuiWindowFlags.NoTitleBar) -- end end --- @param window ImGuiWindow --- @param parent_window_in_stack? ImGuiWindow function ImGui.WindowSyncOwnedViewport(window, parent_window_in_stack) local g = GImGui local viewport_rect_changed = false -- Synchronize window --> viewport in most situations -- Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM if window.Viewport.PlatformRequestMove then ImVec2_Copy(window.Pos, window.Viewport.Pos) -- ImGui.MarkIniSettingsDirty(window) elseif window.Viewport.Pos.x ~= window.Pos.x or window.Viewport.Pos.y ~= window.Pos.y then viewport_rect_changed = true ImVec2_Copy(window.Viewport.Pos, window.Pos) end if window.Viewport.PlatformRequestResize then ImVec2_Copy(window.Size, window.Viewport.Size) ImVec2_Copy(window.SizeFull, window.Viewport.Size) -- ImGui.MarkIniSettingsDirty(window) elseif window.Viewport.Size.x ~= window.Size.x or window.Viewport.Size.y ~= window.Size.y then viewport_rect_changed = true ImVec2_Copy(window.Viewport.Size, window.Size) end window.Viewport:UpdateWorkRect() -- The viewport may have changed monitor since the global update in UpdateViewportsNewFrame() -- Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have this effect. if viewport_rect_changed then ImGui.UpdateViewportPlatformMonitor(window.Viewport) end -- Update common viewport flags local viewport_flags_to_clear = bit32.bor(ImGuiViewportFlags.TopMost, ImGuiViewportFlags.NoTaskBarIcon, ImGuiViewportFlags.NoDecoration, ImGuiViewportFlags.NoRendererClear) local viewport_flags = bit32.band(window.Viewport.Flags, bit32.bnot(viewport_flags_to_clear)) local window_flags = window.Flags local is_modal = bit32.band(window_flags, ImGuiWindowFlags.Modal) ~= 0 local is_short_lived_floating_window = bit32.band(window_flags, bit32.bor(ImGuiWindowFlags.ChildMenu, ImGuiWindowFlags.Tooltip, ImGuiWindowFlags.Popup)) ~= 0 if bit32.band(window_flags, ImGuiWindowFlags.Tooltip) ~= 0 then viewport_flags = bit32.bor(viewport_flags, ImGuiViewportFlags.TopMost) end if (g.IO.ConfigViewportsNoTaskBarIcon or is_short_lived_floating_window) and not is_modal then viewport_flags = bit32.bor(viewport_flags, ImGuiViewportFlags.NoTaskBarIcon) end if g.IO.ConfigViewportsNoDecoration or is_short_lived_floating_window then viewport_flags = bit32.bor(viewport_flags, ImGuiViewportFlags.NoDecoration) end -- Not correct to set modal as topmost because: -- - Because other popups can be stacked above a modal (e.g. combo box in a modal) -- - ImGuiViewportFlags.TopMost is currently handled different in backends -- if bit32.band(window_flags, ImGuiWindowFlags.Modal) ~= 0 then -- viewport_flags = bit32.bor(viewport_flags, ImGuiViewportFlags.TopMost) -- end -- For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that clicking on them -- won't steal the OS focus away from their parent window (which may be reflected in OS the title bar decoration). -- Setting _NoFocusOnClick would technically prevent us from bringing back to front in case they are being covered by an OS window from a different app, -- but it shouldn't be much of a problem considering those are already popups that are closed when clicking elsewhere. if is_short_lived_floating_window and not is_modal then viewport_flags = bit32.bor(viewport_flags, ImGuiViewportFlags.NoFocusOnAppearing, ImGuiViewportFlags.NoFocusOnClick) end -- We can overwrite viewport flags using ImGuiWindowClass (advanced users) if window.WindowClass.ViewportFlagsOverrideSet ~= 0 then viewport_flags = bit32.bor(viewport_flags, window.WindowClass.ViewportFlagsOverrideSet) end if window.WindowClass.ViewportFlagsOverrideClear ~= 0 then viewport_flags = bit32.band(viewport_flags, bit32.bnot(window.WindowClass.ViewportFlagsOverrideClear)) end -- We can also tell the backend that clearing the platform window won't be necessary, -- as our window background is filling the viewport and we have disabled BgAlpha. -- FIXME: Work on support for per-viewport transparency (#2766) if bit32.band(window_flags, ImGuiWindowFlags.NoBackground) == 0 then viewport_flags = bit32.bor(viewport_flags, ImGuiViewportFlags.NoRendererClear) end window.Viewport.Flags = viewport_flags -- Update parent viewport ID -- (the !IsFallbackWindow test mimic the one done in WindowSelectViewport()) if window.WindowClass.ParentViewportId ~= 0xFFFFFFFF then local old_parent_viewport_id = window.Viewport.ParentViewportId window.Viewport.ParentViewportId = window.WindowClass.ParentViewportId if window.Viewport.ParentViewportId ~= old_parent_viewport_id then window.Viewport.ParentViewport = ImGui.FindViewportByID(window.Viewport.ParentViewportId) --[[@as ImGuiViewport]] end elseif bit32.band(window_flags, bit32.bor(ImGuiWindowFlags.Popup, ImGuiWindowFlags.Tooltip)) ~= 0 and parent_window_in_stack and (not parent_window_in_stack.IsFallbackWindow or parent_window_in_stack.WasActive) then window.Viewport.ParentViewport = parent_window_in_stack.Viewport window.Viewport.ParentViewportId = parent_window_in_stack.Viewport.ID else if g.IO.ConfigViewportsNoDefaultParent then window.Viewport.ParentViewport = nil else window.Viewport.ParentViewport = ImGui.GetMainViewport() end window.Viewport.ParentViewportId = g.IO.ConfigViewportsNoDefaultParent and 0 or IMGUI_VIEWPORT_DEFAULT_ID end end function ImGui.UpdatePlatformWindows() local g = GImGui IM_ASSERT(g.FrameCountEnded == g.FrameCount, "Forgot to call Render() or EndFrame() before UpdatePlatformWindows()?") IM_ASSERT(g.FrameCountPlatformEnded < g.FrameCount) g.FrameCountPlatformEnded = g.FrameCount if bit32.band(g.ConfigFlagsCurrFrame, ImGuiConfigFlags.ViewportsEnable) == 0 then return end -- Create/resize/destroy platform windows to match each active viewport. -- Skip the main viewport (index 0), which is always fully handled by the application! for i = 2, g.Viewports.Size do repeat local viewport = g.Viewports.Data[i] -- Destroy platform window if the viewport hasn't been submitted or if it is hosting a hidden window -- (the implicit/fallback Debug##Default window will be registering its viewport then be disabled, causing a dummy DestroyPlatformWindow to be made each frame) local destroy_platform_window = false destroy_platform_window = destroy_platform_window or (viewport.LastFrameActive < g.FrameCount - 1) destroy_platform_window = destroy_platform_window or (viewport.Window and not ImGui.IsWindowActiveAndVisible(viewport.Window)) --[[@as bool]] if destroy_platform_window then ImGui.DestroyPlatformWindow(viewport) break end -- New windows that appears directly in a new viewport won't always have a size on their first frame if viewport.LastFrameActive < g.FrameCount or viewport.Size.x <= 0 or viewport.Size.y <= 0 then break end -- Create window local is_new_platform_window = not viewport.PlatformWindowCreated if is_new_platform_window then IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Create Platform Window %08X '%s'", viewport.ID, viewport.Window and viewport.Window.Name or "n/a") g.PlatformIO.Platform_CreateWindow(viewport) if g.PlatformIO.Renderer_CreateWindow ~= nil then g.PlatformIO.Renderer_CreateWindow(viewport) end g.PlatformWindowsCreatedCount = g.PlatformWindowsCreatedCount + 1 viewport.LastNameHash = 0 ImVec2_CopyV(viewport.LastPlatformPos, FLT_MAX, FLT_MAX) ImVec2_CopyV(viewport.LastPlatformSize, FLT_MAX, FLT_MAX) -- By clearing those we'll enforce a call to Platform_SetWindowPos/Size below, before Platform_ShowWindow (FIXME: Is that necessary?) ImVec2_Copy(viewport.LastRendererSize, viewport.Size) -- We don't need to call Renderer_SetWindowSize() as it is expected Renderer_CreateWindow() already did it. viewport.PlatformWindowCreated = true end -- Apply Position and Size (from ImGui to Platform/Renderer backends) if (viewport.LastPlatformPos.x ~= viewport.Pos.x or viewport.LastPlatformPos.y ~= viewport.Pos.y) and not viewport.PlatformRequestMove then g.PlatformIO.Platform_SetWindowPos(viewport, viewport.Pos) end if (viewport.LastPlatformSize.x ~= viewport.Size.x or viewport.LastPlatformSize.y ~= viewport.Size.y) and not viewport.PlatformRequestResize then g.PlatformIO.Platform_SetWindowSize(viewport, viewport.Size) end if (viewport.LastRendererSize.x ~= viewport.Size.x or viewport.LastRendererSize.y ~= viewport.Size.y) and g.PlatformIO.Renderer_SetWindowSize ~= nil then g.PlatformIO.Renderer_SetWindowSize(viewport, viewport.Size) end ImVec2_Copy(viewport.LastPlatformPos, viewport.Pos) ImVec2_Copy(viewport.LastPlatformSize, viewport.Size) ImVec2_Copy(viewport.LastRendererSize, viewport.Size) -- Update title bar (if it changed) local window_for_title = GetWindowForTitleDisplay(viewport.Window) if window_for_title ~= nil then local title = window_for_title.Name local title_begin = 1 local title_end = ImGui.FindRenderedTextEnd(title) local title_hash = ImHashStr(title, title_end - title_begin) if viewport.LastNameHash ~= title_hash then -- This still creates new strings g.PlatformIO.Platform_SetWindowTitle(viewport, string.sub(title, title_begin, title_end - 1)) viewport.LastNameHash = title_hash end end -- Update alpha (if it changed) if viewport.LastAlpha ~= viewport.Alpha and g.PlatformIO.Platform_SetWindowAlpha ~= nil then g.PlatformIO.Platform_SetWindowAlpha(viewport, viewport.Alpha) end viewport.LastAlpha = viewport.Alpha -- Optional, general purpose call to allow the backend to perform general book-keeping even if things haven't changed. if g.PlatformIO.Platform_UpdateWindow ~= nil then g.PlatformIO.Platform_UpdateWindow(viewport) end if is_new_platform_window then -- On startup ensure new platform window don't steal focus (give it a few frames, as nested contents may lead to viewport being created a few frames late) if g.FrameCount < 3 then viewport.Flags = bit32.bor(viewport.Flags, ImGuiViewportFlags.NoFocusOnAppearing) end -- Show window g.PlatformIO.Platform_ShowWindow(viewport) end -- Clear request flags viewport:ClearRequestFlags() until true end end -- This is a default/basic function for performing the rendering/swap of multiple Platform Windows. -- Custom renderers may prefer to not call this function at all, and instead iterate the publicly exposed platform data and handle rendering/sync themselves. -- The Render/Swap functions stored in ImGuiPlatformIO are merely here to allow for this helper to exist, but you can do it yourself: -- -- local platform_io = ImGui.GetPlatformIO() -- for i = 2, platform_io.Viewports.Size do -- if bit32.band(platform_io.Viewports.Data[i].Flags, ImGuiViewportFlags.IsMinimized) == 0 then -- MyRenderFunction(platform_io.Viewports.Data[i], my_args) -- end -- end -- for i = 2, platform_io.Viewports.Size do -- if bit32.band(platform_io.Viewports.Data[i].Flags, ImGuiViewportFlags.IsMinimized) == 0 then -- MySwapBufferFunction(platform_io.Viewports.Data[i], my_args) -- end -- end -- function ImGui.RenderPlatformWindowsDefault(platform_render_arg, renderer_render_arg) -- Skip the main viewport (index 1 in Lua), which is always fully handled by the application! local platform_io = ImGui.GetPlatformIO() -- First pass: render all windows for i = 2, platform_io.Viewports.Size do local viewport = platform_io.Viewports.Data[i] if bit32.band(viewport.Flags, ImGuiViewportFlags.IsMinimized) == 0 then if platform_io.Platform_RenderWindow then platform_io.Platform_RenderWindow(viewport, platform_render_arg) end if platform_io.Renderer_RenderWindow then platform_io.Renderer_RenderWindow(viewport, renderer_render_arg) end end end -- Second pass: swap buffers for all windows for i = 2, platform_io.Viewports.Size do local viewport = platform_io.Viewports.Data[i] if bit32.band(viewport.Flags, ImGuiViewportFlags.IsMinimized) == 0 then if platform_io.Platform_SwapBuffers then platform_io.Platform_SwapBuffers(viewport, platform_render_arg) end if platform_io.Renderer_SwapBuffers then platform_io.Renderer_SwapBuffers(viewport, renderer_render_arg) end end end end --- @param pos ImVec2 function ImGui.FindPlatformMonitorForPos(pos) local g = GImGui for monitor_n = 1, g.PlatformIO.Monitors.Size do local monitor = g.PlatformIO.Monitors.Data[monitor_n] if ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize):Contains(pos) then return monitor_n end end return -1 end --- @param rect ImRect --- @return int function ImGui.FindPlatformMonitorForRect(rect) local g = GImGui local monitor_count = g.PlatformIO.Monitors.Size if monitor_count <= 1 then return monitor_count end -- Use a minimum threshold of 1.0f so a zero-sized rect won't false positive, and will still find the correct monitor given its position. -- This is necessary for tooltips which always resize down to zero at first. local surface_threshold = math.max(rect:GetWidth() * rect:GetHeight() * 0.5, 1.0) local best_monitor_n = 1 -- Default to the first monitor as fallback local best_monitor_surface = 0.001 for monitor_n = 1, g.PlatformIO.Monitors.Size do if best_monitor_surface >= surface_threshold then break end local monitor = g.PlatformIO.Monitors.Data[monitor_n] local monitor_rect = ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize) if monitor_rect:Contains(rect) then return monitor_n end local overlapping_rect = ImRect(rect.Min.x, rect.Min.y, rect.Max.x, rect.Max.y) overlapping_rect:ClipWithFull(monitor_rect) local overlapping_surface = overlapping_rect:GetWidth() * overlapping_rect:GetHeight() if overlapping_surface > best_monitor_surface then best_monitor_surface = overlapping_surface best_monitor_n = monitor_n end end return best_monitor_n end --- @param viewport ImGuiViewportP function ImGui.UpdateViewportPlatformMonitor(viewport) viewport.PlatformMonitor = ImGui.FindPlatformMonitorForRect(viewport:GetMainRect()) end --- @param viewport_p ImGuiViewport --- @return ImGuiPlatformMonitor function ImGui.GetViewportPlatformMonitor(viewport_p) local g = GImGui local viewport = viewport_p --- @cast viewport ImGuiViewportP local monitor_idx = viewport.PlatformMonitor if monitor_idx >= 1 and monitor_idx <= g.PlatformIO.Monitors.Size then return g.PlatformIO.Monitors.Data[monitor_idx] end return g.FallbackMonitor end --- @param viewport ImGuiViewportP function ImGui.DestroyPlatformWindow(viewport) local g = GImGui if viewport.PlatformWindowCreated then IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Destroy Platform Window %08X '%s'", viewport.ID, viewport.Window and viewport.Window.Name or "n/a") if g.PlatformIO.Renderer_DestroyWindow then g.PlatformIO.Renderer_DestroyWindow(viewport) end if g.PlatformIO.Platform_DestroyWindow then g.PlatformIO.Platform_DestroyWindow(viewport) end IM_ASSERT(viewport.RendererUserData == nil and viewport.PlatformUserData == nil) -- Don't clear PlatformWindowCreated for the main viewport, as we initially set that up to true in Initialize() -- The righter way may be to leave it to the backend to set this flag all-together, and made the flag public. if viewport.ID ~= IMGUI_VIEWPORT_DEFAULT_ID then viewport.PlatformWindowCreated = false end else IM_ASSERT(viewport.RendererUserData == nil and viewport.PlatformUserData == nil and viewport.PlatformHandle == nil) end viewport.RendererUserData = nil viewport.PlatformUserData = nil viewport.PlatformHandle = nil viewport:ClearRequestFlags() end function ImGui.DestroyPlatformWindows() local g = GImGui for _, viewport in g.Viewports:iter() do ImGui.DestroyPlatformWindow(viewport) end end --------------------------------------------------------------------------------------- -- [SECTION] METRICS/DEBUGGER WINDOW --------------------------------------------------------------------------------------- --- @param draw_list ImDrawList --- @param viewport ImGuiViewport --- @param bb ImRect function ImGui.DebugRenderViewportThumbnail(draw_list, viewport, bb) local g = GImGui local window = g.CurrentWindow local scale = bb:GetSize() / viewport.Size local off = bb.Min - viewport.Pos * scale local alpha_mul = (bit32.band(viewport.Flags, ImGuiViewportFlags.IsMinimized) ~= 0) and 0.30 or 1.00 window.DrawList:AddRectFilled(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.Border, alpha_mul * 0.40)) for _, thumb_window in g.Windows:iter() do repeat if not thumb_window.WasActive or bit32.band(thumb_window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 then break end if thumb_window.Viewport ~= viewport then break end local thumb_r = thumb_window:Rect() local title_r = thumb_window:TitleBarRect() thumb_r = ImRect(ImTrunc(off + thumb_r.Min * scale), ImTrunc(off + thumb_r.Max * scale)) title_r = ImRect(ImTrunc(off + title_r.Min * scale), ImTrunc(off + ImVec2(title_r.Max.x, title_r.Min.y + title_r:GetHeight() * 3.0) * scale)) thumb_r:ClipWithFull(bb) title_r:ClipWithFull(bb) local window_is_focused = g.NavWindow and thumb_window.RootWindowForTitleBarHighlight == g.NavWindow.RootWindowForTitleBarHighlight window.DrawList:AddRectFilled(thumb_r.Min, thumb_r.Max, ImGui.GetColorU32(ImGuiCol.WindowBg, alpha_mul)) window.DrawList:AddRectFilled(title_r.Min, title_r.Max, ImGui.GetColorU32(window_is_focused and ImGuiCol.TitleBgActive or ImGuiCol.TitleBg, alpha_mul)) window.DrawList:AddRect(thumb_r.Min, thumb_r.Max, ImGui.GetColorU32(ImGuiCol.Border, alpha_mul)) window.DrawList:AddText(g.Font, g.FontSize * 1.0, title_r.Min, ImGui.GetColorU32(ImGuiCol.Text, alpha_mul), thumb_window.Name, 1, ImGui.FindRenderedTextEnd(thumb_window.Name)) until true end draw_list:AddRect(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.Border, alpha_mul)) if viewport.ID == g.DebugMetricsConfig.HighlightViewportID then window.DrawList:AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255)) end end function ImGui.RenderViewportsThumbnails() local g = GImGui local window = g.CurrentWindow local SCALE = 1.0 / 8.0 local bb_full = ImRect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX) for _, monitor in g.PlatformIO.Monitors:iter() do bb_full:Add(ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize)) end local p = window.DC.CursorPos local off = p - bb_full.Min * SCALE for _, monitor in g.PlatformIO.Monitors:iter() do local monitor_draw_bb = ImRect(off + monitor.MainPos * SCALE, off + (monitor.MainPos + monitor.MainSize) * SCALE) window.DrawList:AddRect(monitor_draw_bb.Min, monitor_draw_bb.Max, (g.DebugMetricsConfig.HighlightMonitorIdx == g.PlatformIO.Monitors:index_from_ptr(monitor)) and IM_COL32(255, 255, 0, 255) or ImGui.GetColorU32(ImGuiCol.Border), 4.0) window.DrawList:AddRectFilled(monitor_draw_bb.Min, monitor_draw_bb.Max, ImGui.GetColorU32(ImGuiCol.Border, 0.10), 4.0) end for _, viewport in g.Viewports:iter() do local viewport_draw_bb = ImRect(off + viewport.Pos * SCALE, off + (viewport.Pos + viewport.Size) * SCALE) ImGui.DebugRenderViewportThumbnail(window.DrawList, viewport, viewport_draw_bb) end ImGui.Dummy(bb_full:GetSize() * SCALE) end --- @param idx ImGuiCol --- @return ImVec4 # live reference to the style color (like upstream's const&) function ImGui.GetStyleColorVec4(idx) return GImGui.Style.Colors[idx] end function ImGui.ErrorCheckUsingSetCursorPosToExtendParentBoundaries() local g = GImGui local window = g.CurrentWindow IM_ASSERT(window.DC.IsSetPos) window.DC.IsSetPos = false if window.DC.CursorPos.x <= window.DC.CursorMaxPos.x and window.DC.CursorPos.y <= window.DC.CursorMaxPos.y then return end if window.SkipItems then return end -- Upstream asserts (user error): "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries. -- Please submit an item e.g. Dummy() afterwards". We keep the old, tolerant behavior instead of erroring. window.DC.CursorMaxPos = ImMax(window.DC.CursorMaxPos, window.DC.CursorPos) end --------------------------------------------------------------------------------------- -- [SECTION] POPUPS: modals, context popups, dimming --------------------------------------------------------------------------------------- local function GetWindowDisplayLayer(window) return (bit32.band(window.Flags, ImGuiWindowFlags.Tooltip) ~= 0) and 1 or 0 end --- @return int # 0-based display index (like upstream), -1 if not found function ImGui.FindWindowDisplayIndex(window) local g = GImGui local i = g.Windows:find_index(window) return i and (i - 1) or -1 end function ImGui.BringWindowToDisplayBack(window) local g = GImGui if g.Windows.Data[1] == window then return end local i = g.Windows:find_index(window) if i then table.remove(g.Windows.Data, i) table.insert(g.Windows.Data, 1, window) end end function ImGui.FindBottomMostVisibleWindowWithinBeginStack(parent_window) local g = GImGui local bottom_most_visible_window = parent_window for i = ImGui.FindWindowDisplayIndex(parent_window), 0, -1 do local window = g.Windows.Data[i + 1] if bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) == 0 then if not ImGui.IsWindowWithinBeginStackOf(window, parent_window) then break end if ImGui.IsWindowActiveAndVisible(window) and GetWindowDisplayLayer(window) <= GetWindowDisplayLayer(parent_window) then bottom_most_visible_window = window end end end return bottom_most_visible_window end function ImGui.GetTopMostAndVisiblePopupModal() local g = GImGui for n = g.OpenPopupStack.Size, 1, -1 do local popup = g.OpenPopupStack.Data[n].Window if popup and bit32.band(popup.Flags, ImGuiWindowFlags.Modal) ~= 0 and ImGui.IsWindowActiveAndVisible(popup) then return popup end end return nil end function ImGui.RenderDimmedBackgroundBehindWindow(window, col) if bit32.band(col, IM_COL32_A_MASK) == 0 then return end local viewport = window.Viewport local viewport_rect = viewport:GetMainRect() -- Draw behind window by moving the draw command at the FRONT of the draw list local draw_list = (window.RootWindowDockTree or window.RootWindow).DrawList draw_list:ChannelsMerge() if draw_list.CmdBuffer.Size == 0 then draw_list:AddDrawCmd() end draw_list:PushClipRect(viewport_rect.Min - ImVec2(1, 1), viewport_rect.Max + ImVec2(1, 1), false) draw_list:AddRectFilled(viewport_rect.Min, viewport_rect.Max, col) local cmd = draw_list.CmdBuffer.Data[draw_list.CmdBuffer.Size] draw_list.CmdBuffer:pop_back() draw_list.CmdBuffer:push_front(cmd) draw_list:AddDrawCmd() draw_list:PopClipRect() end function ImGui.RenderDimmedBackgrounds() local g = GImGui local modal_window = ImGui.GetTopMostAndVisiblePopupModal() if g.DimBgRatio <= 0.0 and g.NavWindowingHighlightAlpha <= 0.0 then return end local dim_bg_for_modal = (modal_window ~= nil) local dim_bg_for_window_list = (g.NavWindowingTargetAnim ~= nil and g.NavWindowingTargetAnim.Active) if not dim_bg_for_modal and not dim_bg_for_window_list then return end if dim_bg_for_modal then local dim_behind_window = ImGui.FindBottomMostVisibleWindowWithinBeginStack(modal_window) ImGui.RenderDimmedBackgroundBehindWindow(dim_behind_window, ImGui.GetColorU32(modal_window.DC.ModalDimBgColor, g.DimBgRatio, true)) elseif dim_bg_for_window_list then ImGui.RenderDimmedBackgroundBehindWindow(g.NavWindowingTargetAnim, ImGui.GetColorU32(ImGuiCol.NavWindowingDimBg, g.DimBgRatio)) if g.NavWindowingListWindow ~= nil and g.NavWindowingListWindow.Active and g.NavWindowingListWindow.Viewport and g.NavWindowingListWindow.Viewport ~= g.NavWindowingTargetAnim.Viewport then ImGui.RenderDimmedBackgroundBehindWindow(g.NavWindowingListWindow, ImGui.GetColorU32(ImGuiCol.NavWindowingDimBg, g.DimBgRatio)) end -- Draw border around Ctrl+Tab target window local window = g.NavWindowingTargetAnim local viewport = window.Viewport local distance = g.FontSize local bb = window:Rect() bb:Expand(distance) if bb:GetWidth() >= viewport.Size.x and bb:GetHeight() >= viewport.Size.y then bb:Expand(-distance - 1.0) end window.DrawList:ChannelsMerge() if window.DrawList.CmdBuffer.Size == 0 then window.DrawList:AddDrawCmd() end window.DrawList:PushClipRect(viewport.Pos, viewport.Pos + viewport.Size) window.DrawList:AddRect(bb.Min, bb.Max, ImGui.GetColorU32(ImGuiCol.NavWindowingHighlight, g.NavWindowingHighlightAlpha), window.WindowRounding, 0, 3.0) window.DrawList:PopClipRect() end end function ImGui.ClosePopupsExceptModals() local g = GImGui local popup_count_to_keep = g.OpenPopupStack.Size while popup_count_to_keep > 0 do local window = g.OpenPopupStack.Data[popup_count_to_keep].Window if not window or bit32.band(window.Flags, ImGuiWindowFlags.Modal) ~= 0 then break end popup_count_to_keep = popup_count_to_keep - 1 end if popup_count_to_keep < g.OpenPopupStack.Size then ImGui.ClosePopupToLevel(popup_count_to_keep, true) end end --- @return bool visible, bool? p_open # p_open is the updated value of the optional close button flag function ImGui.BeginPopupModal(name, p_open, flags) if flags == nil then flags = 0 end local g = GImGui local window = g.CurrentWindow local id = window:GetID(name) if not ImGui.IsPopupOpen(id, ImGuiPopupFlags.None) then g.NextWindowData:ClearFlags() if p_open then p_open = false end return false, p_open end if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasPos) == 0 then local viewport = window.WasActive and window.Viewport or ImGui.GetMainViewport() ImGui.SetNextWindowPos(viewport:GetCenter(), ImGuiCond.FirstUseEver, ImVec2(0.5, 0.5)) end flags = bit32.bor(flags, ImGuiWindowFlags.Popup, ImGuiWindowFlags.Modal, ImGuiWindowFlags.NoCollapse, ImGuiWindowFlags.NoDocking) local is_open p_open, is_open = ImGui.Begin(name, p_open, flags) if not is_open or (p_open ~= nil and not p_open) then ImGui.EndPopup() if is_open then ImGui.ClosePopupToLevel(g.BeginPopupStack.Size, true) end return false, p_open end return is_open, p_open end function ImGui.IsPopupOpenRequestForWindow(popup_flags) local g = GImGui local mouse_button = ImGui.GetMouseButtonFromPopupFlags(popup_flags) if ImGui.IsMouseReleased(mouse_button, ImGuiKeyOwner_NoOwner) and ImGui.IsWindowHovered(ImGuiHoveredFlags.AllowWhenBlockedByPopup) then if bit32.band(popup_flags, ImGuiPopupFlags.NoOpenOverItems) == 0 or not ImGui.IsAnyItemHovered() then return true end end if g.NavOpenContextMenuWindowId and g.NavOpenContextMenuWindowId ~= 0 and g.CurrentWindow.ID ~= 0 then if ImGui.IsWindowChildOf(g.NavWindow, g.CurrentWindow, false, false) then return true end end return false end function ImGui.BeginPopupContextWindow(str_id, popup_flags) if popup_flags == nil then popup_flags = 0 end local g = GImGui local window = g.CurrentWindow if not str_id then str_id = "window_context" end local id = window:GetID(str_id) if ImGui.IsPopupOpenRequestForWindow(popup_flags) then ImGui.OpenPopupEx(id, popup_flags) end return ImGui.BeginPopupEx(id, bit32.bor(ImGuiWindowFlags.AlwaysAutoResize, ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoSavedSettings)) end function ImGui.BeginPopupContextVoid(str_id, popup_flags) if popup_flags == nil then popup_flags = 0 end local g = GImGui local window = g.CurrentWindow if not str_id then str_id = "void_context" end local id = window:GetID(str_id) local mouse_button = ImGui.GetMouseButtonFromPopupFlags(popup_flags) if ImGui.IsMouseReleased(mouse_button, id) and not ImGui.IsWindowHovered(ImGuiHoveredFlags.AnyWindow) then if ImGui.GetTopMostPopupModal() == nil then ImGui.OpenPopupEx(id, popup_flags) end end return ImGui.BeginPopupEx(id, bit32.bor(ImGuiWindowFlags.AlwaysAutoResize, ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoSavedSettings)) end function ImGui.BeginTooltipHidden() local g = GImGui local _, ret = ImGui.Begin("##Tooltip_Hidden", nil, bit32.bor(ImGuiWindowFlags.Tooltip, ImGuiWindowFlags.NoInputs, ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoMove, ImGuiWindowFlags.NoResize, ImGuiWindowFlags.NoSavedSettings, ImGuiWindowFlags.AlwaysAutoResize)) ImGui.SetWindowHiddenAndSkipItemsForCurrentFrame(g.CurrentWindow) return ret end -- [SECTION] MISC GETTERS/SETTERS, FOCUS function ImGui.NavMoveRequestResolveWithLastItem(result) local g = GImGui g.NavMoveScoringItems = false -- Ensure request doesn't need more processing ImGui.NavApplyItemToResult(result) ImGui.NavUpdateAnyRequestFlag() end function ImGui.SetKeyboardFocusHere(offset) if offset == nil then offset = 0 end local g = GImGui local window = g.CurrentWindow IM_ASSERT(offset >= -1) -- -1 is allowed but not below if g.DragDropActive or g.MovingWindow ~= nil then return end ImGui.SetNavWindow(window) local move_flags = bit32.bor(ImGuiNavMoveFlags.IsTabbing, ImGuiNavMoveFlags.Activate, ImGuiNavMoveFlags.FocusApi, ImGuiNavMoveFlags.NoSetNavCursorVisible) local scroll_flags = window.Appearing and bit32.bor(ImGuiScrollFlags.KeepVisibleEdgeX, ImGuiScrollFlags.AlwaysCenterY) or bit32.bor(ImGuiScrollFlags.KeepVisibleEdgeX, ImGuiScrollFlags.KeepVisibleEdgeY) ImGui.NavMoveRequestSubmit(ImGuiDir.None, (offset < 0) and ImGuiDir.Up or ImGuiDir.Down, move_flags, scroll_flags) if offset == -1 then ImGui.NavMoveRequestResolveWithLastItem(g.NavMoveResultLocal) else g.NavTabbingDir = 1 g.NavTabbingCounter = offset + 1 end end function ImGui.FocusItem() local g = GImGui local window = g.CurrentWindow if g.DragDropActive or g.MovingWindow ~= nil then return end local move_flags = bit32.bor(ImGuiNavMoveFlags.IsTabbing, ImGuiNavMoveFlags.FocusApi, ImGuiNavMoveFlags.NoSetNavCursorVisible, ImGuiNavMoveFlags.NoSelect) local scroll_flags = window.Appearing and bit32.bor(ImGuiScrollFlags.KeepVisibleEdgeX, ImGuiScrollFlags.AlwaysCenterY) or bit32.bor(ImGuiScrollFlags.KeepVisibleEdgeX, ImGuiScrollFlags.KeepVisibleEdgeY) ImGui.SetNavWindow(window) ImGui.NavMoveRequestSubmit(ImGuiDir.None, ImGuiDir.Up, move_flags, scroll_flags) ImGui.NavMoveRequestResolveWithLastItem(g.NavMoveResultLocal) end function ImGui.ActivateItemByID(id) local g = GImGui g.NavNextActivateId = id g.NavNextActivateFlags = ImGuiActivateFlags.None end function ImGui.SetNavCursorVisible(visible) local g = GImGui if g.NavWindow and bit32.band(g.NavWindow.Flags, ImGuiWindowFlags.NoNavInputs) ~= 0 then visible = false elseif g.IO.ConfigNavCursorVisibleAlways then visible = true end g.NavCursorVisible = visible end function ImGui.GetFrameCount() return GImGui.FrameCount end function ImGui.GetStateStorage() return GImGui.CurrentWindow.DC.StateStorage end function ImGui.SetStateStorage(tree) local window = GImGui.CurrentWindow window.DC.StateStorage = tree or window.StateStorage end function ImGui.SetNextFrameWantCaptureKeyboard(want) GImGui.WantCaptureKeyboardNextFrame = want and 1 or 0 end function ImGui.SetNextFrameWantCaptureMouse(want) GImGui.WantCaptureMouseNextFrame = want and 1 or 0 end function ImGui.GetVersion() return IMGUI_VERSION end local StyleColorNames = nil function ImGui.GetStyleColorName(idx) if StyleColorNames == nil then StyleColorNames = {} for k, v in pairs(ImGuiCol) do if k ~= "COUNT" then StyleColorNames[v] = k end end end return StyleColorNames[idx] or "Unknown" end -- [SECTION] NAV END FRAME local NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15 local function GetFallbackWindowNameForWindowingList(window) if bit32.band(window.Flags, ImGuiWindowFlags.Popup) ~= 0 then return "(Popup)" end if bit32.band(window.Flags, ImGuiWindowFlags.MenuBar) ~= 0 and window.Name == "##MainMenuBar" then return "(Main menu bar)" end if window.DockNodeAsHost then return "(Dock node)" end return "(Untitled)" end function ImGui.NavUpdateWindowingOverlay() local g = GImGui IM_ASSERT(g.NavWindowingTarget ~= nil) if g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY then return end local viewport = ImGui.GetMainViewport() ImGui.SetNextWindowSizeConstraints(ImVec2(viewport.Size.x * 0.20, viewport.Size.y * 0.20), ImVec2(FLT_MAX, FLT_MAX)) ImGui.SetNextWindowPos(viewport:GetCenter(), ImGuiCond.Always, ImVec2(0.5, 0.5)) ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, g.Style.WindowPadding * 2.0) ImGui.Begin("##NavWindowingOverlay", nil, bit32.bor(ImGuiWindowFlags.NoTitleBar, ImGuiWindowFlags.NoFocusOnAppearing, ImGuiWindowFlags.NoResize, ImGuiWindowFlags.NoMove, ImGuiWindowFlags.NoInputs, ImGuiWindowFlags.AlwaysAutoResize, ImGuiWindowFlags.NoSavedSettings)) g.NavWindowingListWindow = g.CurrentWindow for n = g.WindowsFocusOrder.Size, 1, -1 do local window = g.WindowsFocusOrder.Data[n] IM_ASSERT(window ~= nil) if ImGui.IsWindowNavFocusable(window) then local label = window.Name if ImGui.FindRenderedTextEnd(label) == 1 then label = GetFallbackWindowNameForWindowingList(window) end ImGui.Selectable(label, g.NavWindowingTarget == window) end end ImGui.End() ImGui.PopStyleVar() end local function NavUpdateCreateWrappingRequest() local g = GImGui local window = g.NavWindow local do_forward = false local src = window.NavRectRel[g.NavLayer] local bb_rel = ImRect(src.Min.x, src.Min.y, src.Max.x, src.Max.y) local clip_dir = g.NavMoveDir local move_flags = g.NavMoveFlags local wrap_size = (g.NavLayer == ImGuiNavLayer.Menu) and window.Size or (window.ContentSize + window.WindowPadding) if g.NavMoveDir == ImGuiDir.Left and bit32.band(move_flags, bit32.bor(ImGuiNavMoveFlags.WrapX, ImGuiNavMoveFlags.LoopX)) ~= 0 then bb_rel.Min.x = wrap_size.x; bb_rel.Max.x = wrap_size.x if bit32.band(move_flags, ImGuiNavMoveFlags.WrapX) ~= 0 then bb_rel:TranslateY(-bb_rel:GetHeight()) clip_dir = ImGuiDir.Up end do_forward = true end if g.NavMoveDir == ImGuiDir.Right and bit32.band(move_flags, bit32.bor(ImGuiNavMoveFlags.WrapX, ImGuiNavMoveFlags.LoopX)) ~= 0 then bb_rel.Min.x = -window.WindowPadding.x; bb_rel.Max.x = -window.WindowPadding.x if bit32.band(move_flags, ImGuiNavMoveFlags.WrapX) ~= 0 then bb_rel:TranslateY(bb_rel:GetHeight()) clip_dir = ImGuiDir.Down end do_forward = true end if g.NavMoveDir == ImGuiDir.Up and bit32.band(move_flags, bit32.bor(ImGuiNavMoveFlags.WrapY, ImGuiNavMoveFlags.LoopY)) ~= 0 then bb_rel.Min.y = wrap_size.y; bb_rel.Max.y = wrap_size.y if bit32.band(move_flags, ImGuiNavMoveFlags.WrapY) ~= 0 then bb_rel:TranslateX(-bb_rel:GetWidth()) clip_dir = ImGuiDir.Left end do_forward = true end if g.NavMoveDir == ImGuiDir.Down and bit32.band(move_flags, bit32.bor(ImGuiNavMoveFlags.WrapY, ImGuiNavMoveFlags.LoopY)) ~= 0 then bb_rel.Min.y = -window.WindowPadding.y; bb_rel.Max.y = -window.WindowPadding.y if bit32.band(move_flags, ImGuiNavMoveFlags.WrapY) ~= 0 then bb_rel:TranslateX(bb_rel:GetWidth()) clip_dir = ImGuiDir.Right end do_forward = true end if not do_forward then return end window.NavRectRel[g.NavLayer] = bb_rel ImGui.NavClearPreferredPosForAxis(ImGuiAxis.X) ImGui.NavClearPreferredPosForAxis(ImGuiAxis.Y) ImGui.NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags) end function ImGui.NavEndFrame() local g = GImGui if g.NavWindowingTarget ~= nil then ImGui.NavUpdateWindowingOverlay() end if g.NavWindow and ImGui.NavMoveRequestButNoResultYet() and bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.WrapMask_) ~= 0 and bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.Forwarded) == 0 then NavUpdateCreateWrappingRequest() end end -- [SECTION] ImGuiTextFilter --- @class ImGuiTextFilter MT.ImGuiTextFilter = {} MT.ImGuiTextFilter.__index = MT.ImGuiTextFilter --- @param default_filter? string function ImGuiTextFilter(default_filter) local this = setmetatable({ InputBuf = {}, _CountExclude = 0, _Items = ImVector() }, MT.ImGuiTextFilter) for i = 1, 256 do this.InputBuf[i] = 0 end if default_filter and default_filter ~= "" then for i = 1, ImMin(#default_filter, 255) do this.InputBuf[i] = string.byte(default_filter, i) end this:Build() end return this end function MT.ImGuiTextFilter:Draw(label, width) if label == nil then label = "Filter (inc,-exc)" end if width ~= nil and width ~= 0.0 then ImGui.SetNextItemWidth(width) end return self:DrawWithHint(label, "incl -excl") end function MT.ImGuiTextFilter:DrawWithHint(label, hint) if label == nil then label = "Filter" end if hint == nil then hint = "incl -excl" end local value_changed = ImGui.InputTextWithHint(label, hint, self.InputBuf, 256) if value_changed then self:Build() end return value_changed end function MT.ImGuiTextFilter:Clear() self.InputBuf[1] = 0; self:Build() end function MT.ImGuiTextFilter:IsActive() return self._Items.Size ~= 0 end -- Items are { Text = string (lowercased), CountInclude = int } function MT.ImGuiTextFilter:Build() local items = self._Items items:resize(0) self._CountExclude = 0 local n = 0 while self.InputBuf[n + 1] ~= nil and self.InputBuf[n + 1] ~= 0 do n = n + 1 end local buf = ImGui._ByteArrayToString(self.InputBuf, 1, n + 1) local buf_e = n + 1 -- exclusive, 1-based local word_b = 1 local seq_start_idx = -1 -- 1-based index into items, -1 = none while word_b < buf_e do while word_b < buf_e and ImCharIsBlankA(string.byte(buf, word_b)) do word_b = word_b + 1 end local is_excl = word_b < buf_e and string.byte(buf, word_b) == 45 -- '-' if is_excl then word_b = word_b + 1 end local is_quote = word_b < buf_e and string.byte(buf, word_b) == 34 -- '"' local word_e if is_quote then word_b = word_b + 1 word_e = string.find(buf, '"', word_b, true) or buf_e if word_e > buf_e then word_e = buf_e end else word_e = word_b while word_e < buf_e do local c = string.byte(buf, word_e) if c == 32 or c == 44 then break end word_e = word_e + 1 end end if word_e - word_b > 0 then local item = { Text = string.lower(string.sub(buf, word_b, word_e - 1)), CountInclude = 0 } if is_excl then items:insert(self._CountExclude + 1, item) self._CountExclude = self._CountExclude + 1 if seq_start_idx ~= -1 then seq_start_idx = seq_start_idx + 1 end else if seq_start_idx == -1 then seq_start_idx = items.Size + 1 end items:push_back(item) items.Data[seq_start_idx].CountInclude = items.Data[seq_start_idx].CountInclude + 1 end end if word_e < buf_e and string.byte(buf, word_e) == 44 then -- ',' seq_start_idx = -1 end word_b = word_e + 1 end end --- @param text string? --- @param text_end? int # exclusive, 1-based function MT.ImGuiTextFilter:PassFilter(text, text_end) local items = self._Items if items.Size == 0 then return true end if text == nil then text = "" end if text_end ~= nil then text = string.sub(text, 1, text_end - 1) end text = string.lower(text) local seq = 1 local seq_excl_end = self._CountExclude + 1 while seq < seq_excl_end do if string.find(text, items.Data[seq].Text, 1, true) then return false end seq = seq + 1 end local seq_end = items.Size + 1 if seq == seq_end then return true end while seq < seq_end do local seq_incl_end = seq + items.Data[seq].CountInclude while seq < seq_incl_end do if not string.find(text, items.Data[seq].Text, 1, true) then break end seq = seq + 1 end if seq == seq_incl_end then return true end seq = seq_incl_end end return false end -- [SECTION] ImGuiListClipper ImGuiListClipperFlags = ImGuiListClipperFlags or { None = 0, NoSetTableRowCounters = bit32.lshift(1, 0) } local function GetSkipItemForListClipping() local g = GImGui if g.CurrentTable then return g.CurrentTable.HostSkipItems end return g.CurrentWindow.SkipItems end local function ClipperRangeFromIndices(min, max) return { Min = min, Max = max, PosToIndexConvert = false, PosToIndexOffsetMin = 0, PosToIndexOffsetMax = 0 } end local function ClipperRangeFromPositions(y1, y2, off_min, off_max) return { Min = y1, Max = y2, PosToIndexConvert = true, PosToIndexOffsetMin = off_min, PosToIndexOffsetMax = off_max } end -- `offset` is a 0-based count of leading ranges to leave alone local function ImGuiListClipper_SortAndFuseRanges(ranges, offset) if offset == nil then offset = 0 end if ranges.Size - offset <= 1 then return end local d = ranges.Data for sort_end = ranges.Size - offset - 1, 1, -1 do for i = offset + 1, sort_end + offset do if d[i].Min > d[i + 1].Min then d[i], d[i + 1] = d[i + 1], d[i] end end end local i = 2 + offset while i <= ranges.Size do IM_ASSERT(not d[i].PosToIndexConvert and not d[i - 1].PosToIndexConvert) if d[i - 1].Max < d[i].Min then i = i + 1 else d[i - 1].Min = ImMin(d[i - 1].Min, d[i].Min) d[i - 1].Max = ImMax(d[i - 1].Max, d[i].Max) ranges:erase(i) end end end local function ImGuiListClipper_SeekCursorAndSetupPrevLine(clipper, pos_y, line_height) local g = GImGui local window = g.CurrentWindow local off_y = pos_y - window.DC.CursorPos.y window.DC.CursorPos.y = pos_y window.DC.CursorMaxPos.y = ImMax(window.DC.CursorMaxPos.y, pos_y - g.Style.ItemSpacing.y) window.DC.CursorPosPrevLine.y = window.DC.CursorPos.y - line_height window.DC.PrevLineSize.y = (line_height - g.Style.ItemSpacing.y) local columns = window.DC.CurrentColumns if columns then columns.LineMinY = window.DC.CursorPos.y end local tbl = g.CurrentTable if tbl then if tbl.IsInsideRow then ImGui.TableEndRow(tbl) end local row_increase = math.floor((off_y / line_height) + 0.5) if row_increase > 0 and bit32.band(clipper.Flags, ImGuiListClipperFlags.NoSetTableRowCounters) == 0 then tbl.CurrentRow = tbl.CurrentRow + row_increase tbl.RowBgColorCounter = tbl.RowBgColorCounter + row_increase end tbl.RowPosY2 = window.DC.CursorPos.y end end --- @class ImGuiListClipper MT.ImGuiListClipper = {} MT.ImGuiListClipper.__index = MT.ImGuiListClipper function ImGuiListClipper() return setmetatable({ DisplayStart = 0, DisplayEnd = 0, UserIndex = 0, ItemsCount = 0, ItemsHeight = 0.0, Flags = 0, StartPosY = 0.0, StartSeekOffsetY = 0.0, Ctx = nil, TempData = nil }, MT.ImGuiListClipper) end --- @param items_count int --- @param items_height? float function MT.ImGuiListClipper:Begin(items_count, items_height) if items_height == nil then items_height = -1.0 end self.Ctx = ImGui.GetCurrentContext() local g = self.Ctx local window = g.CurrentWindow local tbl = g.CurrentTable if tbl and tbl.IsInsideRow then ImGui.TableEndRow(tbl) end self.StartPosY = window.DC.CursorPos.y self.ItemsHeight = items_height self.ItemsCount = items_count self.DisplayStart = -1 self.DisplayEnd = 0 g.ClipperTempDataStacked = g.ClipperTempDataStacked + 1 while g.ClipperTempDataStacked > g.ClipperTempData.Size do g.ClipperTempData:push_back({ ListClipper = nil, LossynessOffset = 0.0, StepNo = 0, ItemsFrozen = 0, Ranges = ImVector() }) end local data = g.ClipperTempData.Data[g.ClipperTempDataStacked] data.ListClipper = self; data.StepNo = 0; data.ItemsFrozen = 0; data.Ranges:resize(0) data.LossynessOffset = window.DC.CursorStartPosLossyness.y self.TempData = data self.StartSeekOffsetY = data.LossynessOffset end function MT.ImGuiListClipper:End() local data = self.TempData if data then local g = self.Ctx if self.ItemsCount >= 0 and self.ItemsCount < INT_MAX and self.DisplayStart >= 0 then self:SeekCursorForItem(self.ItemsCount) end IM_ASSERT(data.ListClipper == self) data.StepNo = data.Ranges.Size g.ClipperTempDataStacked = g.ClipperTempDataStacked - 1 if g.ClipperTempDataStacked > 0 then data = g.ClipperTempData.Data[g.ClipperTempDataStacked] data.ListClipper.TempData = data end self.TempData = nil end self.DisplayStart = self.ItemsCount self.DisplayEnd = self.ItemsCount self.ItemsCount = -1 end function MT.ImGuiListClipper:IncludeItemByIndex(item_index) self:IncludeItemsByIndex(item_index, item_index + 1) end function MT.ImGuiListClipper:IncludeItemsByIndex(item_begin, item_end) local data = self.TempData IM_ASSERT(self.DisplayStart < 0) IM_ASSERT(item_begin <= item_end) if item_begin < item_end then data.Ranges:push_back(ClipperRangeFromIndices(item_begin, item_end)) end end function MT.ImGuiListClipper:SeekCursorForItem(item_n) local pos_y = self.StartPosY + self.StartSeekOffsetY + item_n * self.ItemsHeight ImGuiListClipper_SeekCursorAndSetupPrevLine(self, pos_y, self.ItemsHeight) end local function ImGuiListClipper_StepInternal(clipper) local g = clipper.Ctx local window = g.CurrentWindow local data = clipper.TempData IM_ASSERT(data ~= nil, "Called ImGuiListClipper::Step() too many times, or before ImGuiListClipper::Begin() ?") local tbl = g.CurrentTable if tbl and tbl.IsInsideRow then ImGui.TableEndRow(tbl) end if clipper.ItemsCount == 0 or GetSkipItemForListClipping() then return false end if data.StepNo == 0 and tbl ~= nil and not tbl.IsUnfrozenRows then clipper.DisplayStart = data.ItemsFrozen clipper.DisplayEnd = ImMin(data.ItemsFrozen + 1, clipper.ItemsCount) if clipper.DisplayStart < clipper.DisplayEnd then data.ItemsFrozen = data.ItemsFrozen + 1 end return true end local calc_clipping = false if data.StepNo == 0 then clipper.StartPosY = window.DC.CursorPos.y if clipper.ItemsHeight <= 0.0 then data.Ranges:push_front(ClipperRangeFromIndices(data.ItemsFrozen, data.ItemsFrozen + 1)) clipper.DisplayStart = ImMax(data.Ranges.Data[1].Min, data.ItemsFrozen) clipper.DisplayEnd = ImMin(data.Ranges.Data[1].Max, clipper.ItemsCount) data.StepNo = 1 return true end calc_clipping = true end if clipper.ItemsHeight <= 0.0 then IM_ASSERT(data.StepNo == 1) -- (no float precision mitigation needed: Luau numbers are doubles) clipper.ItemsHeight = (window.DC.CursorPos.y - clipper.StartPosY) / (clipper.DisplayEnd - clipper.DisplayStart) if clipper.ItemsHeight == 0.0 and clipper.ItemsCount == INT_MAX then return false end if clipper.ItemsHeight <= 0.0 then IM_ASSERT_USER_ERROR(clipper.ItemsHeight > 0.0, "ImGuiListClipper: Failed to calculate item height! First item hasn't been submitted by user code, or has not moved the cursor vertically!") return false end calc_clipping = true end local already_submitted = clipper.DisplayEnd if calc_clipping then clipper.StartSeekOffsetY = data.LossynessOffset - data.ItemsFrozen * clipper.ItemsHeight if g.LogEnabled then data.Ranges:push_back(ClipperRangeFromIndices(0, clipper.ItemsCount)) else local is_nav_request = (g.NavMoveScoringItems and g.NavWindow and g.NavWindow.RootWindowForNav == window.RootWindowForNav) and true or false local nav_off_min = (is_nav_request and g.NavMoveClipDir == ImGuiDir.Up) and -1 or 0 local nav_off_max = (is_nav_request and g.NavMoveClipDir == ImGuiDir.Down) and 1 or 0 if is_nav_request then data.Ranges:push_back(ClipperRangeFromPositions(g.NavScoringRect.Min.y, g.NavScoringRect.Max.y, nav_off_min, nav_off_max)) if not g.NavScoringNoClipRect:IsInverted() then data.Ranges:push_back(ClipperRangeFromPositions(g.NavScoringNoClipRect.Min.y, g.NavScoringNoClipRect.Max.y, nav_off_min, nav_off_max)) end end if is_nav_request and bit32.band(g.NavMoveFlags, ImGuiNavMoveFlags.IsTabbing) ~= 0 and g.NavTabbingDir == -1 then data.Ranges:push_back(ClipperRangeFromIndices(clipper.ItemsCount - 1, clipper.ItemsCount)) end local nav_rect_abs = ImGui.WindowRectRelToAbs(window, window.NavRectRel[0]) if g.NavId ~= 0 and window.NavLastIds[0] == g.NavId then data.Ranges:push_back(ClipperRangeFromPositions(nav_rect_abs.Min.y, nav_rect_abs.Max.y, 0, 0)) end local min_y = window.ClipRect.Min.y local max_y = window.ClipRect.Max.y local bs = g.BoxSelectState if bs and bs.IsActive and bs.Window == window then local pad_y = g.Style.ItemSpacing.y min_y = min_y - pad_y max_y = max_y + pad_y if bs.UnclipMode then data.Ranges:push_back(ClipperRangeFromPositions(bs.UnclipRect.Min.y - pad_y, bs.UnclipRect.Max.y + pad_y, 0, 0)) end end data.Ranges:push_back(ClipperRangeFromPositions(min_y, max_y, nav_off_min, nav_off_max)) end for _, range in data.Ranges:iter() do if range.PosToIndexConvert then local m1 = ImTrunc((range.Min - window.DC.CursorPos.y - data.LossynessOffset) / clipper.ItemsHeight) local m2 = ImTrunc(((range.Max - window.DC.CursorPos.y - data.LossynessOffset) / clipper.ItemsHeight) + 0.999999) range.Min = ImClamp(already_submitted + m1 + range.PosToIndexOffsetMin, already_submitted, clipper.ItemsCount - 1) range.Max = ImClamp(already_submitted + m2 + range.PosToIndexOffsetMax, range.Min + 1, clipper.ItemsCount) range.PosToIndexConvert = false end end ImGuiListClipper_SortAndFuseRanges(data.Ranges, data.StepNo) end while data.StepNo < data.Ranges.Size do local r = data.Ranges.Data[data.StepNo + 1] clipper.DisplayStart = ImMax(r.Min, already_submitted) clipper.DisplayEnd = ImMin(r.Max, clipper.ItemsCount) data.StepNo = data.StepNo + 1 if clipper.DisplayStart < clipper.DisplayEnd then if clipper.DisplayStart > already_submitted then clipper:SeekCursorForItem(clipper.DisplayStart) end return true end end if clipper.ItemsCount < INT_MAX then clipper:SeekCursorForItem(clipper.ItemsCount) end return false end function MT.ImGuiListClipper:Step() local ret = ImGuiListClipper_StepInternal(self) if ret and (self.DisplayStart >= self.DisplayEnd) then ret = false end if not ret then self:End() end return ret end function ImGui.CalcClipRectVisibleItemsY(clip_rect, pos, items_height) local out_visible_start = ImMax(ImTrunc((clip_rect.Min.y - pos.y) / items_height), 0) local out_visible_end = ImMax(ImTrunc(ImCeil((clip_rect.Max.y - pos.y) / items_height)), out_visible_start) return out_visible_start, out_visible_end end -- [SECTION] ERROR CHECKING, STATE RECOVERY function ImGuiErrorRecoveryState() return { SizeOfWindowStack = 0, SizeOfIDStack = 0, SizeOfTreeStack = 0, SizeOfColorStack = 0, SizeOfStyleVarStack = 0, SizeOfFontStack = 0, SizeOfFocusScopeStack = 0, SizeOfGroupStack = 0, SizeOfItemFlagsStack = 0, SizeOfBeginPopupStack = 0, SizeOfDisabledStack = 0 } end function ImGui.ErrorRecoveryStoreState(state_out) local g = GImGui state_out.SizeOfWindowStack = g.CurrentWindowStack.Size state_out.SizeOfIDStack = g.CurrentWindow.IDStack.Size state_out.SizeOfTreeStack = g.CurrentWindow.DC.TreeDepth -- NOT g.TreeNodeStack.Size which is a partial stack! state_out.SizeOfColorStack = g.ColorStack.Size state_out.SizeOfStyleVarStack = g.StyleVarStack.Size state_out.SizeOfFontStack = g.FontStack.Size state_out.SizeOfFocusScopeStack = g.FocusScopeStack.Size state_out.SizeOfGroupStack = g.GroupStack.Size state_out.SizeOfItemFlagsStack = g.ItemFlagsStack.Size state_out.SizeOfBeginPopupStack = g.BeginPopupStack.Size state_out.SizeOfDisabledStack = g.DisabledStackSize end function ImGui.ErrorRecoveryTryToRecoverState(state_in) local g = GImGui while g.CurrentWindowStack.Size > state_in.SizeOfWindowStack do local window = g.CurrentWindow if bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) ~= 0 then if g.CurrentTable ~= nil and g.CurrentTable.InnerWindow == g.CurrentWindow then IM_ASSERT_USER_ERROR(false, "Missing EndTable()") ImGui.EndTable() else IM_ASSERT_USER_ERROR(false, "Missing EndChild()") ImGui.EndChild() end else IM_ASSERT_USER_ERROR(false, "Missing End()") ImGui.End() end end if g.CurrentWindowStack.Size == state_in.SizeOfWindowStack then ImGui.ErrorRecoveryTryToRecoverWindowState(state_in) end end function ImGui.ErrorRecoveryTryToRecoverWindowState(state_in) local g = GImGui while g.CurrentTable ~= nil and g.CurrentTable.InnerWindow == g.CurrentWindow do IM_ASSERT_USER_ERROR(false, "Missing EndTable()") ImGui.EndTable() end local window = g.CurrentWindow -- FIXME: Can't recover from inside BeginTabItem/EndTabItem yet. while g.CurrentTabBar ~= nil and g.CurrentTabBar.Window == window do IM_ASSERT_USER_ERROR(false, "Missing EndTabBar()") ImGui.EndTabBar() end while g.CurrentMultiSelect ~= nil and g.CurrentMultiSelect.Storage.Window == window and ImGui.EndMultiSelect do IM_ASSERT_USER_ERROR(false, "Missing EndMultiSelect()") ImGui.EndMultiSelect() end if window.DC.MenuBarAppending then IM_ASSERT_USER_ERROR(false, "Missing EndMenuBar()") ImGui.EndMenuBar() end while window.DC.TreeDepth > state_in.SizeOfTreeStack do IM_ASSERT_USER_ERROR(false, "Missing TreePop()") ImGui.TreePop() end while g.GroupStack.Size > state_in.SizeOfGroupStack do IM_ASSERT_USER_ERROR(false, "Missing EndGroup()") ImGui.EndGroup() end IM_ASSERT(g.GroupStack.Size == state_in.SizeOfGroupStack) while window.IDStack.Size > state_in.SizeOfIDStack do IM_ASSERT_USER_ERROR(false, "Missing PopID()") ImGui.PopID() end while g.DisabledStackSize > state_in.SizeOfDisabledStack do IM_ASSERT_USER_ERROR(false, "Missing EndDisabled()") if bit32.band(g.CurrentItemFlags, ImGuiItemFlags.Disabled) ~= 0 then ImGui.EndDisabled() else ImGui.EndDisabledOverrideReenable() g.CurrentWindowStack:back().DisabledOverrideReenable = false end end IM_ASSERT(g.DisabledStackSize == state_in.SizeOfDisabledStack) while g.ColorStack.Size > state_in.SizeOfColorStack do IM_ASSERT_USER_ERROR(false, "Missing PopStyleColor()") ImGui.PopStyleColor() end while g.ItemFlagsStack.Size > state_in.SizeOfItemFlagsStack do IM_ASSERT_USER_ERROR(false, "Missing PopItemFlag()") ImGui.PopItemFlag() end while g.StyleVarStack.Size > state_in.SizeOfStyleVarStack do IM_ASSERT_USER_ERROR(false, "Missing PopStyleVar()") ImGui.PopStyleVar() end while g.FontStack.Size > state_in.SizeOfFontStack do IM_ASSERT_USER_ERROR(false, "Missing PopFont()") ImGui.PopFont() end while g.FocusScopeStack.Size > state_in.SizeOfFocusScopeStack do IM_ASSERT_USER_ERROR(false, "Missing PopFocusScope()") ImGui.PopFocusScope() end end -- Lines are stored in g.DebugLogBuf (one chunk per call) function ImGui.DebugLog(fmt, ...) local g = GImGui g.DebugLogBuf = g.DebugLogBuf or ImGuiTextBuffer() local str = string.format("[%05d] ", g.FrameCount) .. ((select("#", ...) > 0) and string.format(fmt, ...) or fmt) g.DebugLogBuf:append(str) -- Line index (upstream: g.DebugLogIndex). Lines are complete strings without '\n'. local lines = g.DebugLogLines if lines == nil then lines = { "" }; g.DebugLogLines = lines end for part, nl in string.gmatch(str, "([^\n]*)(\n?)") do lines[#lines] = lines[#lines] .. part if nl ~= "" then lines[#lines + 1] = "" end if part == "" and nl == "" then break end end if bit32.band(g.DebugLogFlags, ImGuiDebugLogFlags.OutputToTTY) ~= 0 then print((string.gsub(str, "\n$", ""))) end end --- @return bool should_assert function ImGui.ErrorLog(msg) local g = GImGui if g == nil then return true end local window = g.CurrentWindow if g.IO.ConfigErrorRecoveryEnableDebugLog then if g.ErrorFirst then ImGui.DebugLog(string.format("[imgui-error] (current settings: Assert=%d, Log=%d, Tooltip=%d)\n", g.IO.ConfigErrorRecoveryEnableAssert and 1 or 0, g.IO.ConfigErrorRecoveryEnableDebugLog and 1 or 0, g.IO.ConfigErrorRecoveryEnableTooltip and 1 or 0)) end ImGui.DebugLog(string.format("[imgui-error] In window '%s': %s\n", window and window.Name or "NULL", tostring(msg))) end g.ErrorFirst = false if g.IO.ConfigErrorRecoveryEnableTooltip then if g.WithinFrameScope and ImGui.BeginErrorTooltip() then if g.ErrorCountCurrentFrame < 20 then ImGui.Text("In window '%s': %s", window and window.Name or "NULL", tostring(msg)) if window and (not window.IsFallbackWindow or window.WasActive) then ImGui.GetForegroundDrawList(window.Viewport):AddRect(window.Pos, window.Pos + window.Size, IM_COL32(255, 0, 0, 255)) end end if g.ErrorCountCurrentFrame == 20 then ImGui.Text("(and more errors)") end ImGui.EndErrorTooltip() end g.ErrorCountCurrentFrame = g.ErrorCountCurrentFrame + 1 end if g.ErrorCallback ~= nil then g.ErrorCallback(g, g.ErrorCallbackUserData, msg) end return g.IO.ConfigErrorRecoveryEnableAssert end function ImGui.ErrorCheckEndFrameFinalizeErrorTooltip() local g = GImGui if g.DebugDrawIdConflictsId ~= 0 and g.IO.KeyCtrl == false then g.DebugDrawIdConflictsCount = g.HoveredIdPreviousFrameItemCount end if g.DebugDrawIdConflictsId ~= 0 and not g.DebugItemPickerActive and ImGui.BeginErrorTooltip() then ImGui.Text("Programmer error: %d visible items with conflicting ID!", g.DebugDrawIdConflictsCount) ImGui.BulletText("Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers!") ImGui.BulletText("Empty label e.g. Button(\"\") == same ID as parent widget/node. Use Button(\"##xx\") instead!") ImGui.BulletText("Set io.ConfigDebugHighlightIdConflicts=false to disable this warning in non-programmers builds.") ImGui.Separator() ImGui.Text("(Hold Ctrl to: ") ImGui.SameLine(0.0, 0.0) ImGui.TextLinkOpenURL("read FAQ \"About ID Stack System\"", "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage") ImGui.SameLine(0.0, 0.0) ImGui.Text(")") ImGui.EndErrorTooltip() end if g.ErrorCountCurrentFrame > 0 and ImGui.BeginErrorTooltip() then ImGui.Separator() ImGui.Text("(Hold Ctrl to: ") ImGui.SameLine(0.0, 0.0) if ImGui.SmallButton("Enable Asserts") then g.IO.ConfigErrorRecoveryEnableAssert = true end ImGui.SameLine(0, 0) ImGui.Text(")") ImGui.EndErrorTooltip() end end function ImGui.BeginErrorTooltip() local g = GImGui local window = ImGui.FindWindowByName("##Tooltip_Error") local use_locked_pos = g.IO.KeyCtrl and window ~= nil and window.WasActive local c = g.Style.Colors[ImGuiCol.PopupBg] ImGui.PushStyleColor(ImGuiCol.PopupBg, ImVec4(ImLerp(c.x, 1.0, 0.15), ImLerp(c.y, 0.0, 0.15), ImLerp(c.z, 0.0, 0.15), ImLerp(c.w, 1.0, 0.15))) if use_locked_pos then ImGui.SetNextWindowPos(g.ErrorTooltipLockedPos) end local _, is_visible = ImGui.Begin("##Tooltip_Error", nil, bit32.bor(ImGuiWindowFlags.Tooltip, ImGuiWindowFlags.NoDecoration, ImGuiWindowFlags.NoMove, ImGuiWindowFlags.NoResize, ImGuiWindowFlags.NoSavedSettings, ImGuiWindowFlags.AlwaysAutoResize)) ImGui.PopStyleColor() if is_visible and g.CurrentWindow.BeginCount == 1 then ImGui.SeparatorText("MESSAGE FROM DEAR IMGUI") ImGui.BringWindowToDisplayFront(g.CurrentWindow) ImGui.BringWindowToFocusFront(g.CurrentWindow) g.ErrorTooltipLockedPos = ImGui.GetWindowPos() elseif not is_visible then ImGui.End() end return is_visible end function ImGui.EndErrorTooltip() ImGui.End() end do --[[ [SECTION] METRICS/DEBUGGER WINDOW, DEBUG LOG, ID STACK TOOL, FONT DEBUG ]] ---------------------------------------------------------------- -- [SECTION] Font atlas / font debugging ---------------------------------------------------------------- -- byte buffers for InputText (same as the demo's DemoBuf/DemoBufStr; kept here so the library doesn't need the demo) local function DemoBuf(str, size) local t = {} for i = 1, #str do t[i] = string.byte(str, i) end t[#str + 1] = 0 if size then for i = #str + 2, size do t[i] = 0 end end return t end local function DemoBufStr(buf) local n = 0 while buf[n + 1] ~= nil and buf[n + 1] ~= 0 do n = n + 1 end if n == 0 then return "" end return ImGui._ByteArrayToString(buf, 1, n + 1) end local function MetricsHelpMarker(desc) ImGui.TextDisabled("(?)") if ImGui.BeginItemTooltip() then ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0) ImGui.TextUnformatted(desc) ImGui.PopTextWrapPos() ImGui.EndTooltip() end end ImGui.MetricsHelpMarker = MetricsHelpMarker local function U8(c) return (c and c > 0) and utf8.char(c) or "" end function ImGui.ShowFontSelector(label) local io = ImGui.GetIO() local font_current = ImGui.GetFont() if ImGui.BeginCombo(label, font_current:GetDebugName()) then for i, font in io.Fonts.Fonts:iter() do ImGui.PushID(i) if ImGui.Selectable(font:GetDebugName(), font == font_current, ImGuiSelectableFlags.SelectOnNav) then io.FontDefault = font end if font == font_current then ImGui.SetItemDefaultFocus() end ImGui.PopID() end ImGui.EndCombo() end ImGui.SameLine() MetricsHelpMarker("- Load additional fonts with io.Fonts->AddFontXXX() functions.\n- On Roblox: register the .ttf bytes with ImGui_ImplRoblox.AddFile() first.\n- Read FAQ and docs/FONTS.md for more details.") end function ImGui.DebugNodeFontGlyph(font, glyph) ImGui.Text("Codepoint: U+%04X", glyph.Codepoint) ImGui.Separator() ImGui.Text("Visible: %d", glyph.Visible and 1 or 0) ImGui.Text("AdvanceX: %.1f", glyph.AdvanceX) ImGui.Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph.X0, glyph.Y0, glyph.X1, glyph.Y1) ImGui.Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph.U0, glyph.V0, glyph.U1, glyph.V1) if glyph.PackId >= 0 then local r = { uv0 = ImVec2(), uv1 = ImVec2() } if font.OwnerAtlas:GetCustomRect(glyph.PackId, r) then ImGui.Text("PackId: 0x%X (%dx%d rect at %d,%d)", glyph.PackId, r.w, r.h, r.x, r.y) end end ImGui.Text("SourceIdx: %d", glyph.SourceIdx) end function ImGui.DebugNodeFontGlyphsForSrcMask(font, baked, src_mask) local draw_list = ImGui.GetWindowDrawList() local glyph_col = ImGui.GetColorU32(ImGuiCol.Text) local cell_size = baked.Size * 1 local cell_spacing = ImGui.GetStyle().ItemSpacing.y -- [port] only scans loaded codepoints (IndexLookup range) instead of the whole 0..0xFFFF range local max_c = baked.IndexLookup.Size - 1 local base = 0 while base <= max_c do local count = 0 for n = 0, 255 do local c = base + n if baked:IsGlyphLoaded(c) then local glyph = baked:FindGlyphNoFallback(c) if glyph and bit32.band(src_mask, bit32.lshift(1, glyph.SourceIdx)) ~= 0 then count = count + 1 end end end if count > 0 and ImGui.TreeNode(base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, (count > 1) and "glyphs" or "glyph") then local base_pos = ImGui.GetCursorScreenPos() for n = 0, 255 do local c = base + n local cell_p1 = ImVec2(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + math.floor(n / 16) * (cell_size + cell_spacing)) local cell_p2 = ImVec2(cell_p1.x + cell_size, cell_p1.y + cell_size) local glyph = baked:IsGlyphLoaded(c) and baked:FindGlyphNoFallback(c) or nil draw_list:AddRect(cell_p1, cell_p2, glyph and IM_COL32(255, 255, 255, 100) or IM_COL32(255, 255, 255, 50)) if glyph and bit32.band(src_mask, bit32.lshift(1, glyph.SourceIdx)) ~= 0 then font:RenderChar(draw_list, cell_size, cell_p1, glyph_col, c) if ImGui.IsMouseHoveringRect(cell_p1, cell_p2) and ImGui.BeginTooltip() then ImGui.DebugNodeFontGlyph(font, glyph) ImGui.EndTooltip() end end end ImGui.Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)) ImGui.TreePop() end base = base + 256 end end function ImGui.DebugNodeFont(font) local g = ImGui.GetCurrentContext() local cfg = g.DebugMetricsConfig local atlas = font.OwnerAtlas local opened = ImGui.TreeNode(tostring(font), "Font: \"%s\": %d sources(s)", font:GetDebugName(), font.Sources.Size) if not opened then ImGui.Indent() end ImGui.Indent() if cfg.ShowFontPreview then ImGui.PushFont(font, 0.0) ImGui.Text("The quick brown fox jumps over the lazy dog") ImGui.PopFont() end if not opened then ImGui.Unindent() ImGui.Unindent() return end if ImGui.SmallButton("Set as default") then ImGui.GetIO().FontDefault = font end ImGui.SameLine() ImGui.BeginDisabled(atlas.Fonts.Size <= 1 or atlas.Locked == true or atlas.RemoveFont == nil) if ImGui.SmallButton("Remove") and atlas.RemoveFont then atlas:RemoveFont(font) end ImGui.EndDisabled() ImGui.SameLine() if ImGui.SmallButton("Clear bakes") then ImFontAtlasFontDiscardBakes(atlas, font, 0) end ImGui.SameLine() if ImGui.SmallButton("Clear unused") then ImFontAtlasFontDiscardBakes(atlas, font, 2) end ImGui.Text("Fallback character: '%s' (U+%04X)", U8(font.FallbackChar), font.FallbackChar or 0) ImGui.Text("Ellipsis character: '%s' (U+%04X)", U8(font.EllipsisChar), font.EllipsisChar or 0) for src_n, src in font.Sources:iter() do if ImGui.TreeNode(tostring(src), "Input %d: '%s' [%d], Oversample: %d,%d, PixelSnapH: %d, Offset: (%.1f,%.1f)", src_n - 1, src.Name or "", src.FontNo or 0, src.OversampleH or 0, src.OversampleV or 0, src.PixelSnapH and 1 or 0, src.GlyphOffset.x, src.GlyphOffset.y) then local loader = src.FontLoader or atlas.FontLoader ImGui.Text("Loader: '%s'", (loader and loader.Name) or "N/A") ImGui.TreePop() end end local builder = atlas.Builder if builder then for _, baked in builder.BakedPool:iter() do if baked.OwnerFont == font then ImGui.PushID(baked.BakedId) if ImGui.TreeNode("Glyphs", "Baked at { %.2fpx, d.%.2f }: %d glyphs%s", baked.Size, baked.RasterizerDensity, baked.Glyphs.Size, (baked.LastUsedFrame < (builder.FrameCount or 0) - 1) and " *Unused*" or "") then if ImGui.SmallButton("Load all") then for c = 0, 0x7F do baked:FindGlyph(c) end -- [port] ASCII only: loading the whole BMP in Luau stalls for seconds end local surface_sqrt = math.floor(math.sqrt(baked.MetricsTotalSurface)) ImGui.Text("Ascent: %f, Descent: %f, Ascent-Descent: %f", baked.Ascent, baked.Descent, baked.Ascent - baked.Descent) ImGui.Text("Texture Area: about %d px ~%dx%d px", baked.MetricsTotalSurface, surface_sqrt, surface_sqrt) for src_n, src in font.Sources:iter() do ImGui.BulletText("Input %d: '%s', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", src_n - 1, src.Name or "", src.OversampleH or 0, src.OversampleV or 0, src.PixelSnapH and 1 or 0, src.GlyphOffset.x, src.GlyphOffset.y) end ImGui.DebugNodeFontGlyphsForSrcMask(font, baked, bit32.bnot(0)) ImGui.TreePop() end ImGui.PopID() end end end ImGui.TreePop() ImGui.Unindent() end local StatusNames = { [0] = "OK", "Destroyed", "WantCreate", "WantUpdates", "WantDestroy" } local FormatNames = { [0] = "RGBA32", "Alpha8" } function ImGui.DebugNodeTexture(tex, int_id, highlight_rect) local g = ImGui.GetCurrentContext() ImGui.PushID(int_id) if ImGui.TreeNode("", "Texture #%03d (%dx%d pixels)", tex.UniqueID or 0, tex.Width, tex.Height) then local cfg = g.DebugMetricsConfig _, cfg.ShowTextureUsedRect = ImGui.Checkbox("Show used rect", cfg.ShowTextureUsedRect) ImGui.PushStyleVar(ImGuiStyleVar.ImageBorderSize, math.max(1.0, g.Style.ImageBorderSize)) local p = ImGui.GetCursorScreenPos() if tex.Status == ImTextureStatus.WantDestroy or tex.Status == ImTextureStatus.Destroyed then ImGui.Dummy(ImVec2(tex.Width, tex.Height)) else local ref = ImTextureRef(); ref._TexData = tex ImGui.ImageWithBg(ref, ImVec2(tex.Width, tex.Height), ImVec2(0.0, 0.0), ImVec2(1.0, 1.0), ImVec4(0.0, 0.0, 0.0, 1.0)) end if cfg.ShowTextureUsedRect and tex.UsedRect then local u = tex.UsedRect ImGui.GetWindowDrawList():AddRect(ImVec2(p.x + u.x, p.y + u.y), ImVec2(p.x + u.x + u.w, p.y + u.y + u.h), IM_COL32(255, 0, 255, 255)) end if highlight_rect ~= nil then local r_outer = ImRect(p.x, p.y, p.x + tex.Width, p.y + tex.Height) local r_inner = ImRect(p.x + highlight_rect.x, p.y + highlight_rect.y, p.x + highlight_rect.x + highlight_rect.w, p.y + highlight_rect.y + highlight_rect.h) ImGui.RenderRectFilledWithHole(ImGui.GetWindowDrawList(), r_outer, r_inner, IM_COL32(0, 0, 0, 100), 0.0) ImGui.GetWindowDrawList():AddRect(r_inner.Min - ImVec2(1, 1), r_inner.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255)) end ImGui.PopStyleVar() ImGui.Text("Status = %s (%d), Format = %s (%d), UseColors = %d", StatusNames[tex.Status] or "?", tex.Status, FormatNames[tex.Format] or "?", tex.Format, tex.UseColors and 1 or 0) ImGui.Text("TexID = %s", tostring(tex.TexID)) ImGui.TreePop() end ImGui.PopID() end function ImGui.ShowFontAtlas(atlas) local g = ImGui.GetCurrentContext() local io = g.IO local style = g.Style ImGui.BeginDisabled() ImGui.CheckboxFlags("io.BackendFlags: RendererHasTextures", io.BackendFlags, ImGuiBackendFlags.RendererHasTextures) ImGui.EndDisabled() ImGui.ShowFontSelector("Font") local changed style.FontSizeBase, changed = ImGui.DragFloat("FontSizeBase", style.FontSizeBase, 0.20, 5.0, 100.0, "%.0f") if changed then style._NextFrameFontSizeBase = style.FontSizeBase end ImGui.SameLine(0.0, 0.0); ImGui.Text(" (out %.2f)", ImGui.GetFontSize()) ImGui.SameLine(); MetricsHelpMarker("- This is scaling font only. General scaling will come later.") style.FontScaleMain = ImGui.DragFloat("FontScaleMain", style.FontScaleMain, 0.02, 0.5, 4.0) style.FontScaleDpi = ImGui.DragFloat("FontScaleDpi", style.FontScaleDpi, 0.02, 0.5, 4.0) ImGui.BulletText("Load a nice font for better results!") ImGui.BulletText("Please submit feedback:") ImGui.SameLine(); ImGui.TextLinkOpenURL("#8465", "https://github.com/ocornut/imgui/issues/8465") ImGui.BulletText("Read FAQ for more details:") ImGui.SameLine(); ImGui.TextLinkOpenURL("dearimgui.com/faq", "https://www.dearimgui.com/faq/") ImGui.SeparatorText("Font List") local cfg = g.DebugMetricsConfig _, cfg.ShowFontPreview = ImGui.Checkbox("Show font preview", cfg.ShowFontPreview == true) if ImGui.TreeNode("Loader", "Loader: '%s'", atlas.FontLoaderName or "NULL") then ImGui.BeginDisabled() ImGui.RadioButtonEx("stb_truetype", true) ImGui.SameLine() ImGui.RadioButtonEx("FreeType", false) ImGui.SetItemTooltip("Not available on Roblox.") ImGui.EndDisabled() ImGui.TreePop() end for i, font in atlas.Fonts:iter() do ImGui.PushID(i) ImGui.DebugNodeFont(font) ImGui.PopID() end ImGui.SeparatorText("Font Atlas") if ImGui.Button("Compact") then atlas:CompactCache() end ImGui.SameLine() if ImGui.Button("Grow") then ImFontAtlasTextureGrow(atlas) end ImGui.SameLine() if ImGui.Button("Clear All") then ImFontAtlasBuildClear(atlas) end ImGui.SetItemTooltip("Destroy cache and custom rectangles.") for tex_n, tex in atlas.TexList:iter() do if tex_n > 1 then ImGui.SameLine() end ImGui.Text("Tex: %dx%d", tex.Width, tex.Height) end local b = atlas.Builder if b then local packed_sqrt = math.floor(math.sqrt(b.RectsPackedSurface or 0)) local discarded_sqrt = math.floor(math.sqrt(b.RectsDiscardedSurface or 0)) ImGui.Text("Packed rects: %d, area: about %d px ~%dx%d px", b.RectsPackedCount or 0, b.RectsPackedSurface or 0, packed_sqrt, packed_sqrt) ImGui.Text("incl. Discarded rects: %d, area: about %d px ~%dx%d px", b.RectsDiscardedCount or 0, b.RectsDiscardedSurface or 0, discarded_sqrt, discarded_sqrt) end for tex_n, tex in atlas.TexList:iter() do if tex_n == atlas.TexList.Size then ImGui.SetNextItemOpen(true, ImGuiCond.Once) end ImGui.DebugNodeTexture(tex, atlas.TexList.Size - tex_n, nil) end end ---------------------------------------------------------------- -- [SECTION] Debug Log window ---------------------------------------------------------------- local function SameLineOrWrap(size) local g = ImGui.GetCurrentContext() local window = g.CurrentWindow local pos = ImVec2(window.DC.CursorPosPrevLine.x + g.Style.ItemSpacing.x, window.DC.CursorPosPrevLine.y) if window.WorkRect:Contains(ImRect(pos, pos + size)) then ImGui.SameLine() end end local function ShowDebugLogFlag(name, flags) local g = ImGui.GetCurrentContext() local size = ImVec2(ImGui.GetFrameHeight() + g.Style.ItemInnerSpacing.x + ImGui.CalcTextSize(name).x, ImGui.GetFrameHeight()) SameLineOrWrap(size) local pressed pressed, g.DebugLogFlags = ImGui.CheckboxFlags(name, g.DebugLogFlags, flags) if pressed and g.IO.KeyShift and bit32.band(g.DebugLogFlags, flags) ~= 0 then g.DebugLogAutoDisableFrames = 2 g.DebugLogAutoDisableFlags = bit32.bor(g.DebugLogAutoDisableFlags or 0, flags) end ImGui.SetItemTooltip("Hold Shift when clicking to enable for 2 frames only (useful for spammy log entries)") end function ImGui.ShowDebugLogWindow(p_open) local g = ImGui.GetCurrentContext() if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasSize) == 0 then ImGui.SetNextWindowSize(ImVec2(0.0, ImGui.GetFontSize() * 12.0), ImGuiCond.FirstUseEver) end local visible p_open, visible = ImGui.Begin("Dear ImGui Debug Log", p_open) if not visible or ImGui.GetCurrentWindow().BeginCount > 1 then ImGui.End() return p_open end local L = ImGuiDebugLogFlags local all_enable_flags = bit32.band(L.EventMask_, bit32.bnot(L.EventInputRouting)) _, g.DebugLogFlags = ImGui.CheckboxFlags("All", g.DebugLogFlags, all_enable_flags) ImGui.SetItemTooltip("(except InputRouting which is spammy)") for _, e in ipairs({ { "Errors", L.EventError }, { "ActiveId", L.EventActiveId }, { "Clipper", L.EventClipper }, { "Docking", L.EventDocking }, { "Focus", L.EventFocus }, { "IO", L.EventIO }, { "Font", L.EventFont }, { "Nav", L.EventNav }, { "Popup", L.EventPopup }, { "Selection", L.EventSelection }, { "Table", L.EventTable }, { "Viewport", L.EventViewport }, { "InputRouting", L.EventInputRouting } }) do ShowDebugLogFlag(e[1], e[2]) end if ImGui.SmallButton("Clear") then if g.DebugLogBuf then g.DebugLogBuf:clear() end g.DebugLogLines = { "" } end ImGui.SameLine() if ImGui.SmallButton("Copy") then ImGui.SetClipboardText(g.DebugLogBuf and g.DebugLogBuf:c_str() or "") end ImGui.SameLine() if ImGui.SmallButton("Configure Outputs..") then ImGui.OpenPopup("Outputs") end if ImGui.BeginPopup("Outputs") then _, g.DebugLogFlags = ImGui.CheckboxFlags("OutputToTTY", g.DebugLogFlags, L.OutputToTTY) ImGui.BeginDisabled() _, g.DebugLogFlags = ImGui.CheckboxFlags("OutputToDebugger", g.DebugLogFlags, L.OutputToDebugger) _, g.DebugLogFlags = ImGui.CheckboxFlags("OutputToTestEngine", g.DebugLogFlags, L.OutputToTestEngine) ImGui.EndDisabled() ImGui.EndPopup() end ImGui.BeginChild("##log", ImVec2(0.0, 0.0), ImGuiChildFlags.Borders, bit32.bor(ImGuiWindowFlags.AlwaysVerticalScrollbar, ImGuiWindowFlags.AlwaysHorizontalScrollbar)) local backup_log_flags = g.DebugLogFlags g.DebugLogFlags = bit32.band(g.DebugLogFlags, bit32.bnot(L.EventClipper)) local lines = g.DebugLogLines or { "" } local count = #lines if lines[count] == "" then count = count - 1 end -- trailing partial line local clipper = ImGuiListClipper() clipper:Begin(count) while clipper:Step() do for line_no = clipper.DisplayStart, clipper.DisplayEnd - 1 do ImGui.TextUnformatted(lines[line_no + 1]) end end g.DebugLogFlags = backup_log_flags if ImGui.GetScrollY() >= ImGui.GetScrollMaxY() then ImGui.SetScrollHereY(1.0) end ImGui.EndChild() ImGui.End() return p_open end ---------------------------------------------------------------- -- [SECTION] ID Stack Tool ---------------------------------------------------------------- local function PathQuery(g) local q = g.DebugItemPathQuery if q == nil then q = { MainID = 0, Active = false, Complete = false, Step = -1, Results = {} } g.DebugItemPathQuery = q g.DebugIDStackTool = { LastActiveFrame = -1, OptHexEncodeNonAsciiChars = true, OptCopyToClipboardOnCtrlC = false, CopyToClipboardLastTime = -FLT_MAX } end return q end -- Update queries. The steps are: -1: query Stack, >= 0: query each stack item local function DebugItemPathQuery_UpdateAndGetHookId(query, id) if query.MainID ~= id then query.MainID = id query.Step = -1 query.Complete = false query.Results = {} end query.Active = false if id == 0 then return 0 end local res = query.Results if query.Step >= 0 and query.Step < #res then if res[query.Step + 1].QuerySuccess or res[query.Step + 1].QueryFrameCount > 2 then query.Step = query.Step + 1 end end query.Complete = (query.Step == #res) if query.Step == -1 then query.Active = true return id elseif query.Step >= 0 and query.Step < #res then res[query.Step + 1].QueryFrameCount = res[query.Step + 1].QueryFrameCount + 1 query.Active = true return res[query.Step + 1].ID end return 0 end function ImGui.UpdateDebugToolItemPathQuery() local g = ImGui.GetCurrentContext() local q = PathQuery(g) local id = 0 if g.DebugIDStackTool.LastActiveFrame + 1 == g.FrameCount then id = (g.HoveredIdPreviousFrame ~= 0 and g.HoveredIdPreviousFrame) or g.ActiveId end g.DebugHookIdInfoId = DebugItemPathQuery_UpdateAndGetHookId(q, id) end -- Hooks called by GetID() family functions. `data_id` is the string/number that was hashed. function ImGui.DebugHookIdInfo(id, data_type, data_id) local g = ImGui.GetCurrentContext() local query = PathQuery(g) if not query.Active then return end local window = g.CurrentWindow if query.Step == -1 then query.Step = 0 query.Results = {} for n = 0, window.IDStack.Size do query.Results[n + 1] = { ID = (n < window.IDStack.Size) and window.IDStack.Data[n + 1] or id, QueryFrameCount = 0, QuerySuccess = false, DataType = -1, Desc = nil } end return end if query.Step ~= window.IDStack.Size then return end local info = query.Results[query.Step + 1] if info == nil or info.ID ~= id then return end if info.Desc == nil then if data_type == ImGuiDataType.S32 then info.Desc = string.format("%d", data_id) elseif data_type == ImGuiDataType.String then info.Desc = tostring(data_id) else info.Desc = string.format("0x%08X [override]", id) end end info.QuerySuccess = true if info.DataType == -1 then info.DataType = data_type end end local function SkipUncontributingPrefix(label) local p = string.find(label, "###", 1, true) if p then return string.sub(label, p) end return label end local function FormatLevelInfo(g, query, n, format_for_ui) local info = query.Results[n + 1] local window = (info.Desc == nil and n == 0) and ImGui.FindWindowByID(info.ID) or nil if window then return string.format(format_for_ui and "\"%s\" [window]" or "%s", SkipUncontributingPrefix(window.Name)) end if info.QuerySuccess then return string.format((format_for_ui and info.DataType == ImGuiDataType.String) and "\"%s\"" or "%s", SkipUncontributingPrefix(info.Desc)) end if query.Step < #query.Results then return "" end return "???" end local function GetResultAsPath(g, query, hex_encode_non_ascii_chars) local out = {} for n = 0, #query.Results - 1 do local desc = FormatLevelInfo(g, query, n, false) out[#out + 1] = (n == 0) and "//" or "/" desc = string.gsub(desc, "/", "\\/") if hex_encode_non_ascii_chars then desc = string.gsub(desc, "[\128-\255]", function(c) return string.format("\\x%02x", string.byte(c)) end) end out[#out + 1] = desc end return table.concat(out) end function ImGui.ShowIDStackToolWindow(p_open) local g = ImGui.GetCurrentContext() if bit32.band(g.NextWindowData.HasFlags, ImGuiNextWindowDataFlags.HasSize) == 0 then ImGui.SetNextWindowSize(ImVec2(0.0, ImGui.GetFontSize() * 8.0), ImGuiCond.FirstUseEver) end local visible p_open, visible = ImGui.Begin("Dear ImGui ID Stack Tool", p_open) if not visible or ImGui.GetCurrentWindow().BeginCount > 1 then ImGui.End() return p_open end local query = PathQuery(g) local tool = g.DebugIDStackTool tool.LastActiveFrame = g.FrameCount local result_path = GetResultAsPath(g, query, tool.OptHexEncodeNonAsciiChars) ImGui.Text("0x%08X", query.MainID) ImGui.SameLine() MetricsHelpMarker("Hover an item with the mouse to display elements of the ID Stack leading to the item's final ID.\nEach level of the stack correspond to a PushID() call.\nAll levels of the stack are hashed together to make the final ID of a widget (ID displayed at the bottom level of the stack).\nRead FAQ entry about the ID stack for details.") local time_since_copy = g.Time - tool.CopyToClipboardLastTime ImGui.PushStyleVarY(ImGuiStyleVar.FramePadding, 0.0) _, tool.OptHexEncodeNonAsciiChars = ImGui.Checkbox("Hex-encode non-ASCII", tool.OptHexEncodeNonAsciiChars) ImGui.SameLine() _, tool.OptCopyToClipboardOnCtrlC = ImGui.Checkbox("Ctrl+C: copy path", tool.OptCopyToClipboardOnCtrlC) ImGui.PopStyleVar() ImGui.SameLine() ImGui.TextColored((time_since_copy >= 0.0 and time_since_copy < 0.75 and math.fmod(time_since_copy, 0.25) < 0.25 * 0.5) and ImVec4(1.0, 1.0, 0.3, 1.0) or ImVec4(), "*COPIED*") if tool.OptCopyToClipboardOnCtrlC and ImGui.Shortcut(bit32.bor(ImGuiMod_Ctrl, ImGuiKey.C), bit32.bor(ImGuiInputFlags.RouteGlobal, ImGuiInputFlags.RouteOverFocused)) then tool.CopyToClipboardLastTime = g.Time ImGui.SetClipboardText(result_path) end ImGui.Text("- Path \"%s\"", query.Complete and result_path or "") ImGui.Separator() if #query.Results > 0 and ImGui.BeginTable("##table", 3, ImGuiTableFlags.Borders) then local id_width = ImGui.CalcTextSize("0xDDDDDDDD").x ImGui.TableSetupColumn("Seed", ImGuiTableColumnFlags.WidthFixed, id_width) ImGui.TableSetupColumn("PushID", ImGuiTableColumnFlags.WidthStretch) ImGui.TableSetupColumn("Result", ImGuiTableColumnFlags.WidthFixed, id_width) ImGui.TableHeadersRow() for n = 0, #query.Results - 1 do local info = query.Results[n + 1] ImGui.TableNextColumn() ImGui.Text("0x%08X", (n > 0) and query.Results[n].ID or 0) ImGui.TableNextColumn() ImGui.TextUnformatted(FormatLevelInfo(g, query, n, true)) ImGui.TableNextColumn() ImGui.Text("0x%08X", info.ID) if n == #query.Results - 1 then ImGui.TableSetBgColor(ImGuiTableBgTarget.CellBg, ImGui.GetColorU32(ImGuiCol.Header)) end end ImGui.EndTable() end ImGui.End() return p_open end ---------------------------------------------------------------- -- [SECTION] Metrics/Debugger window ---------------------------------------------------------------- local function B(v) return v and 1 or 0 end local function FG(window) return ImGui.GetForegroundDrawList(window and window.Viewport or ImGui.GetMainViewport()) end local function YellowRect(window, min, max) FG(window):AddRect(min, max, IM_COL32(255, 255, 0, 255)) end function ImGui.DebugNodeColumns(columns) if not ImGui.TreeNode(columns.ID, "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns.ID, columns.Count, columns.Flags) then return end ImGui.BulletText("Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns.OffMaxX - columns.OffMinX, columns.OffMinX, columns.OffMaxX) for i, column in columns.Columns:iter() do ImGui.BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", i - 1, column.OffsetNorm, ImGui.GetColumnOffsetFromNorm(columns, column.OffsetNorm)) end ImGui.TreePop() end function ImGui.DebugNodeStorage(storage, label) local n = 0 for _ in pairs(storage) do n = n + 1 end if not ImGui.TreeNode(label, "%s: %d entries", label, n) then return end for k, v in pairs(storage) do ImGui.BulletText("Key 0x%08X Value { %s }", tonumber(k) or 0, tostring(v)) end ImGui.TreePop() end function ImGui.DebugNodeDrawList(window, viewport, draw_list, label) local g = ImGui.GetCurrentContext() local cfg = g.DebugMetricsConfig local cmd_count = draw_list.CmdBuffer.Size if cmd_count > 0 and draw_list.CmdBuffer.Data[cmd_count].ElemCount == 0 and draw_list.CmdBuffer.Data[cmd_count].UserCallback == nil then cmd_count = cmd_count - 1 end local node_open = ImGui.TreeNode(tostring(draw_list), "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list._OwnerName or "", draw_list.VtxBuffer.Size, draw_list.IdxBuffer.Size, cmd_count) if draw_list == ImGui.GetWindowDrawList() then ImGui.SameLine() ImGui.TextColored(ImVec4(1.0, 0.4, 0.4, 1.0), "CURRENTLY APPENDING") if node_open then ImGui.TreePop() end return end local fg_draw_list = viewport and ImGui.GetForegroundDrawList(viewport) or nil if window and ImGui.IsItemHovered() and fg_draw_list then fg_draw_list:AddRect(window.Pos, window.Pos + window.Size, IM_COL32(255, 255, 0, 255)) end if not node_open then return end if window and not window.WasActive then ImGui.TextDisabled("Warning: owning Window is inactive. This DrawList is not being rendered!") end local vtx, idx = draw_list.VtxBuffer.Buf, draw_list.IdxBuffer.Data for cmd_n = 1, cmd_count do local pcmd = draw_list.CmdBuffer.Data[cmd_n] if pcmd.UserCallback then ImGui.BulletText("Callback %s", tostring(pcmd.UserCallback)) else local cr = pcmd.ClipRect local open = ImGui.TreeNode(cmd_n, "DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", math.floor(pcmd.ElemCount / 3), tostring(pcmd.TexRef and (pcmd.TexRef._TexID or pcmd.TexRef._TexData)), cr.x, cr.y, cr.z, cr.w) local function ShowMesh(show_mesh, show_aabb) if not fg_draw_list then return end local clip_rect = ImRect(cr.x, cr.y, cr.z, cr.w) local vtxs_rect = ImRect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX) local base = pcmd.IdxOffset for i = base, base + pcmd.ElemCount - 1, 3 do local tri = {} for n = 0, 2 do local vi = idx[i + n + 1] + pcmd.VtxOffset if vi > draw_list.VtxBuffer.Size then return end tri[n + 1] = ImVec2(ImDrawVtx_Pos(vtx, vi)) vtxs_rect:Add(tri[n + 1]) end if show_mesh then fg_draw_list:AddPolyline(tri, 3, IM_COL32(255, 255, 0, 255), 1.0, ImDrawFlags.Closed) end end if show_aabb then fg_draw_list:AddRect(ImTrunc(clip_rect.Min), ImTrunc(clip_rect.Max), IM_COL32(255, 0, 255, 255)) fg_draw_list:AddRect(ImTrunc(vtxs_rect.Min), ImTrunc(vtxs_rect.Max), IM_COL32(0, 255, 255, 255)) end end if ImGui.IsItemHovered() and (cfg.ShowDrawCmdMesh or cfg.ShowDrawCmdBoundingBoxes) then ShowMesh(cfg.ShowDrawCmdMesh, cfg.ShowDrawCmdBoundingBoxes) end if open then ImGui.Selectable(string.format("Mesh: ElemCount: %d, VtxOffset: +%d, IdxOffset: +%d", pcmd.ElemCount, pcmd.VtxOffset, pcmd.IdxOffset)) if ImGui.IsItemHovered() then ShowMesh(true, false) end ImGui.TreePop() end end end ImGui.TreePop() end function ImGui.DebugNodeTabBar(tab_bar, label) local g = ImGui.GetCurrentContext() local is_active = (tab_bar.PrevFrameVisible >= ImGui.GetFrameCount() - 2) local names = {} for tab_n = 1, math.min(tab_bar.Tabs.Size, 3) do local tab = tab_bar.Tabs.Data[tab_n] names[#names + 1] = "'" .. (ImGui.TabBarGetTabName and ImGui.TabBarGetTabName(tab_bar, tab) or "?") .. "'" end local buf = string.format("%s 0x%08X (%d tabs)%s { %s%s", label, tab_bar.ID, tab_bar.Tabs.Size, is_active and "" or " *Inactive*", table.concat(names, ", "), (tab_bar.Tabs.Size > 3) and " ... }" or " } ") if not is_active then ImGui.PushStyleColor(ImGuiCol.Text, g.Style.Colors[ImGuiCol.TextDisabled]) end local open = ImGui.TreeNode(label, "%s", buf) if not is_active then ImGui.PopStyleColor() end if is_active and ImGui.IsItemHovered() then local dl = FG(tab_bar.Window) dl:AddRect(tab_bar.BarRect.Min, tab_bar.BarRect.Max, IM_COL32(255, 255, 0, 255)) dl:AddLine(ImVec2(tab_bar.ScrollingRectMinX, tab_bar.BarRect.Min.y), ImVec2(tab_bar.ScrollingRectMinX, tab_bar.BarRect.Max.y), IM_COL32(0, 255, 0, 255)) dl:AddLine(ImVec2(tab_bar.ScrollingRectMaxX, tab_bar.BarRect.Min.y), ImVec2(tab_bar.ScrollingRectMaxX, tab_bar.BarRect.Max.y), IM_COL32(0, 255, 0, 255)) end if open then for tab_n, tab in tab_bar.Tabs:iter() do ImGui.PushID(tab_n) if ImGui.SmallButton("<") and ImGui.TabBarQueueReorder then ImGui.TabBarQueueReorder(tab_bar, tab, -1) end ImGui.SameLine(0, 2) if ImGui.SmallButton(">") and ImGui.TabBarQueueReorder then ImGui.TabBarQueueReorder(tab_bar, tab, 1) end ImGui.SameLine() ImGui.Text("%02d%s Tab 0x%08X '%s' Offset: %.2f, Width: %.2f/%.2f", tab_n - 1, (tab.ID == tab_bar.SelectedTabId) and "*" or " ", tab.ID, ImGui.TabBarGetTabName and ImGui.TabBarGetTabName(tab_bar, tab) or "?", tab.Offset, tab.Width, tab.ContentWidth) ImGui.PopID() end ImGui.TreePop() end end local function SizingDesc(f) local TF = ImGuiTableFlags f = bit32.band(f, TF.SizingMask_) if f == TF.SizingFixedFit then return "FixedFit" elseif f == TF.SizingFixedSame then return "FixedSame" elseif f == TF.SizingStretchProp then return "StretchProp" elseif f == TF.SizingStretchSame then return "StretchSame" end return "N/A" end function ImGui.DebugNodeTableSettings(settings, tbl) local open = ImGui.TreeNode(tostring(settings), "Settings 0x%08X (%d columns)", settings.ID, settings.ColumnsCount) local hovered = ImGui.IsItemHovered() if hovered and tbl == nil and settings.ID ~= 0 then tbl = ImGui.TableFindByID(settings.ID) end if hovered and tbl ~= nil then YellowRect(tbl.OuterWindow, tbl.OuterRect.Min, tbl.OuterRect.Max) end if open then ImGui.BulletText("SaveFlags: 0x%08X", settings.SaveFlags) ImGui.BulletText("ColumnsCount: %d (max %d)", settings.ColumnsCount, settings.ColumnsCountMax) for n = 0, settings.ColumnsCount - 1 do local c = settings.Columns[n] local dir = (c.SortOrder ~= -1) and c.SortDirection or ImGuiSortDirection.None ImGui.BulletText("Column %d Order %d SortOrder %2d %s Vis %d %s %7.3f ID 0x%08X", n, c.DisplayOrder, c.SortOrder, (dir == ImGuiSortDirection.Ascending) and "Asc" or ((dir == ImGuiSortDirection.Descending) and "Des" or "---"), c.IsEnabled, (c.IsStretch ~= 0) and "Weight" or "Width ", c.WidthOrWeight, c.ID) end ImGui.TreePop() end end function ImGui.DebugNodeTable(tbl) local g = ImGui.GetCurrentContext() local is_active = (tbl.LastFrameActive >= g.FrameCount - 2) if not is_active then ImGui.PushStyleColor(ImGuiCol.Text, g.Style.Colors[ImGuiCol.TextDisabled]) end local open = ImGui.TreeNode(tostring(tbl), "Table 0x%08X (%d columns, in '%s')%s", tbl.ID, tbl.ColumnsCount, tbl.OuterWindow and tbl.OuterWindow.Name or "?", is_active and "" or " *Inactive*") if not is_active then ImGui.PopStyleColor() end if ImGui.IsItemHovered() then YellowRect(tbl.OuterWindow, tbl.OuterRect.Min, tbl.OuterRect.Max) end if not open then return end if tbl.InstanceCurrent > 0 then ImGui.Text("** %d instances of same table! Some data below will refer to last instance.", tbl.InstanceCurrent + 1) end local clear_settings = ImGui.SmallButton("Clear settings") ImGui.BulletText("OuterRect: Pos: (%.1f,%.1f) Size: (%.1f,%.1f) Sizing: '%s'", tbl.OuterRect.Min.x, tbl.OuterRect.Min.y, tbl.OuterRect:GetWidth(), tbl.OuterRect:GetHeight(), SizingDesc(tbl.Flags)) ImGui.BulletText("ColumnsGivenWidth: %.1f, ColumnsAutoFitWidth: %.1f, InnerWidth: %.1f%s", tbl.ColumnsGivenWidth, tbl.ColumnsAutoFitWidth, tbl.InnerWidth, (tbl.InnerWidth == 0.0) and " (auto)" or "") ImGui.BulletText("CellPaddingX: %.1f, CellSpacingX: %.1f/%.1f, OuterPaddingX: %.1f", tbl.CellPaddingX, tbl.CellSpacingX1, tbl.CellSpacingX2, tbl.OuterPaddingX) ImGui.BulletText("HoveredColumnBody: %d, HoveredColumnBorder: %d", tbl.HoveredColumnBody, tbl.HoveredColumnBorder) ImGui.BulletText("ResizedColumn: %d, HeldHeaderColumn: %d, ReorderColumn: %d", tbl.LastResizedColumn, tbl.LastHeldHeaderColumn, tbl.ReorderColumn) for n = 0, tbl.InstanceCurrent do local inst = ImGui.TableGetInstanceData(tbl, n) ImGui.BulletText("Instance %d: HoveredRow: %d, LastOuterHeight: %.2f", n, inst.HoveredRowLast, inst.LastOuterHeight) end local sum_weights = 0.0 for n = 0, tbl.ColumnsCount - 1 do if bit32.band(tbl.Columns[n].Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 then sum_weights = sum_weights + tbl.Columns[n].StretchWeight end end for n = 0, tbl.ColumnsCount - 1 do local c = tbl.Columns[n] local CF = ImGuiTableColumnFlags local buf = string.format( "Column %d order %d '%s': offset %+.2f to %+.2f%s\nEnabled: %d, VisibleX/Y: %d/%d, RequestOutput: %d, SkipItems: %d, DrawChannels: %d,%d\n" .. "WidthGiven: %.1f, Request/Auto: %.1f/%.1f, StretchWeight: %.3f (%.1f%%)\nMinX: %.1f, MaxX: %.1f (%+.1f), ClipRect: %.1f to %.1f (+%.1f)\n" .. "ContentWidth: %.1f,%.1f, HeadersUsed/Ideal %.1f/%.1f\nSort: %d%s, UserData: 0x%08X, Flags: 0x%04X: %s%s%s..", n, c.DisplayOrder, ImGui.TableGetColumnName(tbl, n) or "", c.MinX - tbl.WorkRect.Min.x, c.MaxX - tbl.WorkRect.Min.x, (n < tbl.FreezeColumnsRequest) and " (Frozen)" or "", B(c.IsEnabled), B(c.IsVisibleX), B(c.IsVisibleY), B(c.IsRequestOutput), B(c.IsSkipItems), c.DrawChannelFrozen, c.DrawChannelUnfrozen, c.WidthGiven, c.WidthRequest, c.WidthAuto, c.StretchWeight, (c.StretchWeight > 0.0 and sum_weights > 0) and (c.StretchWeight / sum_weights) * 100.0 or 0.0, c.MinX, c.MaxX, c.MaxX - c.MinX, c.ClipRect.Min.x, c.ClipRect.Max.x, c.ClipRect.Max.x - c.ClipRect.Min.x, c.ContentMaxXFrozen - c.WorkMinX, c.ContentMaxXUnfrozen - c.WorkMinX, c.ContentMaxXHeadersUsed - c.WorkMinX, c.ContentMaxXHeadersIdeal - c.WorkMinX, c.SortOrder, (c.SortDirection == ImGuiSortDirection.Ascending) and " (Asc)" or ((c.SortDirection == ImGuiSortDirection.Descending) and " (Des)" or ""), tonumber(c.UserData) or 0, c.Flags, (bit32.band(c.Flags, CF.WidthStretch) ~= 0) and "WidthStretch " or "", (bit32.band(c.Flags, CF.WidthFixed) ~= 0) and "WidthFixed " or "", (bit32.band(c.Flags, CF.NoResize) ~= 0) and "NoResize " or "") ImGui.Bullet() ImGui.Selectable(buf) if ImGui.IsItemHovered() then YellowRect(tbl.OuterWindow, ImVec2(c.MinX, tbl.OuterRect.Min.y), ImVec2(c.MaxX, tbl.OuterRect.Max.y)) end end local settings = ImGui.TableGetBoundSettings(tbl) if settings then ImGui.DebugNodeTableSettings(settings, tbl) end if clear_settings then tbl.IsResetAllRequest = true end ImGui.TreePop() end local function DebugNodeDockNodeFlags(flags, label, enabled) local D = ImGuiDockNodeFlags ImGui.PushID(label) ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, ImVec2(0.0, 0.0)) ImGui.Text("%s:", label) if not enabled then ImGui.BeginDisabled() end for _, n in ipairs({ "NoResize", "NoResizeX", "NoResizeY", "NoTabBar", "HiddenTabBar", "NoWindowMenuButton", "NoCloseButton", "DockedWindowsInFocusRoute", "NoDocking", "NoDockingSplit", "NoDockingSplitOther", "NoDockingOverMe", "NoDockingOverOther", "NoDockingOverEmpty", "NoUndocking" }) do if D[n] then _, flags = ImGui.CheckboxFlags(n, flags, D[n]) end end if not enabled then ImGui.EndDisabled() end ImGui.PopStyleVar() ImGui.PopID() return flags end function ImGui.DebugNodeDockNode(node, label) local g = ImGui.GetCurrentContext() local is_alive = (g.FrameCount - node.LastFrameAlive < 2) local is_active = (g.FrameCount - node.LastFrameActive < 2) if not is_alive then ImGui.PushStyleColor(ImGuiCol.Text, g.Style.Colors[ImGuiCol.TextDisabled]) end local tree_node_flags = node.IsFocused and ImGuiTreeNodeFlags.Selected or ImGuiTreeNodeFlags.None local open if node.Windows.Size > 0 then open = ImGui.TreeNodeEx(node.ID, tree_node_flags, "%s 0x%04X%s: %d windows (vis: '%s')", label, node.ID, node.IsVisible and "" or " (hidden)", node.Windows.Size, node.VisibleWindow and node.VisibleWindow.Name or "NULL") else local kind = (node.SplitAxis == ImGuiAxis.X) and "horizontal split" or ((node.SplitAxis == ImGuiAxis.Y) and "vertical split" or "empty") open = ImGui.TreeNodeEx(node.ID, tree_node_flags, "%s 0x%04X%s: %s (vis: '%s')", label, node.ID, node.IsVisible and "" or " (hidden)", kind, node.VisibleWindow and node.VisibleWindow.Name or "NULL") end if not is_alive then ImGui.PopStyleColor() end if is_active and ImGui.IsItemHovered() then local window = node.HostWindow or node.VisibleWindow if window then YellowRect(window, node.Pos, node.Pos + node.Size) end end if open then ImGui.BulletText("Pos (%.0f,%.0f), Size (%.0f, %.0f) Ref (%.0f, %.0f)", node.Pos.x, node.Pos.y, node.Size.x, node.Size.y, node.SizeRef.x, node.SizeRef.y) ImGui.DebugNodeWindow(node.HostWindow, "HostWindow") ImGui.DebugNodeWindow(node.VisibleWindow, "VisibleWindow") ImGui.BulletText("SelectedTabID: 0x%08X, LastFocusedNodeID: 0x%08X", node.SelectedTabId, node.LastFocusedNodeId) ImGui.BulletText("Misc:%s%s%s%s%s%s%s", node:IsDockSpace() and " IsDockSpace" or "", node:IsCentralNode() and " IsCentralNode" or "", is_alive and " IsAlive" or "", is_active and " IsActive" or "", node.IsFocused and " IsFocused" or "", node.WantLockSizeOnce and " WantLockSizeOnce" or "", node.HasCentralNodeChild and " HasCentralNodeChild" or "") if ImGui.TreeNode("flags", "Flags Merged: 0x%04X, Local: 0x%04X, InWindows: 0x%04X, Shared: 0x%04X", node.MergedFlags, node.LocalFlags, node.LocalFlagsInWindows, node.SharedFlags) then if ImGui.BeginTable("flags", 4) then ImGui.TableNextColumn(); DebugNodeDockNodeFlags(node.MergedFlags, "MergedFlags", false) ImGui.TableNextColumn(); node.LocalFlags = DebugNodeDockNodeFlags(node.LocalFlags, "LocalFlags", true) ImGui.TableNextColumn(); DebugNodeDockNodeFlags(node.LocalFlagsInWindows, "LocalFlagsInWindows", false) ImGui.TableNextColumn(); node.SharedFlags = DebugNodeDockNodeFlags(node.SharedFlags, "SharedFlags", true) ImGui.EndTable() end ImGui.TreePop() end if node.ParentNode then ImGui.DebugNodeDockNode(node.ParentNode, "ParentNode") end if node.ChildNodes[0] then ImGui.DebugNodeDockNode(node.ChildNodes[0], "Child[0]") end if node.ChildNodes[1] then ImGui.DebugNodeDockNode(node.ChildNodes[1], "Child[1]") end if node.TabBar then ImGui.DebugNodeTabBar(node.TabBar, "TabBar") end ImGui.DebugNodeWindowsList(node.Windows, "Windows") ImGui.TreePop() end end function ImGui.DebugNodeWindow(window, label) if window == nil then ImGui.BulletText("%s: NULL", label) return end local g = ImGui.GetCurrentContext() local is_active = window.WasActive local tree_node_flags = (window == g.NavWindow) and ImGuiTreeNodeFlags.Selected or ImGuiTreeNodeFlags.None if not is_active then ImGui.PushStyleColor(ImGuiCol.Text, g.Style.Colors[ImGuiCol.TextDisabled]) end local open = ImGui.TreeNodeEx(label, tree_node_flags, "%s '%s'%s", label, window.Name, is_active and "" or " *Inactive*") if not is_active then ImGui.PopStyleColor() end if ImGui.IsItemHovered() and is_active then YellowRect(window, window.Pos, window.Pos + window.Size) end if not open then return end if window.MemoryCompacted then ImGui.TextDisabled("Note: some memory buffers have been compacted/freed.") end local W = ImGuiWindowFlags local flags = window.Flags local function F(f, s) return (bit32.band(flags, f) ~= 0) and s or "" end ImGui.DebugNodeDrawList(window, window.Viewport, window.DrawList, "DrawList") ImGui.BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), ContentSize (%.1f,%.1f) Ideal (%.1f,%.1f)", window.Pos.x, window.Pos.y, window.Size.x, window.Size.y, window.ContentSize.x, window.ContentSize.y, window.ContentSizeIdeal.x, window.ContentSizeIdeal.y) ImGui.BulletText("Flags: 0x%08X (%s%s%s%s%s%s%s%s%s..)", flags, F(W.ChildWindow, "Child "), F(W.Tooltip, "Tooltip "), F(W.Popup, "Popup "), F(W.Modal, "Modal "), F(W.ChildMenu, "ChildMenu "), F(W.NoSavedSettings, "NoSavedSettings "), F(W.NoMouseInputs, "NoMouseInputs"), F(W.NoNavInputs, "NoNavInputs"), F(W.AlwaysAutoResize, "AlwaysAutoResize")) if bit32.band(flags, W.ChildWindow) ~= 0 then local cf = window.ChildFlags or 0 local C = ImGuiChildFlags ImGui.BulletText("ChildFlags: 0x%08X (%s%s%s%s..)", cf, (bit32.band(cf, C.Borders) ~= 0) and "Borders " or "", (bit32.band(cf, C.ResizeX) ~= 0) and "ResizeX " or "", (bit32.band(cf, C.ResizeY) ~= 0) and "ResizeY " or "", (bit32.band(cf, C.NavFlattened) ~= 0) and "NavFlattened " or "") end ImGui.BulletText("WindowClassId: 0x%08X", window.WindowClass and window.WindowClass.ClassId or 0) ImGui.BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f) Scrollbar:%s%s", window.Scroll.x, window.ScrollMax.x, window.Scroll.y, window.ScrollMax.y, window.ScrollbarX and "X" or "", window.ScrollbarY and "Y" or "") ImGui.BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", B(window.Active), B(window.WasActive), B(window.WriteAccessed), (window.Active or window.WasActive) and window.BeginOrderWithinContext or -1) ImGui.BulletText("Appearing: %d, Hidden: %d (CanSkip %d Cannot %d), SkipItems: %d", B(window.Appearing), B(window.Hidden), window.HiddenFramesCanSkipItems, window.HiddenFramesCannotSkipItems, B(window.SkipItems)) for layer = 0, 1 do local r = window.NavRectRel[layer] if r.Min.x >= r.Max.x and r.Min.y >= r.Max.y then ImGui.BulletText("NavLastIds[%d]: 0x%08X", layer, window.NavLastIds[layer]) else ImGui.BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window.NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y) end end ImGui.BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window.DC.NavLayersActiveMask, window.NavLastChildNavWindow and window.NavLastChildNavWindow.Name or "NULL") ImGui.BulletText("Viewport: %d%s, ViewportId: 0x%08X, ViewportPos: (%.1f,%.1f)", window.Viewport and (window.Viewport.Idx or 0) or -1, window.ViewportOwned and " (Owned)" or "", window.ViewportId or 0, window.ViewportPos.x, window.ViewportPos.y) ImGui.BulletText("DockId: 0x%04X, DockOrder: %d, Act: %d, Vis: %d", window.DockId or 0, window.DockOrder or -1, B(window.DockIsActive), B(window.DockTabIsVisible)) if window.DockNode or window.DockNodeAsHost then ImGui.DebugNodeDockNode(window.DockNodeAsHost or window.DockNode, window.DockNodeAsHost and "DockNodeAsHost" or "DockNode") end if window.RootWindow ~= window then ImGui.DebugNodeWindow(window.RootWindow, "RootWindow") end if window.RootWindowDockTree and window.RootWindowDockTree ~= window.RootWindow then ImGui.DebugNodeWindow(window.RootWindowDockTree, "RootWindowDockTree") end if window.ParentWindow ~= nil then ImGui.DebugNodeWindow(window.ParentWindow, "ParentWindow") end if window.ParentWindowForFocusRoute ~= nil then ImGui.DebugNodeWindow(window.ParentWindowForFocusRoute, "ParentWindowForFocusRoute") end if window.DC.ChildWindows.Size > 0 then ImGui.DebugNodeWindowsList(window.DC.ChildWindows, "ChildWindows") end if window.ColumnsStorage.Size > 0 and ImGui.TreeNode("Columns", "Columns sets (%d)", window.ColumnsStorage.Size) then for _, columns in window.ColumnsStorage:iter() do ImGui.DebugNodeColumns(columns) end ImGui.TreePop() end ImGui.DebugNodeStorage(window.StateStorage, "Storage") ImGui.TreePop() end function ImGui.DebugNodeWindowSettings(settings) if settings.WantDelete then ImGui.BeginDisabled() end ImGui.BulletText("0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d", settings.ID, settings.Name or "", settings.Pos.x, settings.Pos.y, settings.Size.x, settings.Size.y, B(settings.Collapsed)) if settings.WantDelete then ImGui.EndDisabled() end end function ImGui.DebugNodeWindowsList(windows, label) if not ImGui.TreeNode(label, "%s (%d)", label, windows.Size) then return end for i = windows.Size, 1, -1 do ImGui.PushID(tostring(windows.Data[i])) ImGui.DebugNodeWindow(windows.Data[i], "Window") ImGui.PopID() end ImGui.TreePop() end function ImGui.DebugNodeWindowsListByBeginStackParent(windows, first, last, parent_in_begin_stack) for i = first, last do local window = windows[i] if window.ParentWindowInBeginStack == parent_in_begin_stack then local buf = string.format("[%04d] Window", window.BeginOrderWithinContext) ImGui.DebugNodeWindow(window, buf) ImGui.TreePush(buf) ImGui.DebugNodeWindowsListByBeginStackParent(windows, i + 1, last, window) ImGui.TreePop() end end end function ImGui.DebugNodeViewport(viewport) local g = ImGui.GetCurrentContext() ImGui.SetNextItemOpen(true, ImGuiCond.Once) local open = ImGui.TreeNode(viewport.ID, "Viewport #%d, ID: 0x%08X, Parent: 0x%08X, Window: \"%s\"", viewport.Idx or 0, viewport.ID, viewport.ParentViewportId or 0, viewport.Window and viewport.Window.Name or "N/A") if ImGui.IsItemHovered() then g.DebugMetricsConfig.HighlightViewportID = viewport.ID end if open then ImGui.BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nFrameBufferScale: (%.2f,%.2f)\nWorkArea Inset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f\nMonitor: %d, DpiScale: %.0f%%", viewport.Pos.x, viewport.Pos.y, viewport.Size.x, viewport.Size.y, viewport.FramebufferScale and viewport.FramebufferScale.x or 1, viewport.FramebufferScale and viewport.FramebufferScale.y or 1, viewport.WorkInsetMin.x, viewport.WorkInsetMin.y, viewport.WorkInsetMax.x, viewport.WorkInsetMax.y, viewport.PlatformMonitor or 0, (viewport.DpiScale or 1) * 100.0) ImGui.BulletText("Flags: 0x%04X", viewport.Flags or 0) if viewport.DrawDataP then for _, draw_list in viewport.DrawDataP.CmdLists:iter() do ImGui.DebugNodeDrawList(nil, viewport, draw_list, "DrawList") end end ImGui.TreePop() end end function ImGui.DebugTextEncoding(str) ImGui.Text("Text: \"%s\"", str) if not ImGui.BeginTable("##DebugTextEncoding", 4, bit32.bor(ImGuiTableFlags.Borders, ImGuiTableFlags.RowBg, ImGuiTableFlags.SizingFixedFit, ImGuiTableFlags.Resizable)) then return end ImGui.TableSetupColumn("Offset") ImGui.TableSetupColumn("UTF-8") ImGui.TableSetupColumn("Glyph") ImGui.TableSetupColumn("Codepoint") ImGui.TableHeadersRow() for pos, c in utf8.codes(str) do local nxt = utf8.offset(str, 2, pos) or (#str + 1) ImGui.TableNextColumn() ImGui.Text("%d", pos - 1) ImGui.TableNextColumn() for b = pos, nxt - 1 do if b > pos then ImGui.SameLine() end ImGui.Text("0x%02X", string.byte(str, b)) end ImGui.TableNextColumn() ImGui.TextUnformatted(string.sub(str, pos, nxt - 1)) ImGui.TableNextColumn() ImGui.Text("U+%04X", c) end ImGui.EndTable() end local WRT_Names = { "OuterRect", "OuterRectClipped", "InnerRect", "InnerClipRect", "WorkRect", "Content", "ContentIdeal", "ContentRegionRect" } local TRT_Names = { "OuterRect", "InnerRect", "WorkRect", "HostClipRect", "InnerClipRect", "BackgroundClipRect", "ColumnsRect", "ColumnsWorkRect", "ColumnsClipRect", "ColumnsContentHeadersUsed", "ColumnsContentHeadersIdeal", "ColumnsContentFrozen", "ColumnsContentUnfrozen" } local TRT_ColumnsRect = 6 local function GetWindowRect(window, t) if t == 0 then return window:Rect() elseif t == 1 then return window.OuterRectClipped elseif t == 2 then return window.InnerRect elseif t == 3 then return window.InnerClipRect elseif t == 4 then return window.WorkRect elseif t == 5 then local m = window.InnerRect.Min - window.Scroll + window.WindowPadding; return ImRect(m, m + window.ContentSize) elseif t == 6 then local m = window.InnerRect.Min - window.Scroll + window.WindowPadding; return ImRect(m, m + window.ContentSizeIdeal) else return window.ContentRegionRect end end local function GetTableRect(tbl, t, n) local inst = ImGui.TableGetInstanceData(tbl, tbl.InstanceCurrent) if t == 0 then return tbl.OuterRect elseif t == 1 then return tbl.InnerRect elseif t == 2 then return tbl.WorkRect elseif t == 3 then return tbl.HostClipRect elseif t == 4 then return tbl.InnerClipRect elseif t == 5 then return tbl.BgClipRect end local c = tbl.Columns[n] if t == 6 then return ImRect(c.MinX, tbl.InnerClipRect.Min.y, c.MaxX, tbl.InnerClipRect.Min.y + inst.LastOuterHeight) elseif t == 7 then return ImRect(c.WorkMinX, tbl.WorkRect.Min.y, c.WorkMaxX, tbl.WorkRect.Max.y) elseif t == 8 then return c.ClipRect elseif t == 9 then return ImRect(c.WorkMinX, tbl.InnerClipRect.Min.y, c.ContentMaxXHeadersUsed, tbl.InnerClipRect.Min.y + inst.LastTopHeadersRowHeight) elseif t == 10 then return ImRect(c.WorkMinX, tbl.InnerClipRect.Min.y, c.ContentMaxXHeadersIdeal, tbl.InnerClipRect.Min.y + inst.LastTopHeadersRowHeight) elseif t == 11 then return ImRect(c.WorkMinX, tbl.InnerClipRect.Min.y, c.ContentMaxXFrozen, tbl.InnerClipRect.Min.y + inst.LastFrozenHeight) else return ImRect(c.WorkMinX, tbl.InnerClipRect.Min.y + inst.LastFrozenHeight, c.ContentMaxXUnfrozen, tbl.InnerClipRect.Max.y) end end local function ForEachTable(g, fn) for i = 0, g.Tables:GetMapSize() - 1 do local t = g.Tables:GetByIndex(i) if t then fn(t) end end end local MetricsEncodingBuf = nil local MetricsDockRootOnly = true function ImGui.ShowMetricsWindow(p_open) local g = ImGui.GetCurrentContext() local io = g.IO local cfg = g.DebugMetricsConfig if cfg.ShowDebugLog then cfg.ShowDebugLog = ImGui.ShowDebugLogWindow(cfg.ShowDebugLog) end if cfg.ShowIDStackTool then cfg.ShowIDStackTool = ImGui.ShowIDStackToolWindow(cfg.ShowIDStackTool) end local visible p_open, visible = ImGui.Begin("Dear ImGui Metrics/Debugger", p_open) if not visible or ImGui.GetCurrentWindow().BeginCount > 1 then ImGui.End() return p_open end ImGui.Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM) ImGui.Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0 / math.max(io.Framerate, 0.0001), io.Framerate) ImGui.Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, math.floor(io.MetricsRenderIndices / 3)) ImGui.Text("%d visible windows, %d current allocations", io.MetricsRenderWindows, g.DebugAllocInfo.TotalAllocCount - g.DebugAllocInfo.TotalFreeCount) ImGui.Separator() if cfg.ShowWindowsRectsType == nil or cfg.ShowWindowsRectsType < 0 then cfg.ShowWindowsRectsType = 4 end if cfg.ShowTablesRectsType == nil or cfg.ShowTablesRectsType < 0 then cfg.ShowTablesRectsType = 2 end -- Tools if ImGui.TreeNode("Tools") then ImGui.SeparatorText("Visualize") _, cfg.ShowDebugLog = ImGui.Checkbox("Show Debug Log", cfg.ShowDebugLog == true) ImGui.SameLine(); MetricsHelpMarker("You can also call ImGui::ShowDebugLogWindow() from your code.") _, cfg.ShowIDStackTool = ImGui.Checkbox("Show ID Stack Tool", cfg.ShowIDStackTool == true) ImGui.SameLine(); MetricsHelpMarker("You can also call ImGui::ShowIDStackToolWindow() from your code.") _, cfg.ShowWindowsBeginOrder = ImGui.Checkbox("Show windows begin order", cfg.ShowWindowsBeginOrder == true) _, cfg.ShowWindowsRects = ImGui.Checkbox("Show windows rectangles", cfg.ShowWindowsRects == true) ImGui.SameLine() ImGui.SetNextItemWidth(ImGui.GetFontSize() * 12) local changed cfg.ShowWindowsRectsType, changed = ImGui.Combo("##show_windows_rect_type", cfg.ShowWindowsRectsType, WRT_Names, #WRT_Names, #WRT_Names) cfg.ShowWindowsRects = cfg.ShowWindowsRects or changed if cfg.ShowWindowsRects and g.NavWindow ~= nil then ImGui.BulletText("'%s':", g.NavWindow.Name) ImGui.Indent() for rect_n = 0, #WRT_Names - 1 do local r = GetWindowRect(g.NavWindow, rect_n) ImGui.Text("(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r:GetWidth(), r:GetHeight(), WRT_Names[rect_n + 1]) end ImGui.Unindent() end _, cfg.ShowTablesRects = ImGui.Checkbox("Show tables rectangles", cfg.ShowTablesRects == true) ImGui.SameLine() ImGui.SetNextItemWidth(ImGui.GetFontSize() * 12) cfg.ShowTablesRectsType, changed = ImGui.Combo("##show_table_rects_type", cfg.ShowTablesRectsType, TRT_Names, #TRT_Names, #TRT_Names) cfg.ShowTablesRects = cfg.ShowTablesRects or changed _, g.DebugShowGroupRects = ImGui.Checkbox("Show groups rectangles", g.DebugShowGroupRects == true) ImGui.SeparatorText("Validate") _, io.ConfigDebugBeginReturnValueLoop = ImGui.Checkbox("Debug Begin/BeginChild return value", io.ConfigDebugBeginReturnValueLoop == true) ImGui.SameLine(); MetricsHelpMarker("Some calls to Begin()/BeginChild() will return false.\n\nWill cycle through window depths then repeat. Windows should be flickering while running.") _, cfg.ShowTextEncodingViewer = ImGui.Checkbox("UTF-8 Encoding viewer", cfg.ShowTextEncodingViewer == true) ImGui.SameLine(); MetricsHelpMarker("You can also call ImGui::DebugTextEncoding() from your code with a given string to test that your UTF-8 encoding settings are correct.") if cfg.ShowTextEncodingViewer then MetricsEncodingBuf = MetricsEncodingBuf or DemoBuf("", 64) ImGui.SetNextItemWidth(-FLT_MIN) ImGui.InputText("##DebugTextEncodingBuf", MetricsEncodingBuf, 64) local str = DemoBufStr(MetricsEncodingBuf) if str ~= "" then ImGui.DebugTextEncoding(str) end end ImGui.TreePop() end -- Windows if ImGui.TreeNode("Windows", "Windows (%d)", g.Windows.Size) then ImGui.DebugNodeWindowsList(g.Windows, "By display order") ImGui.DebugNodeWindowsList(g.WindowsFocusOrder, "By focus order (root windows)") if ImGui.TreeNode("By submission order (begin stack)") then local tmp = {} for _, window in g.Windows:iter() do if window.LastFrameActive + 1 >= g.FrameCount then tmp[#tmp + 1] = window end end table.sort(tmp, function(a, b) return a.BeginOrderWithinContext < b.BeginOrderWithinContext end) ImGui.DebugNodeWindowsListByBeginStackParent(tmp, 1, #tmp, nil) ImGui.TreePop() end ImGui.TreePop() end -- DrawLists local drawlist_count = 0 for _, viewport in g.Viewports:iter() do drawlist_count = drawlist_count + viewport.DrawDataP.CmdLists.Size end if ImGui.TreeNode("DrawLists", "DrawLists (%d)", drawlist_count) then _, cfg.ShowDrawCmdMesh = ImGui.Checkbox("Show ImDrawCmd mesh when hovering", cfg.ShowDrawCmdMesh ~= false) _, cfg.ShowDrawCmdBoundingBoxes = ImGui.Checkbox("Show ImDrawCmd bounding boxes when hovering", cfg.ShowDrawCmdBoundingBoxes ~= false) for _, viewport in g.Viewports:iter() do local has = false for _, draw_list in viewport.DrawDataP.CmdLists:iter() do if not has then ImGui.Text("Active DrawLists in Viewport #%d, ID: 0x%08X", viewport.Idx or 0, viewport.ID) end has = true ImGui.DebugNodeDrawList(nil, viewport, draw_list, "DrawList") end end ImGui.TreePop() end -- Viewports if ImGui.TreeNode("Viewports", "Viewports (%d)", g.Viewports.Size) then ImGui.SetNextItemOpen(true, ImGuiCond.Once) if ImGui.TreeNode("Windows Minimap") then ImGui.RenderViewportsThumbnails() ImGui.TreePop() end cfg.HighlightViewportID = 0 for _, viewport in g.Viewports:iter() do ImGui.DebugNodeViewport(viewport) end ImGui.TreePop() end -- Fonts for i, atlas in g.FontAtlases:iter() do if ImGui.TreeNode(tostring(atlas), "Fonts (%d), Textures (%d)", atlas.Fonts.Size, atlas.TexList.Size) then ImGui.ShowFontAtlas(atlas) ImGui.TreePop() end end -- Popups if ImGui.TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size) then for _, popup_data in g.OpenPopupStack:iter() do local window = popup_data.Window local W = ImGuiWindowFlags ImGui.BulletText("PopupID: %08x, Window: '%s' (%s%s), RestoreNavWindow '%s', ParentWindow '%s'", popup_data.PopupId, window and window.Name or "NULL", (window and bit32.band(window.Flags, W.ChildWindow) ~= 0) and "Child;" or "", (window and bit32.band(window.Flags, W.ChildMenu) ~= 0) and "Menu;" or "", popup_data.RestoreNavWindow and popup_data.RestoreNavWindow.Name or "NULL", (window and window.ParentWindow) and window.ParentWindow.Name or "NULL") end ImGui.TreePop() end -- TabBars if ImGui.TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.AliveCount) then for n = 0, g.TabBars:GetMapSize() - 1 do local tab_bar = g.TabBars:GetByIndex(n) if tab_bar then ImGui.PushID(n) ImGui.DebugNodeTabBar(tab_bar, "TabBar") ImGui.PopID() end end ImGui.TreePop() end -- Tables if ImGui.TreeNode("Tables", "Tables (%d)", g.Tables.AliveCount) then ForEachTable(g, ImGui.DebugNodeTable) ImGui.TreePop() end -- Docking if ImGui.TreeNode("Docking") then local dc = g.DockContext _, MetricsDockRootOnly = ImGui.Checkbox("List root nodes", MetricsDockRootOnly) _, cfg.ShowDockingNodes = ImGui.Checkbox("Ctrl shows window dock info", cfg.ShowDockingNodes == true) if ImGui.SmallButton("Clear nodes") then ImGui.DockContextClearNodes(g, 0, true) end ImGui.SameLine() if ImGui.SmallButton("Rebuild all") then dc.WantFullRebuild = true end local ids = {} for id in pairs(dc.Nodes) do ids[#ids + 1] = id end table.sort(ids) for _, id in ipairs(ids) do local node = dc.Nodes[id] if node and (not MetricsDockRootOnly or node:IsRootNode()) then ImGui.DebugNodeDockNode(node, "Node") end end ImGui.TreePop() end -- Settings if ImGui.TreeNode("Settings") then if ImGui.SmallButton("Clear") then ImGui.ClearIniSettings() end ImGui.SameLine() if ImGui.SmallButton("Save to memory") then ImGui.SaveIniSettingsToMemory() end ImGui.SameLine() ImGui.TextUnformatted(io.IniFilename and ("\"" .. io.IniFilename .. "\"") or "") ImGui.Text("SettingsDirtyTimer %.2f", g.SettingsDirtyTimer) if ImGui.TreeNode("SettingsHandlers", "Settings handlers: (%d)", g.SettingsHandlers.Size) then for _, handler in g.SettingsHandlers:iter() do ImGui.BulletText("\"%s\"", handler.TypeName) end ImGui.TreePop() end if ImGui.TreeNode("SettingsWindows", "Settings packed data: Windows: %d entries", g.SettingsWindows.Size) then for _, settings in g.SettingsWindows:iter() do ImGui.DebugNodeWindowSettings(settings) end ImGui.TreePop() end if ImGui.TreeNode("SettingsTables", "Settings packed data: Tables: %d entries", g.SettingsTables.Size) then for _, settings in g.SettingsTables:iter() do ImGui.DebugNodeTableSettings(settings, nil) end ImGui.TreePop() end if ImGui.TreeNode("SettingsDocking", "Settings packed data: Docking") then local dc = g.DockContext ImGui.Text("In SettingsWindows:") for _, settings in g.SettingsWindows:iter() do if (settings.DockId or 0) ~= 0 then ImGui.BulletText("Window '%s' -> DockId %08X DockOrder=%d", settings.Name or "", settings.DockId, settings.DockOrder or -1) end end ImGui.Text("In SettingsNodes:") for _, settings in dc.NodesSettings:iter() do local selected_tab_name = nil if (settings.SelectedTabId or 0) ~= 0 then local window = ImGui.FindWindowByID(settings.SelectedTabId) selected_tab_name = window and window.Name or nil end ImGui.BulletText("Node %08X, Parent %08X, SelectedTab %08X ('%s')", settings.ID, settings.ParentNodeId or 0, settings.SelectedTabId or 0, selected_tab_name or ((settings.SelectedTabId or 0) ~= 0 and "N/A" or "")) end ImGui.TreePop() end local ini = g.SettingsIniData or "" if ImGui.TreeNode("SettingsIniData", "Settings unpacked data (.ini): %d bytes", #ini) then ImGui.InputTextMultiline("##Ini", DemoBuf(ini, #ini + 1), #ini + 1, ImVec2(-FLT_MIN, ImGui.GetTextLineHeight() * 20), ImGuiInputTextFlags.ReadOnly) ImGui.TreePop() end ImGui.TreePop() end -- Inputs if ImGui.TreeNode("Inputs") then ImGui.Text("KEYBOARD/GAMEPAD/MOUSE KEYS") ImGui.Indent() ImGui.Text("Keys down:") for key = ImGuiKey.NamedKey_BEGIN, ImGuiKey.NamedKey_END - 1 do if ImGui.IsKeyDown(key) then ImGui.SameLine(); ImGui.Text("\"%s\"", ImGui.GetKeyName(key)) end end ImGui.Text("Keys pressed:") for key = ImGuiKey.NamedKey_BEGIN, ImGuiKey.NamedKey_END - 1 do if ImGui.IsKeyPressed(key) then ImGui.SameLine(); ImGui.Text("\"%s\"", ImGui.GetKeyName(key)) end end ImGui.Text("Keys released:") for key = ImGuiKey.NamedKey_BEGIN, ImGuiKey.NamedKey_END - 1 do if ImGui.IsKeyReleased(key) then ImGui.SameLine(); ImGui.Text("\"%s\"", ImGui.GetKeyName(key)) end end ImGui.Text("Keys mods: %s%s%s%s", io.KeyCtrl and "Ctrl " or "", io.KeyShift and "Shift " or "", io.KeyAlt and "Alt " or "", io.KeySuper and "Super " or "") ImGui.Unindent() ImGui.Text("MOUSE STATE") ImGui.Indent() if ImGui.IsMousePosValid() then ImGui.Text("Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y) else ImGui.Text("Mouse pos: ") end ImGui.Text("Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y) ImGui.Text("Mouse down:") for i = 0, 2 do if ImGui.IsMouseDown(i) then ImGui.SameLine(); ImGui.Text("b%d (%.02f secs)", i, io.MouseDownDuration[i]) end end ImGui.Text("Mouse clicked:") for i = 0, 2 do if ImGui.IsMouseClicked(i) then ImGui.SameLine(); ImGui.Text("b%d (%d)", i, io.MouseClickedCount[i]) end end ImGui.Text("Mouse released:") for i = 0, 2 do if ImGui.IsMouseReleased(i) then ImGui.SameLine(); ImGui.Text("b%d", i) end end ImGui.Text("Mouse wheel: %.1f", io.MouseWheel) ImGui.Text("MouseStationaryTimer: %.2f", g.MouseStationaryTimer) ImGui.Unindent() ImGui.Text("MOUSE WHEELING") ImGui.Indent() ImGui.Text("WheelingWindow: '%s'", g.WheelingWindow and g.WheelingWindow.Name or "NULL") ImGui.Text("WheelingWindowReleaseTimer: %.2f", g.WheelingWindowReleaseTimer or 0) ImGui.Text("WheelingAxisAvg[] = { %.3f, %.3f }", g.WheelingAxisAvg.x, g.WheelingAxisAvg.y) ImGui.Unindent() ImGui.TreePop() end -- Internal state if ImGui.TreeNode("Internal state") then local function N(w) return w and w.Name or "NULL" end ImGui.Text("WINDOWING") ImGui.Indent() ImGui.Text("HoveredWindow: '%s'", N(g.HoveredWindow)) ImGui.Text("HoveredWindow->Root: '%s'", g.HoveredWindow and N(g.HoveredWindow.RootWindowDockTree) or "NULL") ImGui.Text("HoveredWindowUnderMovingWindow: '%s'", N(g.HoveredWindowUnderMovingWindow)) ImGui.Text("HoveredDockNode: 0x%08X", g.DebugHoveredDockNode and g.DebugHoveredDockNode.ID or 0) ImGui.Text("MovingWindow: '%s'", N(g.MovingWindow)) ImGui.Unindent() ImGui.Text("ITEMS") ImGui.Indent() ImGui.Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, B(g.ActiveIdAllowOverlap)) ImGui.Text("ActiveIdWindow: '%s'", N(g.ActiveIdWindow)) ImGui.Text("HoveredId: 0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredIdPreviousFrame, g.HoveredIdTimer, B(g.HoveredIdAllowOverlap)) ImGui.Text("HoverItemDelayId: 0x%08X, Timer: %.2f, ClearTimer: %.2f", g.HoverItemDelayId or 0, g.HoverItemDelayTimer or 0, g.HoverItemDelayClearTimer or 0) ImGui.Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\"", B(g.DragDropActive), g.DragDropPayload.SourceId or 0, tostring(g.DragDropPayload.DataType or "")) ImGui.Unindent() ImGui.Text("NAV,FOCUS") ImGui.Indent() ImGui.Text("NavWindow: '%s'", N(g.NavWindow)) ImGui.Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer) 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) ImGui.Text("NavCursorVisible: %d, NavHighlightItemUnderNav: %d", B(g.NavCursorVisible), B(g.NavHighlightItemUnderNav)) ImGui.Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId or 0) ImGui.Text("NavWindowingTarget: '%s'", N(g.NavWindowingTarget)) ImGui.Unindent() ImGui.TreePop() end -- Overlays if cfg.ShowWindowsRects or cfg.ShowWindowsBeginOrder then for _, window in g.Windows:iter() do if window.WasActive then local draw_list = FG(window) if cfg.ShowWindowsRects then local r = GetWindowRect(window, cfg.ShowWindowsRectsType) draw_list:AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255)) end if cfg.ShowWindowsBeginOrder and bit32.band(window.Flags, ImGuiWindowFlags.ChildWindow) == 0 then local font_size = ImGui.GetFontSize() draw_list:AddRectFilled(window.Pos, window.Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255)) draw_list:AddText(window.Pos, IM_COL32(255, 255, 255, 255), tostring(window.BeginOrderWithinContext)) end end end end if cfg.ShowTablesRects then ForEachTable(g, function(tbl) if tbl.LastFrameActive < g.FrameCount - 1 then return end local draw_list = FG(tbl.OuterWindow) if cfg.ShowTablesRectsType >= TRT_ColumnsRect then for column_n = 0, tbl.ColumnsCount - 1 do local r = GetTableRect(tbl, cfg.ShowTablesRectsType, column_n) local hot = (tbl.HoveredColumnBody == column_n) draw_list:AddRect(r.Min, r.Max, hot and IM_COL32(255, 255, 128, 255) or IM_COL32(255, 0, 128, 255), 0.0, hot and 3.0 or 1.0) end else local r = GetTableRect(tbl, cfg.ShowTablesRectsType, -1) draw_list:AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255)) end end) end ImGui.End() return p_open end end --[[ [SECTION] METRICS/DEBUGGER WINDOW, DEBUG LOG, ID STACK TOOL, FONT DEBUG ]] return true -- [Roblox] ModuleScripts must return exactly one value