Files
imgui-luau/imgui.lua
T

18458 lines
794 KiB
Lua

--- ImGui Sincerely 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) ]]
--- ImGui Sincerely
-- 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) ]]
--- ImGui Sincerely
-- 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, "ImGui Sincerely 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 : "<NULL>", 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<ImGuiWindow>
--- @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 "<NULL>", 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 "<NULL>")
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 "<NULL>")
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 "<NULL>")
-- 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("ImGui Sincerely 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("ImGui Sincerely 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("ImGui Sincerely 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 "<NULL>")
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: <INVALID>") 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