diff --git a/build/ImGui.linemap.json b/build/ImGui.linemap.json index 0737758..58887b0 100644 --- a/build/ImGui.linemap.json +++ b/build/ImGui.linemap.json @@ -1 +1 @@ -[[12, "imgui.lua"], [13756, "imgui_demo.lua"], [14689, "imgui_demo_2.lua"], [17764, "imgui_demo_apps.lua"], [19023, "imgui_demo_widgets.lua"], [20947, "imgui_docking.lua"], [24075, "imgui_dragdrop.lua"], [24367, "imgui_draw.lua"], [29550, "imgui_h.lua"], [31760, "imgui_impl_roblox.lua"], [32547, "imgui_impl_roblox_fonts.lua"], [33325, "imgui_internal.lua"], [36481, "imgui_tables.lua"], [40828, "imgui_tabs.lua"], [41995, "imgui_widgets.lua"], [50519, "imstb_rectpack.lua"], [50965, "imstb_textedit.lua"], [52206, "imstb_truetype.lua"], [54674, "imstd_minstdio.lua"]] \ No newline at end of file +[[12, "imgui.lua"], [13781, "imgui_demo.lua"], [14714, "imgui_demo_2.lua"], [17789, "imgui_demo_apps.lua"], [19048, "imgui_demo_tools.lua"], [19629, "imgui_demo_widgets.lua"], [21553, "imgui_docking.lua"], [24681, "imgui_dragdrop.lua"], [24973, "imgui_draw.lua"], [30161, "imgui_h.lua"], [32373, "imgui_impl_roblox.lua"], [33160, "imgui_impl_roblox_fonts.lua"], [33938, "imgui_internal.lua"], [37108, "imgui_tables.lua"], [41455, "imgui_tabs.lua"], [42622, "imgui_widgets.lua"], [51146, "imstb_rectpack.lua"], [51592, "imstb_textedit.lua"], [52833, "imstb_truetype.lua"], [55301, "imstd_minstdio.lua"]] \ No newline at end of file diff --git a/build/ImGui.luau b/build/ImGui.luau index 3085e42..713d9d9 100644 --- a/build/ImGui.luau +++ b/build/ImGui.luau @@ -2566,7 +2566,9 @@ end function ImGui.PushOverrideID(id) local g = GImGui local window = g.CurrentWindow - -- TODO: DebugHookIdInfo() + if g.DebugHookIdInfoId == id and id ~= 0 then + ImGui.DebugHookIdInfo(id, ImGuiDataType.ID, nil) + end window.IDStack:push_back(id) end @@ -2581,11 +2583,17 @@ end function MT.ImGuiWindow:GetID(id) local seed = self.IDStack:back() + local h if type(id) == "string" then - return ImHashStr(id, nil, seed) + h = ImHashStr(id, nil, seed) else --- @cast id int - return ImHashData(id, -1, seed) + 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 @@ -2621,10 +2629,16 @@ end -- Overloads: GetIDWithSeed(str, str_end, seed) / GetIDWithSeed(n, seed) function ImGui.GetIDWithSeed(a, b, c) + local id if type(a) == "number" then - return ImHashData(a, -1, b) + id = ImHashData(a, -1, b) + else + id = ImHashStr(a, b and (b - 1) or nil, c) end - return ImHashStr(a, b and (b - 1) or nil, c) + 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 @@ -8303,6 +8317,9 @@ function ImGui.NewFrame() g.CurrentItemFlags = g.ItemFlagsStack:back() g.GroupStack:resize(0) + -- [DEBUG] ID Stack Tool query + ImGui.UpdateDebugToolItemPathQuery() + -- Docking ImGui.DockContextNewFrameUpdateDocking(g) @@ -13648,8 +13665,16 @@ function ImGui.DebugLog(fmt, ...) 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(str) + print((string.gsub(str, "\n$", ""))) end end @@ -19019,6 +19044,587 @@ end return true end +__FILES['imgui_demo_tools.lua'] = function() +-- Demo tools: ShowAboutWindow(), ShowFontAtlas(), ShowFontSelector(), ShowDebugLogWindow(), ShowIDStackToolWindow() +local _ + +---------------------------------------------------------------- +-- [SECTION] About Window / ShowAboutWindow() +---------------------------------------------------------------- + +local show_config_info = false +function ImGui.ShowAboutWindow(p_open) + local visible + p_open, visible = ImGui.Begin("About ImGui Sincerely", p_open, ImGuiWindowFlags.AlwaysAutoResize) + if not visible then ImGui.End(); return p_open end + ImGui.Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM) + ImGui.TextLinkOpenURL("Homepage", "https://github.com/ocornut/imgui"); ImGui.SameLine() + ImGui.TextLinkOpenURL("FAQ", "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md"); ImGui.SameLine() + ImGui.TextLinkOpenURL("Wiki", "https://github.com/ocornut/imgui/wiki"); ImGui.SameLine() + ImGui.TextLinkOpenURL("Extensions", "https://github.com/ocornut/imgui/wiki/Useful-Extensions"); ImGui.SameLine() + ImGui.TextLinkOpenURL("Releases", "https://github.com/ocornut/imgui/releases"); ImGui.SameLine() + ImGui.TextLinkOpenURL("Funding", "https://github.com/ocornut/imgui/wiki/Funding") + ImGui.Separator() + ImGui.Text("(c) 2014-2026 Omar Cornut") + ImGui.Text("Developed by Omar Cornut and all Dear ImGui contributors.") + ImGui.Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information.") + ImGui.Text("If your company uses this, please consider funding the project.") + ImGui.Text("Luau/Roblox port: imgui_impl_roblox (software rasterizer into EditableImage tiles).") + + _, show_config_info = ImGui.Checkbox("Config/Build Information", show_config_info) + if show_config_info then + local io = ImGui.GetIO() + local style = ImGui.GetStyle() + local copy_to_clipboard = ImGui.Button("Copy to clipboard") + local child_size = ImVec2(0, ImGui.GetTextLineHeightWithSpacing() * 18) + ImGui.BeginChild(ImGui.GetID("cfg_infos"), child_size, ImGuiChildFlags.FrameStyle) + if copy_to_clipboard then + ImGui.LogToClipboard() + ImGui.LogText("// (Copy from the next line. Keep the ``` markers for formatting.)\n") + ImGui.LogText("```cpp\n") + end + ImGui.Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM) + ImGui.Separator() + ImGui.Text("define: IMGUI_HAS_VIEWPORT") + ImGui.Text("define: IMGUI_HAS_DOCK") + ImGui.Text("runtime: Luau (%s)", _VERSION or "?") + ImGui.Separator() + ImGui.Text("io.BackendPlatformName: %s", io.BackendPlatformName or "NULL") + ImGui.Text("io.BackendRendererName: %s", io.BackendRendererName or "NULL") + ImGui.Text("io.ConfigFlags: 0x%08X", io.ConfigFlags) + local CF = ImGuiConfigFlags + for _, n in ipairs({ "NavEnableKeyboard", "NavEnableGamepad", "NoMouse", "NoMouseCursorChange", "NoKeyboard", "DockingEnable", "ViewportsEnable" }) do + if CF[n] and bit32.band(io.ConfigFlags, CF[n]) ~= 0 then ImGui.Text(" %s", n) end + end + for _, n in ipairs({ "MouseDrawCursor", "ConfigDpiScaleFonts", "ConfigDpiScaleViewports", "ConfigViewportsNoAutoMerge", "ConfigViewportsNoTaskBarIcon", + "ConfigViewportsNoDecoration", "ConfigViewportsNoDefaultParent", "ConfigDockingNoSplit", "ConfigDockingNoDockingOver", "ConfigDockingWithShift", + "ConfigDockingAlwaysTabBar", "ConfigDockingTransparentPayload", "ConfigMacOSXBehaviors", "ConfigNavMoveSetMousePos", "ConfigNavCaptureKeyboard", + "ConfigInputTextCursorBlink", "ConfigWindowsResizeFromEdges", "ConfigWindowsMoveFromTitleBarOnly" }) do + if io[n] == true then ImGui.Text("io.%s", n) end + end + if (io.ConfigMemoryCompactTimer or -1) >= 0.0 then ImGui.Text("io.ConfigMemoryCompactTimer = %.1f", io.ConfigMemoryCompactTimer) end + ImGui.Text("io.BackendFlags: 0x%08X", io.BackendFlags) + local BF = ImGuiBackendFlags + for _, n in ipairs({ "HasGamepad", "HasMouseCursors", "HasSetMousePos", "PlatformHasViewports", "HasMouseHoveredViewport", "HasParentViewport", + "RendererHasVtxOffset", "RendererHasTextures", "RendererHasViewports" }) do + if BF[n] and bit32.band(io.BackendFlags, BF[n]) ~= 0 then ImGui.Text(" %s", n) end + end + ImGui.Separator() + local atlas = io.Fonts + ImGui.Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", atlas.Fonts.Size, atlas.Flags or 0, atlas.TexData and atlas.TexData.Width or 0, atlas.TexData and atlas.TexData.Height or 0) + ImGui.Text("io.Fonts->FontLoaderName: %s", atlas.FontLoaderName or "NULL") + ImGui.Text("io.DisplaySize: %.2f,%.2f", io.DisplaySize.x, io.DisplaySize.y) + ImGui.Text("io.DisplayFramebufferScale: %.2f,%.2f", io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y) + ImGui.Separator() + ImGui.Text("style.WindowPadding: %.2f,%.2f", style.WindowPadding.x, style.WindowPadding.y) + ImGui.Text("style.WindowBorderSize: %.2f", style.WindowBorderSize) + ImGui.Text("style.FramePadding: %.2f,%.2f", style.FramePadding.x, style.FramePadding.y) + ImGui.Text("style.FrameRounding: %.2f", style.FrameRounding) + ImGui.Text("style.FrameBorderSize: %.2f", style.FrameBorderSize) + ImGui.Text("style.ItemSpacing: %.2f,%.2f", style.ItemSpacing.x, style.ItemSpacing.y) + ImGui.Text("style.ItemInnerSpacing: %.2f,%.2f", style.ItemInnerSpacing.x, style.ItemInnerSpacing.y) + if copy_to_clipboard then + ImGui.LogText("\n```\n") + ImGui.LogFinish() + end + ImGui.EndChild() + end + ImGui.End() + return p_open +end + +---------------------------------------------------------------- +-- [SECTION] Font atlas / font debugging +---------------------------------------------------------------- + +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 = ImFontAtlasPackGetRect(font.OwnerAtlas, glyph.PackId) + if 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 + +return true +end + __FILES['imgui_demo_widgets.lua'] = function() -- Demo: DemoWindowWidgets*() sections (port of imgui_demo.cpp). Globals picked up by DemoWindowWidgets() in imgui_demo.lua. local _ @@ -25780,7 +26386,7 @@ function ImFontAtlasBakedDiscard(atlas, font, baked) font.LastBaked = nil end -local function ImFontAtlasFontDiscardBakes(atlas, font, unused_frames) +function ImFontAtlasFontDiscardBakes(atlas, font, unused_frames) local builder = atlas.Builder if builder then for baked_n = 1, builder.BakedPool.Size do @@ -26112,7 +26718,7 @@ function ImFontAtlasTextureGetSizeEstimate(atlas) end --- @param atlas ImFontAtlas -local function ImFontAtlasBuildClear(atlas) +function ImFontAtlasBuildClear(atlas) local new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas) -- ImVec2i ImFontAtlasBuildDestroy(atlas) ImFontAtlasTextureAdd(atlas, new_tex_size.x, new_tex_size.y) @@ -26464,6 +27070,11 @@ function MT.ImFontBaked:FindGlyph(c) return (glyph) and glyph or self.Glyphs.Data[self.FallbackGlyphIndex] end +function MT.ImFontBaked:IsGlyphLoaded(c) + if c >= self.IndexLookup.Size then return false end + return self.IndexLookup.Data[c + 1] ~= IM_FONTGLYPH_INDEX_UNUSED +end + --- @param c ImWchar --- @return ImFontGlyph? function MT.ImFontBaked:FindGlyphNoFallback(c) @@ -29660,6 +30271,8 @@ ImGuiDataType = String = 9, -- string (provided for user convenience, not supported by scalar widgets) COUNT = 10 } +ImGuiDataType.Pointer = 11 -- [internal] +ImGuiDataType.ID = 12 -- [internal] ImTextureID_Invalid = -1 @@ -33763,10 +34376,23 @@ IM_DRAWLIST_ARCFAST_SAMPLE_MAX = IM_DRAWLIST_ARCFAST_TABLE_SIZE function IM_ASSERT_USER_ERROR(_EXPR, _MSG) if not (_EXPR) or (_EXPR) == 0 then if not ImGui.ErrorLog or ImGui.ErrorLog(_MSG) then error(_MSG, 2) end end end function IM_ASSERT_USER_ERROR_RET(_EXPR, _MSG) if not (_EXPR) or (_EXPR) == 0 then error(_MSG, 2) end end -function IMGUI_DEBUG_LOG_ACTIVEID(_str, ...) local g = GImGui if bitAnd(g.DebugLogFlags, ImGuiDebugLogFlags.EventActiveId) ~= 0 then print(string.format(_str, ...)) end end -function IMGUI_DEBUG_LOG_POPUP(_str, ...) local g = GImGui if bitAnd(g.DebugLogFlags, ImGuiDebugLogFlags.EventPopup) ~= 0 then print(string.format(_str, ...)) end end -function IMGUI_DEBUG_LOG_FONT(_str, ...) local g2 = GImGui if g2 and bitAnd(g2.DebugLogFlags, ImGuiDebugLogFlags.EventFont) ~= 0 then print(string.format(_str, ...)) end end -function IMGUI_DEBUG_LOG_VIEWPORT(_str, ...) local g = GImGui if bitAnd(g.DebugLogFlags, ImGuiDebugLogFlags.EventViewport) ~= 0 then print(string.format(_str, ...)) end end +local function DebugLogIf(flag, fmt, ...) + local g = GImGui + if g and bitAnd(g.DebugLogFlags, flag) ~= 0 then ImGui.DebugLog(fmt, ...) end +end +function IMGUI_DEBUG_LOG_ACTIVEID(...) DebugLogIf(ImGuiDebugLogFlags.EventActiveId, ...) end +function IMGUI_DEBUG_LOG_POPUP(...) DebugLogIf(ImGuiDebugLogFlags.EventPopup, ...) end +function IMGUI_DEBUG_LOG_FONT(...) DebugLogIf(ImGuiDebugLogFlags.EventFont, ...) end +function IMGUI_DEBUG_LOG_VIEWPORT(...) DebugLogIf(ImGuiDebugLogFlags.EventViewport, ...) end +function IMGUI_DEBUG_LOG_FOCUS(...) DebugLogIf(ImGuiDebugLogFlags.EventFocus, ...) end +function IMGUI_DEBUG_LOG_NAV(...) DebugLogIf(ImGuiDebugLogFlags.EventNav, ...) end +function IMGUI_DEBUG_LOG_IO(...) DebugLogIf(ImGuiDebugLogFlags.EventIO, ...) end +function IMGUI_DEBUG_LOG_INPUTROUTING(...) DebugLogIf(ImGuiDebugLogFlags.EventInputRouting, ...) end +function IMGUI_DEBUG_LOG_TABLE(...) DebugLogIf(ImGuiDebugLogFlags.EventTable, ...) end +function IMGUI_DEBUG_LOG_DOCKING(...) DebugLogIf(ImGuiDebugLogFlags.EventDocking, ...) end +function IMGUI_DEBUG_LOG_SELECTION(...) DebugLogIf(ImGuiDebugLogFlags.EventSelection, ...) end +function IMGUI_DEBUG_LOG_CLIPPER(...) DebugLogIf(ImGuiDebugLogFlags.EventClipper, ...) end +function IMGUI_DEBUG_LOG_ERROR(...) DebugLogIf(ImGuiDebugLogFlags.EventError, ...) end --- @alias ImGuiSelectionUserData any ImGuiSelectionUserData_Invalid = -1 @@ -35447,6 +36073,7 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct DebugFlashStyleColorIdx = nil, DebugMetricsConfig = ImGuiMetricsConfig(), + DebugHookIdInfoId = 0, DebugAllocInfo = ImGuiDebugAllocInfo(), --- Misc @@ -54899,6 +55526,7 @@ IM_INCLUDE('imgui_impl_roblox.lua') IM_INCLUDE('imgui_demo.lua') IM_INCLUDE('imgui_demo_2.lua') IM_INCLUDE('imgui_demo_apps.lua') +IM_INCLUDE('imgui_demo_tools.lua') IM_INCLUDE('imgui_demo_widgets.lua') return { @@ -54972,8 +55600,17 @@ return { GetMinimumStepAtDecimalPrecision = GetMinimumStepAtDecimalPrecision, HelpMarker = HelpMarker, IMGUI_DEBUG_LOG_ACTIVEID = IMGUI_DEBUG_LOG_ACTIVEID, + IMGUI_DEBUG_LOG_CLIPPER = IMGUI_DEBUG_LOG_CLIPPER, + IMGUI_DEBUG_LOG_DOCKING = IMGUI_DEBUG_LOG_DOCKING, + IMGUI_DEBUG_LOG_ERROR = IMGUI_DEBUG_LOG_ERROR, + IMGUI_DEBUG_LOG_FOCUS = IMGUI_DEBUG_LOG_FOCUS, IMGUI_DEBUG_LOG_FONT = IMGUI_DEBUG_LOG_FONT, + IMGUI_DEBUG_LOG_INPUTROUTING = IMGUI_DEBUG_LOG_INPUTROUTING, + IMGUI_DEBUG_LOG_IO = IMGUI_DEBUG_LOG_IO, + IMGUI_DEBUG_LOG_NAV = IMGUI_DEBUG_LOG_NAV, IMGUI_DEBUG_LOG_POPUP = IMGUI_DEBUG_LOG_POPUP, + IMGUI_DEBUG_LOG_SELECTION = IMGUI_DEBUG_LOG_SELECTION, + IMGUI_DEBUG_LOG_TABLE = IMGUI_DEBUG_LOG_TABLE, IMGUI_DEBUG_LOG_VIEWPORT = IMGUI_DEBUG_LOG_VIEWPORT, IMGUI_DEMO_MARKER = IMGUI_DEMO_MARKER, IMGUI_DISABLE_DEBUG_TOOLS = IMGUI_DISABLE_DEBUG_TOOLS, @@ -55067,6 +55704,7 @@ return { ImFontAtlasBakedGetId = ImFontAtlasBakedGetId, ImFontAtlasBakedGetOrAdd = ImFontAtlasBakedGetOrAdd, ImFontAtlasBuildAcceptCodepointForSource = ImFontAtlasBuildAcceptCodepointForSource, + ImFontAtlasBuildClear = ImFontAtlasBuildClear, ImFontAtlasBuildDestroy = ImFontAtlasBuildDestroy, ImFontAtlasBuildInit = ImFontAtlasBuildInit, ImFontAtlasBuildMain = ImFontAtlasBuildMain, @@ -55085,6 +55723,7 @@ return { ImFontAtlasFlags = ImFontAtlasFlags, ImFontAtlasFontDestroyOutput = ImFontAtlasFontDestroyOutput, ImFontAtlasFontDestroySourceData = ImFontAtlasFontDestroySourceData, + ImFontAtlasFontDiscardBakes = ImFontAtlasFontDiscardBakes, ImFontAtlasFontInitOutput = ImFontAtlasFontInitOutput, ImFontAtlasFontSourceAddToFont = ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit = ImFontAtlasFontSourceInit, diff --git a/imgui/imgui.lua b/imgui/imgui.lua index 40eecf0..6c79434 100644 --- a/imgui/imgui.lua +++ b/imgui/imgui.lua @@ -2555,7 +2555,9 @@ end function ImGui.PushOverrideID(id) local g = GImGui local window = g.CurrentWindow - -- TODO: DebugHookIdInfo() + if g.DebugHookIdInfoId == id and id ~= 0 then + ImGui.DebugHookIdInfo(id, ImGuiDataType.ID, nil) + end window.IDStack:push_back(id) end @@ -2570,11 +2572,17 @@ end function MT.ImGuiWindow:GetID(id) local seed = self.IDStack:back() + local h if type(id) == "string" then - return ImHashStr(id, nil, seed) + h = ImHashStr(id, nil, seed) else --- @cast id int - return ImHashData(id, -1, seed) + 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 @@ -2610,10 +2618,16 @@ end -- Overloads: GetIDWithSeed(str, str_end, seed) / GetIDWithSeed(n, seed) function ImGui.GetIDWithSeed(a, b, c) + local id if type(a) == "number" then - return ImHashData(a, -1, b) + id = ImHashData(a, -1, b) + else + id = ImHashStr(a, b and (b - 1) or nil, c) end - return ImHashStr(a, b and (b - 1) or nil, c) + 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 @@ -8292,6 +8306,9 @@ function ImGui.NewFrame() g.CurrentItemFlags = g.ItemFlagsStack:back() g.GroupStack:resize(0) + -- [DEBUG] ID Stack Tool query + ImGui.UpdateDebugToolItemPathQuery() + -- Docking ImGui.DockContextNewFrameUpdateDocking(g) @@ -13637,8 +13654,16 @@ function ImGui.DebugLog(fmt, ...) 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(str) + print((string.gsub(str, "\n$", ""))) end end diff --git a/imgui/imgui_demo_tools.lua b/imgui/imgui_demo_tools.lua new file mode 100644 index 0000000..149fab0 --- /dev/null +++ b/imgui/imgui_demo_tools.lua @@ -0,0 +1,578 @@ +-- Demo tools: ShowAboutWindow(), ShowFontAtlas(), ShowFontSelector(), ShowDebugLogWindow(), ShowIDStackToolWindow() +local _ + +---------------------------------------------------------------- +-- [SECTION] About Window / ShowAboutWindow() +---------------------------------------------------------------- + +local show_config_info = false +function ImGui.ShowAboutWindow(p_open) + local visible + p_open, visible = ImGui.Begin("About ImGui Sincerely", p_open, ImGuiWindowFlags.AlwaysAutoResize) + if not visible then ImGui.End(); return p_open end + ImGui.Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM) + ImGui.TextLinkOpenURL("Homepage", "https://github.com/ocornut/imgui"); ImGui.SameLine() + ImGui.TextLinkOpenURL("FAQ", "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md"); ImGui.SameLine() + ImGui.TextLinkOpenURL("Wiki", "https://github.com/ocornut/imgui/wiki"); ImGui.SameLine() + ImGui.TextLinkOpenURL("Extensions", "https://github.com/ocornut/imgui/wiki/Useful-Extensions"); ImGui.SameLine() + ImGui.TextLinkOpenURL("Releases", "https://github.com/ocornut/imgui/releases"); ImGui.SameLine() + ImGui.TextLinkOpenURL("Funding", "https://github.com/ocornut/imgui/wiki/Funding") + ImGui.Separator() + ImGui.Text("(c) 2014-2026 Omar Cornut") + ImGui.Text("Developed by Omar Cornut and all Dear ImGui contributors.") + ImGui.Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information.") + ImGui.Text("If your company uses this, please consider funding the project.") + ImGui.Text("Luau/Roblox port: imgui_impl_roblox (software rasterizer into EditableImage tiles).") + + _, show_config_info = ImGui.Checkbox("Config/Build Information", show_config_info) + if show_config_info then + local io = ImGui.GetIO() + local style = ImGui.GetStyle() + local copy_to_clipboard = ImGui.Button("Copy to clipboard") + local child_size = ImVec2(0, ImGui.GetTextLineHeightWithSpacing() * 18) + ImGui.BeginChild(ImGui.GetID("cfg_infos"), child_size, ImGuiChildFlags.FrameStyle) + if copy_to_clipboard then + ImGui.LogToClipboard() + ImGui.LogText("// (Copy from the next line. Keep the ``` markers for formatting.)\n") + ImGui.LogText("```cpp\n") + end + ImGui.Text("Dear ImGui %s (%d)", IMGUI_VERSION, IMGUI_VERSION_NUM) + ImGui.Separator() + ImGui.Text("define: IMGUI_HAS_VIEWPORT") + ImGui.Text("define: IMGUI_HAS_DOCK") + ImGui.Text("runtime: Luau (%s)", _VERSION or "?") + ImGui.Separator() + ImGui.Text("io.BackendPlatformName: %s", io.BackendPlatformName or "NULL") + ImGui.Text("io.BackendRendererName: %s", io.BackendRendererName or "NULL") + ImGui.Text("io.ConfigFlags: 0x%08X", io.ConfigFlags) + local CF = ImGuiConfigFlags + for _, n in ipairs({ "NavEnableKeyboard", "NavEnableGamepad", "NoMouse", "NoMouseCursorChange", "NoKeyboard", "DockingEnable", "ViewportsEnable" }) do + if CF[n] and bit32.band(io.ConfigFlags, CF[n]) ~= 0 then ImGui.Text(" %s", n) end + end + for _, n in ipairs({ "MouseDrawCursor", "ConfigDpiScaleFonts", "ConfigDpiScaleViewports", "ConfigViewportsNoAutoMerge", "ConfigViewportsNoTaskBarIcon", + "ConfigViewportsNoDecoration", "ConfigViewportsNoDefaultParent", "ConfigDockingNoSplit", "ConfigDockingNoDockingOver", "ConfigDockingWithShift", + "ConfigDockingAlwaysTabBar", "ConfigDockingTransparentPayload", "ConfigMacOSXBehaviors", "ConfigNavMoveSetMousePos", "ConfigNavCaptureKeyboard", + "ConfigInputTextCursorBlink", "ConfigWindowsResizeFromEdges", "ConfigWindowsMoveFromTitleBarOnly" }) do + if io[n] == true then ImGui.Text("io.%s", n) end + end + if (io.ConfigMemoryCompactTimer or -1) >= 0.0 then ImGui.Text("io.ConfigMemoryCompactTimer = %.1f", io.ConfigMemoryCompactTimer) end + ImGui.Text("io.BackendFlags: 0x%08X", io.BackendFlags) + local BF = ImGuiBackendFlags + for _, n in ipairs({ "HasGamepad", "HasMouseCursors", "HasSetMousePos", "PlatformHasViewports", "HasMouseHoveredViewport", "HasParentViewport", + "RendererHasVtxOffset", "RendererHasTextures", "RendererHasViewports" }) do + if BF[n] and bit32.band(io.BackendFlags, BF[n]) ~= 0 then ImGui.Text(" %s", n) end + end + ImGui.Separator() + local atlas = io.Fonts + ImGui.Text("io.Fonts: %d fonts, Flags: 0x%08X, TexSize: %d,%d", atlas.Fonts.Size, atlas.Flags or 0, atlas.TexData and atlas.TexData.Width or 0, atlas.TexData and atlas.TexData.Height or 0) + ImGui.Text("io.Fonts->FontLoaderName: %s", atlas.FontLoaderName or "NULL") + ImGui.Text("io.DisplaySize: %.2f,%.2f", io.DisplaySize.x, io.DisplaySize.y) + ImGui.Text("io.DisplayFramebufferScale: %.2f,%.2f", io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y) + ImGui.Separator() + ImGui.Text("style.WindowPadding: %.2f,%.2f", style.WindowPadding.x, style.WindowPadding.y) + ImGui.Text("style.WindowBorderSize: %.2f", style.WindowBorderSize) + ImGui.Text("style.FramePadding: %.2f,%.2f", style.FramePadding.x, style.FramePadding.y) + ImGui.Text("style.FrameRounding: %.2f", style.FrameRounding) + ImGui.Text("style.FrameBorderSize: %.2f", style.FrameBorderSize) + ImGui.Text("style.ItemSpacing: %.2f,%.2f", style.ItemSpacing.x, style.ItemSpacing.y) + ImGui.Text("style.ItemInnerSpacing: %.2f,%.2f", style.ItemInnerSpacing.x, style.ItemInnerSpacing.y) + if copy_to_clipboard then + ImGui.LogText("\n```\n") + ImGui.LogFinish() + end + ImGui.EndChild() + end + ImGui.End() + return p_open +end + +---------------------------------------------------------------- +-- [SECTION] Font atlas / font debugging +---------------------------------------------------------------- + +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 = ImFontAtlasPackGetRect(font.OwnerAtlas, glyph.PackId) + if 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 + +return true diff --git a/imgui/imgui_draw.lua b/imgui/imgui_draw.lua index 06534d4..5104b14 100644 --- a/imgui/imgui_draw.lua +++ b/imgui/imgui_draw.lua @@ -1414,7 +1414,7 @@ function ImFontAtlasBakedDiscard(atlas, font, baked) font.LastBaked = nil end -local function ImFontAtlasFontDiscardBakes(atlas, font, unused_frames) +function ImFontAtlasFontDiscardBakes(atlas, font, unused_frames) local builder = atlas.Builder if builder then for baked_n = 1, builder.BakedPool.Size do @@ -1746,7 +1746,7 @@ function ImFontAtlasTextureGetSizeEstimate(atlas) end --- @param atlas ImFontAtlas -local function ImFontAtlasBuildClear(atlas) +function ImFontAtlasBuildClear(atlas) local new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas) -- ImVec2i ImFontAtlasBuildDestroy(atlas) ImFontAtlasTextureAdd(atlas, new_tex_size.x, new_tex_size.y) @@ -2098,6 +2098,11 @@ function MT.ImFontBaked:FindGlyph(c) return (glyph) and glyph or self.Glyphs.Data[self.FallbackGlyphIndex] end +function MT.ImFontBaked:IsGlyphLoaded(c) + if c >= self.IndexLookup.Size then return false end + return self.IndexLookup.Data[c + 1] ~= IM_FONTGLYPH_INDEX_UNUSED +end + --- @param c ImWchar --- @return ImFontGlyph? function MT.ImFontBaked:FindGlyphNoFallback(c) diff --git a/imgui/imgui_h.lua b/imgui/imgui_h.lua index 349176e..b61b3e2 100644 --- a/imgui/imgui_h.lua +++ b/imgui/imgui_h.lua @@ -111,6 +111,8 @@ ImGuiDataType = String = 9, -- string (provided for user convenience, not supported by scalar widgets) COUNT = 10 } +ImGuiDataType.Pointer = 11 -- [internal] +ImGuiDataType.ID = 12 -- [internal] ImTextureID_Invalid = -1 diff --git a/imgui/imgui_internal.lua b/imgui/imgui_internal.lua index 24cd6b1..7d4262f 100644 --- a/imgui/imgui_internal.lua +++ b/imgui/imgui_internal.lua @@ -439,10 +439,23 @@ IM_DRAWLIST_ARCFAST_SAMPLE_MAX = IM_DRAWLIST_ARCFAST_TABLE_SIZE function IM_ASSERT_USER_ERROR(_EXPR, _MSG) if not (_EXPR) or (_EXPR) == 0 then if not ImGui.ErrorLog or ImGui.ErrorLog(_MSG) then error(_MSG, 2) end end end function IM_ASSERT_USER_ERROR_RET(_EXPR, _MSG) if not (_EXPR) or (_EXPR) == 0 then error(_MSG, 2) end end -function IMGUI_DEBUG_LOG_ACTIVEID(_str, ...) local g = GImGui if bitAnd(g.DebugLogFlags, ImGuiDebugLogFlags.EventActiveId) ~= 0 then print(string.format(_str, ...)) end end -function IMGUI_DEBUG_LOG_POPUP(_str, ...) local g = GImGui if bitAnd(g.DebugLogFlags, ImGuiDebugLogFlags.EventPopup) ~= 0 then print(string.format(_str, ...)) end end -function IMGUI_DEBUG_LOG_FONT(_str, ...) local g2 = GImGui if g2 and bitAnd(g2.DebugLogFlags, ImGuiDebugLogFlags.EventFont) ~= 0 then print(string.format(_str, ...)) end end -function IMGUI_DEBUG_LOG_VIEWPORT(_str, ...) local g = GImGui if bitAnd(g.DebugLogFlags, ImGuiDebugLogFlags.EventViewport) ~= 0 then print(string.format(_str, ...)) end end +local function DebugLogIf(flag, fmt, ...) + local g = GImGui + if g and bitAnd(g.DebugLogFlags, flag) ~= 0 then ImGui.DebugLog(fmt, ...) end +end +function IMGUI_DEBUG_LOG_ACTIVEID(...) DebugLogIf(ImGuiDebugLogFlags.EventActiveId, ...) end +function IMGUI_DEBUG_LOG_POPUP(...) DebugLogIf(ImGuiDebugLogFlags.EventPopup, ...) end +function IMGUI_DEBUG_LOG_FONT(...) DebugLogIf(ImGuiDebugLogFlags.EventFont, ...) end +function IMGUI_DEBUG_LOG_VIEWPORT(...) DebugLogIf(ImGuiDebugLogFlags.EventViewport, ...) end +function IMGUI_DEBUG_LOG_FOCUS(...) DebugLogIf(ImGuiDebugLogFlags.EventFocus, ...) end +function IMGUI_DEBUG_LOG_NAV(...) DebugLogIf(ImGuiDebugLogFlags.EventNav, ...) end +function IMGUI_DEBUG_LOG_IO(...) DebugLogIf(ImGuiDebugLogFlags.EventIO, ...) end +function IMGUI_DEBUG_LOG_INPUTROUTING(...) DebugLogIf(ImGuiDebugLogFlags.EventInputRouting, ...) end +function IMGUI_DEBUG_LOG_TABLE(...) DebugLogIf(ImGuiDebugLogFlags.EventTable, ...) end +function IMGUI_DEBUG_LOG_DOCKING(...) DebugLogIf(ImGuiDebugLogFlags.EventDocking, ...) end +function IMGUI_DEBUG_LOG_SELECTION(...) DebugLogIf(ImGuiDebugLogFlags.EventSelection, ...) end +function IMGUI_DEBUG_LOG_CLIPPER(...) DebugLogIf(ImGuiDebugLogFlags.EventClipper, ...) end +function IMGUI_DEBUG_LOG_ERROR(...) DebugLogIf(ImGuiDebugLogFlags.EventError, ...) end --- @alias ImGuiSelectionUserData any ImGuiSelectionUserData_Invalid = -1 @@ -2123,6 +2136,7 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct DebugFlashStyleColorIdx = nil, DebugMetricsConfig = ImGuiMetricsConfig(), + DebugHookIdInfoId = 0, DebugAllocInfo = ImGuiDebugAllocInfo(), --- Misc