From 71843392f156c9e485cef308efde738e9fc70786 Mon Sep 17 00:00:00 2001 From: 4DBug <4DBug@github.com> Date: Thu, 8 Oct 2026 10:41:15 -0500 Subject: [PATCH] Perf: allocation-free ItemSize/ItemAdd/IsMouseHoveringRect/GetColorU32/RenderArrow, @native on hot UI paths; t_drawhash output-equivalence check --- build/ImGui.linemap.json | 2 +- build/ImGui.luau | 183 +++++++++++++++++++++-------- imgui/imgui.lua | 112 ++++++++++++------ imgui/imgui_draw.lua | 37 ++++-- imgui/imgui_h.lua | 8 ++ imgui/imgui_internal.lua | 16 +++ imgui/imgui_widgets.lua | 10 ++ tools/test/scripts/t_drawhash.luau | 29 +++++ 8 files changed, 296 insertions(+), 101 deletions(-) create mode 100644 tools/test/scripts/t_drawhash.luau diff --git a/build/ImGui.linemap.json b/build/ImGui.linemap.json index a19568f..51390b9 100644 --- a/build/ImGui.linemap.json +++ b/build/ImGui.linemap.json @@ -1 +1 @@ -[[12, "imgui.lua"], [13761, "imgui_demo.lua"], [14694, "imgui_demo_2.lua"], [17769, "imgui_demo_apps.lua"], [19028, "imgui_demo_tools.lua"], [20354, "imgui_demo_widgets.lua"], [22278, "imgui_docking.lua"], [25406, "imgui_dragdrop.lua"], [25698, "imgui_draw.lua"], [30930, "imgui_h.lua"], [33096, "imgui_impl_roblox.lua"], [33920, "imgui_impl_roblox_fonts.lua"], [40323, "imgui_internal.lua"], [43479, "imgui_tables.lua"], [47826, "imgui_tabs.lua"], [48993, "imgui_widgets.lua"], [57517, "imstb_rectpack.lua"], [57963, "imstb_textedit.lua"], [59204, "imstb_truetype.lua"], [61672, "imstd_minstdio.lua"]] \ No newline at end of file +[[12, "imgui.lua"], [13795, "imgui_demo.lua"], [14728, "imgui_demo_2.lua"], [17803, "imgui_demo_apps.lua"], [19062, "imgui_demo_tools.lua"], [20388, "imgui_demo_widgets.lua"], [22312, "imgui_docking.lua"], [25440, "imgui_dragdrop.lua"], [25732, "imgui_draw.lua"], [30979, "imgui_h.lua"], [33153, "imgui_impl_roblox.lua"], [33977, "imgui_impl_roblox_fonts.lua"], [40380, "imgui_internal.lua"], [43552, "imgui_tables.lua"], [47899, "imgui_tabs.lua"], [49066, "imgui_widgets.lua"], [57600, "imstb_rectpack.lua"], [58046, "imstb_textedit.lua"], [59287, "imstb_truetype.lua"], [61755, "imstd_minstdio.lua"]] \ No newline at end of file diff --git a/build/ImGui.luau b/build/ImGui.luau index e3f31ce..eb92529 100644 --- a/build/ImGui.luau +++ b/build/ImGui.luau @@ -375,6 +375,7 @@ function ImHashStr(str, size, seed) return ImHashStrRaw(str, size, seed) end +@native function ImHashStrRaw(str, size, seed) if str == "" or size == 0 then @@ -1541,6 +1542,7 @@ local function GetWindowForTitleDisplay(window) end --- @param id ImGuiID +@native function ImGui.KeepAliveID(id) local g = GImGui @@ -1556,20 +1558,28 @@ 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 - - local rect_clipped = ImRect(r_min, r_max) + -- [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 - rect_clipped:ClipWith(g.CurrentWindow.ClipRect) + 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 - if not rect_clipped:ContainsWithPad(g.IO.MousePos, g.Style.TouchExtraPadding) then + 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 - if (not g.MouseViewport:GetMainRect():Overlaps(rect_clipped)) then + 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 @@ -1642,6 +1652,7 @@ end --- @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 @@ -1677,7 +1688,8 @@ function ImGui.ItemAdd(bb, id, nav_bb_arg, extra_flags) g.NextItemData.HasFlags = ImGuiNextItemDataFlags.None g.NextItemData.ItemFlagsSet = ImGuiItemFlags.None - local is_rect_visible = bb:Overlaps(window.ClipRect) + 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 @@ -1701,16 +1713,18 @@ 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 - if size_or_bb.Min then + local size_x, size_y + local bb_min = size_or_bb.Min + if bb_min then --- @cast size_or_bb ImRect - size = size_or_bb:GetSize() + 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 = size_or_bb + size_x = size_or_bb.x; size_y = size_or_bb.y end local g = GImGui @@ -1719,45 +1733,50 @@ function ImGui.ItemSize(size_or_bb, text_baseline_y) 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 = ImMax(0, window.DC.CurrLineTextBaseOffset - text_baseline_y) + 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 window.DC.IsSameLine then - line_y1 = window.DC.CursorPosPrevLine.y + if dc.IsSameLine then + line_y1 = prev_line.y else - line_y1 = window.DC.CursorPos.y + line_y1 = cursor.y end - local line_height = ImMax(window.DC.CurrLineSize.y, window.DC.CursorPos.y - line_y1 + size.y + offset_to_match_baseline_y) + 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 - window.DC.CursorPosPrevLine.x = window.DC.CursorPos.x + size.x - window.DC.CursorPosPrevLine.y = line_y1 - window.DC.CursorPos.x = IM_TRUNC(window.Pos.x + window.DC.Indent.x + window.DC.ColumnsOffset.x) - window.DC.CursorPos.y = IM_TRUNC(line_y1 + line_height + g.Style.ItemSpacing.y) - window.DC.CursorMaxPos.x = ImMax(window.DC.CursorMaxPos.x, window.DC.CursorPosPrevLine.x) - window.DC.CursorMaxPos.y = ImMax(window.DC.CursorMaxPos.y, window.DC.CursorPos.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) - window.DC.PrevLineSize.y = line_height - window.DC.CurrLineSize.y = 0 - window.DC.PrevLineTextBaseOffset = ImMax(window.DC.CurrLineTextBaseOffset, text_baseline_y) - window.DC.CurrLineTextBaseOffset = 0 - window.DC.IsSetPos = false - window.DC.IsSameLine = false + 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 (window.DC.LayoutType == ImGuiLayoutType.Horizontal) then + 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 @@ -1870,6 +1889,7 @@ end --- @return ImVec2 --- @nodiscard +@native function ImGui.GetContentRegionAvail() local g = GImGui local window = g.CurrentWindow @@ -2085,6 +2105,7 @@ function ImGui.EndGroup() end end +@native function ImGui.CalcItemWidth() local g = GImGui local window = g.CurrentWindow @@ -2575,6 +2596,7 @@ function ImGui.ClearActiveID() end --- @param str_id string|int +@native function ImGui.PushID(str_id) local g = GImGui local window = g.CurrentWindow @@ -2592,6 +2614,7 @@ function ImGui.PushOverrideID(id) 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!") @@ -2600,6 +2623,7 @@ end --- @param id string|int --- @return ImGuiID +@native function MT.ImGuiWindow:GetID(id) local seed = self.IDStack:back() @@ -2662,6 +2686,7 @@ function ImGui.GetIDWithSeed(a, b, c) end --- @param id ImGuiID +@native function ImGui.SetHoveredID(id) local g = GImGui g.HoveredId = id @@ -2763,6 +2788,7 @@ function ImGui.IsWindowContentHoverable(window, flags) end --- @param flags? ImGuiHoveredFlags +@native function ImGui.IsItemHovered(flags) if flags == nil then flags = 0 end @@ -2862,6 +2888,7 @@ 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 @@ -4785,6 +4812,7 @@ 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 @@ -4803,6 +4831,7 @@ end --- @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 @@ -4855,6 +4884,7 @@ end --- @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 @@ -4910,6 +4940,7 @@ end --- @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 @@ -5075,6 +5106,7 @@ end --- @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 @@ -7959,6 +7991,7 @@ 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) @@ -8519,6 +8552,7 @@ end --- @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 @@ -8529,6 +8563,7 @@ end --- @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 @@ -8657,13 +8692,11 @@ 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) - local out_col = 0 - out_col = bit32.bor(out_col, bit32.lshift(IM_F32_TO_INT8_SAT(in_col.x), IM_COL32_R_SHIFT)) - out_col = bit32.bor(out_col, bit32.lshift(IM_F32_TO_INT8_SAT(in_col.y), IM_COL32_G_SHIFT)) - out_col = bit32.bor(out_col, bit32.lshift(IM_F32_TO_INT8_SAT(in_col.z), IM_COL32_B_SHIFT)) - out_col = bit32.bor(out_col, bit32.lshift(IM_F32_TO_INT8_SAT(in_col.w), IM_COL32_A_SHIFT)) - return out_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 @@ -8746,6 +8779,7 @@ local c = ImVec4() --- @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 @@ -8753,14 +8787,14 @@ function ImGui.GetColorU32(col, alpha_mul, col_is_u32) if not col_is_u32 then --- @cast col ImVec4|ImGuiCol + local w if type(col) == "number" then - ImVec4_Copy(c, style.Colors[col]) - c.w = c.w * style.Alpha * alpha_mul + col = style.Colors[col] + w = col.w * style.Alpha * alpha_mul else --- @cast col ImVec4 - ImVec4_Copy(c, col) - c.w = c.w * style.Alpha + w = col.w * style.Alpha end - return ImGui.ColorConvertFloat4ToU32(c) + 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 @@ -28311,6 +28345,7 @@ end --- @return int remaining local TEXT_SIZE_CACHE_MAX = 4096 +@native function ImFontCalcTextSizeEx(font, size, max_width, wrap_width, text, text_begin, text_end_display, text_end, out_offset, flags) -- [Luau] cache for the common case (whole string, single pass, no wrap/limit): per baked font, text -> unscaled width, line count. -- Advances of a baked font never change once loaded, so entries stay valid. ponytail: whole-cache reset at TEXT_SIZE_CACHE_MAX entries, LRU if dynamic text churns it @@ -29243,6 +29278,7 @@ function MT.ImDrawList:ChannelsSetCurrent(n) self._Splitter:SetCurrentChannel(se --- @param points ImVec2[] --- @param points_count int --- @param col ImU32 +@native function MT.ImDrawList:AddConvexPolyFilled(points, points_count, col) if points_count < 3 or bit32.band(col, IM_COL32_A_MASK) == 0 then return @@ -29351,6 +29387,7 @@ function MT.ImDrawList:_SetTexture(tex_ref) self:_OnChangedTexture() end +@native function MT.ImDrawList:PrimReserve(idx_count, vtx_count) IM_ASSERT_PARANOID(idx_count >= 0 and vtx_count >= 0) @@ -29387,6 +29424,7 @@ end --- @param a ImVec2 --- @param c ImVec2 --- @param col ImU32 +@native function MT.ImDrawList:PrimRect(a, c, col) local uv = self._Data.TexUvWhitePixel @@ -29469,6 +29507,7 @@ end --- @param col ImU32 --- @param thickness float --- @param flags? ImDrawFlags +@native function MT.ImDrawList:AddPolyline(points, points_count, col, thickness, flags) if flags == nil then flags = 0 end @@ -29729,6 +29768,7 @@ function MT.ImDrawList:PathRect(a, b, rounding, flags) end end +@native function MT.ImDrawList:AddRectFilled(p_min, p_max, col, rounding, flags) if not rounding then rounding = 0.0 end if not flags then flags = 0 end @@ -29771,6 +29811,7 @@ end --- @param rounding float --- @param thickness? float --- @param flags? ImDrawFlags +@native function MT.ImDrawList:AddRect(p_min, p_max, col, rounding, thickness, flags) if thickness == nil then thickness = 1.0 end if flags == nil then flags = 0 end @@ -29946,6 +29987,7 @@ end --- @param text_end int --- @param wrap_width float --- @param cpu_fine_clip_rect? ImVec4 +@native function MT.ImDrawList:AddText(font, font_size, pos, col, text, text_begin, text_end, wrap_width, cpu_fine_clip_rect) -- Overload: AddText(pos, col, text, text_end?) if type(font) == "table" and type(font[1]) == "number" then @@ -30092,6 +30134,7 @@ end --- @param a_min_sample int --- @param a_max_sample int --- @param a_step int +@native function MT.ImDrawList:_PathArcToFastEx(center, radius, a_min_sample, a_max_sample, a_step) if radius < 0.5 then self._Path:push_back(center) @@ -30180,6 +30223,7 @@ function MT.ImDrawList:_PathArcToFastEx(center, radius, a_min_sample, a_max_samp IM_ASSERT_PARANOID(self._Path.Size == out_ptr - 1) end +@native function MT.ImDrawList:PathArcToFast(center, radius, a_min_of_12, a_max_of_12) if radius < 0.5 then self._Path:push_back(center) @@ -30267,6 +30311,7 @@ end --- @param text_end int --- @param wrap_width? float --- @param flags? ImDrawTextFlags +@native function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_begin, text_end, wrap_width, flags) if wrap_width == nil then wrap_width = .0 end if flags == nil then flags = 0 end @@ -30477,31 +30522,35 @@ end --- @param color ImU32 --- @param dir ImGuiDir --- @param scale? float +local arrow_a, arrow_b, arrow_c = ImVec2(), ImVec2(), ImVec2() -- scratch: the path is consumed by AddTriangleFilled before returning + function ImGui.RenderArrow(draw_list, pos, color, dir, scale) if scale == nil then scale = 1.0 end local h = draw_list._Data.FontSize * 1.00 local r = h * 0.40 * scale - local center = pos + ImVec2(h * 0.50, h * 0.50 * scale) - - local a, b, c + local cx, cy = pos.x + h * 0.50, pos.y + h * 0.50 * scale + local ax, ay, bx, by, qx, qy if dir == ImGuiDir.Up or dir == ImGuiDir.Down then if dir == ImGuiDir.Up then r = -r end - a = ImVec2( 0.000, 0.750) * r - b = ImVec2(-0.866, -0.750) * r - c = ImVec2( 0.866, -0.750) * r + ax, ay = 0.000 * r, 0.750 * r + bx, by = -0.866 * r, -0.750 * r + qx, qy = 0.866 * r, -0.750 * r elseif dir == ImGuiDir.Left or dir == ImGuiDir.Right then if dir == ImGuiDir.Left then r = -r end - a = ImVec2( 0.750, 0.000) * r - b = ImVec2(-0.750, 0.866) * r - c = ImVec2(-0.750, -0.866) * r - elseif dir == ImGuiDir.None or dir == ImGuiDir.COUNT then + ax, ay = 0.750 * r, 0.000 * r + bx, by = -0.750 * r, 0.866 * r + qx, qy = -0.750 * r, -0.866 * r + else IM_ASSERT(false) end - draw_list:AddTriangleFilled(center + a, center + b, center + c, color) + arrow_a.x, arrow_a.y = cx + ax, cy + ay + arrow_b.x, arrow_b.y = cx + bx, cy + by + arrow_c.x, arrow_c.y = cx + qx, cy + qy + draw_list:AddTriangleFilled(arrow_a, arrow_b, arrow_c, color) end --- @param draw_list ImDrawList @@ -31151,16 +31200,21 @@ end --- @param y? number --- @return ImVec2 --- @nodiscard +@native function ImVec2(x, y) return setmetatable({ x = x or 0, y = y or 0 }, IM_VEC2) end +@native function IM_VEC2.__add(lhs, rhs) return setmetatable({ x = lhs.x + rhs.x, y = lhs.y + rhs.y }, IM_VEC2) end +@native function IM_VEC2.__sub(lhs, rhs) return setmetatable({ x = lhs.x - rhs.x, y = lhs.y - rhs.y }, IM_VEC2) end +@native function IM_VEC2.__mul(lhs, rhs) if type(rhs) == "number" then return setmetatable({ x = lhs.x * rhs, y = lhs.y * rhs }, IM_VEC2) end return setmetatable({ x = lhs.x * rhs.x, y = lhs.y * rhs.y }, IM_VEC2) end +@native function IM_VEC2.__div(lhs, rhs) if type(rhs) == "number" then return setmetatable({ x = lhs.x / rhs, y = lhs.y / rhs }, IM_VEC2) end return setmetatable({ x = lhs.x / rhs.x, y = lhs.y / rhs.y }, IM_VEC2) @@ -31172,6 +31226,7 @@ function IM_VEC2:__tostring() return string.format("ImVec2(%g, %g)", self.x, sel --- @param dest ImVec2 --- @param src ImVec2 +@native function ImVec2_Copy(dest, src) dest.x = src.x; dest.y = src.y end function ImVec2_CopyV(dest, src_x, src_y) dest.x = src_x; dest.y = src_y end @@ -31210,6 +31265,7 @@ end --- @return ImVec4 --- @nodiscard +@native function ImVec4(x, y, z, w) return setmetatable({ x = x or 0, y = y or 0, z = z or 0, w = w or 0 }, IM_VEC4) end function IM_VEC4.__add(lhs, rhs) return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w) end @@ -31221,6 +31277,7 @@ function IM_VEC4:__tostring() return string.format("ImVec4(%g, %g, %g, %g)", sel --- @param dest ImVec4 --- @param src ImVec4 +@native function ImVec4_Copy(dest, src) dest.x = src.x; dest.y = src.y; dest.z = src.z; dest.w = src.w end --- A compact ImVector clone @@ -40462,6 +40519,7 @@ end --- @overload fun(lhs: number, rhs: number): number --- @overload fun(lhs: ImVec2, rhs: ImVec2): ImVec2 +@native function ImMin(lhs, rhs) if type(lhs) == "number" then return mathMin(lhs, rhs) end return ImVec2(mathMin(lhs.x, rhs.x), mathMin(lhs.y, rhs.y)) @@ -40469,6 +40527,7 @@ end --- @overload fun(lhs: number, rhs: number): number --- @overload fun(lhs: ImVec2, rhs: ImVec2): ImVec2 +@native function ImMax(lhs, rhs) if type(lhs) == "number" then return mathMax(lhs, rhs) end return ImVec2(mathMax(lhs.x, rhs.x), mathMax(lhs.y, rhs.y)) @@ -40481,6 +40540,7 @@ ImStd.ImQsort = function(base, count, cmp_func) if count > 0 then t_sort(base, c --- @overload fun(v: number, mn: number, mx: number): number --- @overload fun(v: ImVec2, mn: ImVec2, mx: ImVec2): ImVec2 +@native function ImClamp(v, mn, mx) if type(v) == "number" and type(mn) == "number" and type(mx) == "number" then return mathMin(mathMax(v, mn), mx) elseif type(v) == "table" and type(mn) == "table" and type(mx) == "table" then return ImVec2(mathMax(mn.x, mathMin(v.x, mx.x)), mathMax(mn.y, mathMin(v.y, mx.y))) @@ -40490,6 +40550,7 @@ end --- @overload fun(a: number, b: number, t: number): number --- @overload fun(a: ImVec2, b: ImVec2, t: number): ImVec2 --- @overload fun(a: ImVec2, b: ImVec2, t: ImVec2): ImVec2 +@native function ImLerp(a, b, t) if type(a) == "number" and type(b) == "number" and type(t) == "number" then return ((a) + ((b) - (a)) * (t)) elseif type(a) == "table" and type(b) == "table" and type(t) == "number" then return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t) @@ -41004,12 +41065,14 @@ IM_RECT.__newindex = function(t, k, v) IM_ASSERT(false, "ImRect: unknown field " --- @return ImRect --- @nodiscard +@native function ImRect(a, b, c, d) if c and d then return setmetatable({ Min = ImVec2(a, b), Max = ImVec2(c, d) }, IM_RECT) end return setmetatable({ Min = ImVec2(a and a.x or 0, a and a.y or 0), Max = ImVec2(b and b.x or 0, b and b.y or 0) }, IM_RECT) end function IM_RECT:__eq(other) return self.Min == other.Min and self.Max == other.Max end function IM_RECT:__tostring() return string.format("ImRect(Min: %g,%g, Max: %g,%g)", self.Min.x, self.Min.y, self.Max.x, self.Max.y) end --- @param p ImRect|ImVec2 +@native function IM_RECT:Contains(p) if p.Min then --- @cast p ImRect @@ -41027,6 +41090,7 @@ function IM_RECT:ContainsWithPad(p, pad) end --- @param r ImRect +@native function IM_RECT:Overlaps(r) return self.Min.x <= r.Max.x and self.Max.x >= r.Min.x and self.Min.y <= r.Max.y and self.Max.y >= r.Min.y end @@ -41036,6 +41100,7 @@ function IM_RECT:GetCenter() return ImVec2((self.Min.x + self.Max.x) * 0.5, (sel function IM_RECT:GetWidth() return self.Max.x - self.Min.x end function IM_RECT:GetHeight() return self.Max.y - self.Min.y end --- @nodiscard +@native function IM_RECT:GetSize() return ImVec2(self.Max.x - self.Min.x, self.Max.y - self.Min.y) end --- @nodiscard @@ -41048,16 +41113,19 @@ function IM_RECT:GetBL() return ImVec2(self.Min.x, self.Max.y) end function IM_RECT:GetBR() return ImVec2(self.Max.x, self.Max.y) end --- @param r ImRect +@native function IM_RECT:ClipWith(r) ImVec2_Copy(self.Min, ImMax(self.Min, r.Min)); ImVec2_Copy(self.Max, ImMin(self.Max, r.Max)) end --- @param r ImRect +@native function IM_RECT:ClipWithFull(r) ImVec2_Copy(self.Min, ImClamp(self.Min, r.Min, r.Max)); ImVec2_Copy(self.Max, ImClamp(self.Max, r.Min, r.Max)) end --- @param p ImRect|ImVec2 +@native function IM_RECT:Add(p) if p.Min then --- @cast p ImRect @@ -41075,6 +41143,7 @@ function IM_RECT:Add(p) end --- @param amount float|ImVec2 +@native function IM_RECT:Expand(amount) if type(amount) == "number" then self.Min.x = self.Min.x - amount; self.Min.y = self.Min.y - amount @@ -41091,6 +41160,7 @@ function IM_RECT:ToVec4() end --- @param d ImVec2 +@native function IM_RECT:Translate(d) self.Min.x = self.Min.x + d.x; self.Min.y = self.Min.y + d.y self.Max.x = self.Max.x + d.x; self.Max.y = self.Max.y + d.y @@ -41115,6 +41185,7 @@ function IM_RECT:AsVec4() return ImVec4(self.Min.x, self.Min.y, self.Max.x, self --- @param dest ImRect --- @param src ImRect +@native function ImRect_Copy(dest, src) dest.Min.x = src.Min.x; dest.Min.y = src.Min.y dest.Max.x = src.Max.x; dest.Max.y = src.Max.y @@ -41179,6 +41250,7 @@ end function MT.ImDrawList:PathClear() self._Path:clear() end --- @param pos ImVec2 +@native function MT.ImDrawList:PathLineTo(pos) self._Path:push_back(pos) end function MT.ImDrawList:PathLineToMergeDuplicate(pos) @@ -41188,6 +41260,7 @@ function MT.ImDrawList:PathLineToMergeDuplicate(pos) end end +@native function MT.ImDrawList:PathFillConvex(col) self:AddConvexPolyFilled(self._Path.Data, self._Path.Size, col) self._Path.Size = 0 @@ -49026,6 +49099,7 @@ local IM_U32_MAX = UINT_MAX --- @param text string --- @param text_end? int --- @param flags? ImGuiTextFlags +@native function ImGui.TextEx(text, text_end, flags) if not flags then flags = 0 end @@ -49136,6 +49210,7 @@ end --- @param fmt string --- @param ... any +@native function ImGui.TextV(fmt, ...) local window = ImGui.GetCurrentWindow() if window.SkipItems then @@ -49148,6 +49223,7 @@ end --- @param fmt string --- @param ... any +@native function ImGui.Text(fmt, ...) if select('#', ...) > 0 then ImGui.TextV(fmt, ...) @@ -49322,6 +49398,7 @@ end --- @param bb ImRect --- @param id ImGuiID --- @param flags? ImGuiButtonFlags +@native function ImGui.ButtonBehavior(bb, id, flags) if flags == nil then flags = 0 end @@ -49564,6 +49641,7 @@ end --- @param size_arg? ImVec2 --- @param flags? ImGuiButtonFlags --- @return bool +@native function ImGui.ButtonEx(label, size_arg, flags) if size_arg == nil then size_arg = ImVec2(0, 0) end if flags == nil then flags = 0 end @@ -50100,6 +50178,7 @@ end --- @param v bool --- @return bool is_pressed --- @return bool is_checked # The updated `v` passed in +@native function ImGui.Checkbox(label, v) local window = ImGui.GetCurrentWindow() if window.SkipItems then @@ -55838,6 +55917,7 @@ end -- Overloads: TreeNode(label) / TreeNode(str_id, fmt, ...) --- @param label string +@native function ImGui.TreeNode(label, fmt, ...) if fmt ~= nil then return ImGui.TreeNodeEx(label, 0, fmt, ...) @@ -55959,6 +56039,7 @@ end --- @param flags ImGuiTreeNodeFlags --- @param label string --- @param label_end? int +@native function ImGui.TreeNodeBehavior(id, flags, label, label_end) local window = ImGui.GetCurrentWindow() if window.SkipItems then @@ -56372,6 +56453,7 @@ end -- Overloads: CollapsingHeader(label, flags?) -> is_open -- CollapsingHeader(label, p_visible: bool, flags?) -> is_open, p_visible --- @param label string +@native function ImGui.CollapsingHeader(label, a, b) local p_visible, flags if type(a) == "boolean" then p_visible, flags = a, b else flags = a end @@ -56426,6 +56508,7 @@ end --- @param size_arg? any --- @return bool is_pressed --- @return bool is_selected # Updated `selected` +@native function ImGui.Selectable(label, selected, flags, size_arg) if selected == nil then selected = false end if flags == nil then flags = 0 end diff --git a/imgui/imgui.lua b/imgui/imgui.lua index ef30718..128d727 100644 --- a/imgui/imgui.lua +++ b/imgui/imgui.lua @@ -364,6 +364,7 @@ function ImHashStr(str, size, seed) return ImHashStrRaw(str, size, seed) end +@native function ImHashStrRaw(str, size, seed) if str == "" or size == 0 then @@ -1530,6 +1531,7 @@ local function GetWindowForTitleDisplay(window) end --- @param id ImGuiID +@native function ImGui.KeepAliveID(id) local g = GImGui @@ -1545,20 +1547,28 @@ 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 - - local rect_clipped = ImRect(r_min, r_max) + -- [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 - rect_clipped:ClipWith(g.CurrentWindow.ClipRect) + 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 - if not rect_clipped:ContainsWithPad(g.IO.MousePos, g.Style.TouchExtraPadding) then + 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 - if (not g.MouseViewport:GetMainRect():Overlaps(rect_clipped)) then + 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 @@ -1631,6 +1641,7 @@ end --- @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 @@ -1666,7 +1677,8 @@ function ImGui.ItemAdd(bb, id, nav_bb_arg, extra_flags) g.NextItemData.HasFlags = ImGuiNextItemDataFlags.None g.NextItemData.ItemFlagsSet = ImGuiItemFlags.None - local is_rect_visible = bb:Overlaps(window.ClipRect) + 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 @@ -1690,16 +1702,18 @@ 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 - if size_or_bb.Min then + local size_x, size_y + local bb_min = size_or_bb.Min + if bb_min then --- @cast size_or_bb ImRect - size = size_or_bb:GetSize() + 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 = size_or_bb + size_x = size_or_bb.x; size_y = size_or_bb.y end local g = GImGui @@ -1708,45 +1722,50 @@ function ImGui.ItemSize(size_or_bb, text_baseline_y) 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 = ImMax(0, window.DC.CurrLineTextBaseOffset - text_baseline_y) + 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 window.DC.IsSameLine then - line_y1 = window.DC.CursorPosPrevLine.y + if dc.IsSameLine then + line_y1 = prev_line.y else - line_y1 = window.DC.CursorPos.y + line_y1 = cursor.y end - local line_height = ImMax(window.DC.CurrLineSize.y, window.DC.CursorPos.y - line_y1 + size.y + offset_to_match_baseline_y) + 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 - window.DC.CursorPosPrevLine.x = window.DC.CursorPos.x + size.x - window.DC.CursorPosPrevLine.y = line_y1 - window.DC.CursorPos.x = IM_TRUNC(window.Pos.x + window.DC.Indent.x + window.DC.ColumnsOffset.x) - window.DC.CursorPos.y = IM_TRUNC(line_y1 + line_height + g.Style.ItemSpacing.y) - window.DC.CursorMaxPos.x = ImMax(window.DC.CursorMaxPos.x, window.DC.CursorPosPrevLine.x) - window.DC.CursorMaxPos.y = ImMax(window.DC.CursorMaxPos.y, window.DC.CursorPos.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) - window.DC.PrevLineSize.y = line_height - window.DC.CurrLineSize.y = 0 - window.DC.PrevLineTextBaseOffset = ImMax(window.DC.CurrLineTextBaseOffset, text_baseline_y) - window.DC.CurrLineTextBaseOffset = 0 - window.DC.IsSetPos = false - window.DC.IsSameLine = false + 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 (window.DC.LayoutType == ImGuiLayoutType.Horizontal) then + 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 @@ -1859,6 +1878,7 @@ end --- @return ImVec2 --- @nodiscard +@native function ImGui.GetContentRegionAvail() local g = GImGui local window = g.CurrentWindow @@ -2074,6 +2094,7 @@ function ImGui.EndGroup() end end +@native function ImGui.CalcItemWidth() local g = GImGui local window = g.CurrentWindow @@ -2564,6 +2585,7 @@ function ImGui.ClearActiveID() end --- @param str_id string|int +@native function ImGui.PushID(str_id) local g = GImGui local window = g.CurrentWindow @@ -2581,6 +2603,7 @@ function ImGui.PushOverrideID(id) 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!") @@ -2589,6 +2612,7 @@ end --- @param id string|int --- @return ImGuiID +@native function MT.ImGuiWindow:GetID(id) local seed = self.IDStack:back() @@ -2651,6 +2675,7 @@ function ImGui.GetIDWithSeed(a, b, c) end --- @param id ImGuiID +@native function ImGui.SetHoveredID(id) local g = GImGui g.HoveredId = id @@ -2752,6 +2777,7 @@ function ImGui.IsWindowContentHoverable(window, flags) end --- @param flags? ImGuiHoveredFlags +@native function ImGui.IsItemHovered(flags) if flags == nil then flags = 0 end @@ -2851,6 +2877,7 @@ 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 @@ -4774,6 +4801,7 @@ 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 @@ -4792,6 +4820,7 @@ end --- @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 @@ -4844,6 +4873,7 @@ end --- @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 @@ -4899,6 +4929,7 @@ end --- @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 @@ -5064,6 +5095,7 @@ end --- @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 @@ -7948,6 +7980,7 @@ 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) @@ -8508,6 +8541,7 @@ end --- @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 @@ -8518,6 +8552,7 @@ end --- @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 @@ -8646,13 +8681,11 @@ 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) - local out_col = 0 - out_col = bit32.bor(out_col, bit32.lshift(IM_F32_TO_INT8_SAT(in_col.x), IM_COL32_R_SHIFT)) - out_col = bit32.bor(out_col, bit32.lshift(IM_F32_TO_INT8_SAT(in_col.y), IM_COL32_G_SHIFT)) - out_col = bit32.bor(out_col, bit32.lshift(IM_F32_TO_INT8_SAT(in_col.z), IM_COL32_B_SHIFT)) - out_col = bit32.bor(out_col, bit32.lshift(IM_F32_TO_INT8_SAT(in_col.w), IM_COL32_A_SHIFT)) - return out_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 @@ -8735,6 +8768,7 @@ local c = ImVec4() --- @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 @@ -8742,14 +8776,14 @@ function ImGui.GetColorU32(col, alpha_mul, col_is_u32) if not col_is_u32 then --- @cast col ImVec4|ImGuiCol + local w if type(col) == "number" then - ImVec4_Copy(c, style.Colors[col]) - c.w = c.w * style.Alpha * alpha_mul + col = style.Colors[col] + w = col.w * style.Alpha * alpha_mul else --- @cast col ImVec4 - ImVec4_Copy(c, col) - c.w = c.w * style.Alpha + w = col.w * style.Alpha end - return ImGui.ColorConvertFloat4ToU32(c) + 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 diff --git a/imgui/imgui_draw.lua b/imgui/imgui_draw.lua index 034beb1..905f736 100644 --- a/imgui/imgui_draw.lua +++ b/imgui/imgui_draw.lua @@ -2614,6 +2614,7 @@ end --- @return int remaining local TEXT_SIZE_CACHE_MAX = 4096 +@native function ImFontCalcTextSizeEx(font, size, max_width, wrap_width, text, text_begin, text_end_display, text_end, out_offset, flags) -- [Luau] cache for the common case (whole string, single pass, no wrap/limit): per baked font, text -> unscaled width, line count. -- Advances of a baked font never change once loaded, so entries stay valid. ponytail: whole-cache reset at TEXT_SIZE_CACHE_MAX entries, LRU if dynamic text churns it @@ -3546,6 +3547,7 @@ function MT.ImDrawList:ChannelsSetCurrent(n) self._Splitter:SetCurrentChannel(se --- @param points ImVec2[] --- @param points_count int --- @param col ImU32 +@native function MT.ImDrawList:AddConvexPolyFilled(points, points_count, col) if points_count < 3 or bit32.band(col, IM_COL32_A_MASK) == 0 then return @@ -3654,6 +3656,7 @@ function MT.ImDrawList:_SetTexture(tex_ref) self:_OnChangedTexture() end +@native function MT.ImDrawList:PrimReserve(idx_count, vtx_count) IM_ASSERT_PARANOID(idx_count >= 0 and vtx_count >= 0) @@ -3690,6 +3693,7 @@ end --- @param a ImVec2 --- @param c ImVec2 --- @param col ImU32 +@native function MT.ImDrawList:PrimRect(a, c, col) local uv = self._Data.TexUvWhitePixel @@ -3772,6 +3776,7 @@ end --- @param col ImU32 --- @param thickness float --- @param flags? ImDrawFlags +@native function MT.ImDrawList:AddPolyline(points, points_count, col, thickness, flags) if flags == nil then flags = 0 end @@ -4032,6 +4037,7 @@ function MT.ImDrawList:PathRect(a, b, rounding, flags) end end +@native function MT.ImDrawList:AddRectFilled(p_min, p_max, col, rounding, flags) if not rounding then rounding = 0.0 end if not flags then flags = 0 end @@ -4074,6 +4080,7 @@ end --- @param rounding float --- @param thickness? float --- @param flags? ImDrawFlags +@native function MT.ImDrawList:AddRect(p_min, p_max, col, rounding, thickness, flags) if thickness == nil then thickness = 1.0 end if flags == nil then flags = 0 end @@ -4249,6 +4256,7 @@ end --- @param text_end int --- @param wrap_width float --- @param cpu_fine_clip_rect? ImVec4 +@native function MT.ImDrawList:AddText(font, font_size, pos, col, text, text_begin, text_end, wrap_width, cpu_fine_clip_rect) -- Overload: AddText(pos, col, text, text_end?) if type(font) == "table" and type(font[1]) == "number" then @@ -4395,6 +4403,7 @@ end --- @param a_min_sample int --- @param a_max_sample int --- @param a_step int +@native function MT.ImDrawList:_PathArcToFastEx(center, radius, a_min_sample, a_max_sample, a_step) if radius < 0.5 then self._Path:push_back(center) @@ -4483,6 +4492,7 @@ function MT.ImDrawList:_PathArcToFastEx(center, radius, a_min_sample, a_max_samp IM_ASSERT_PARANOID(self._Path.Size == out_ptr - 1) end +@native function MT.ImDrawList:PathArcToFast(center, radius, a_min_of_12, a_max_of_12) if radius < 0.5 then self._Path:push_back(center) @@ -4570,6 +4580,7 @@ end --- @param text_end int --- @param wrap_width? float --- @param flags? ImDrawTextFlags +@native function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_begin, text_end, wrap_width, flags) if wrap_width == nil then wrap_width = .0 end if flags == nil then flags = 0 end @@ -4780,31 +4791,35 @@ end --- @param color ImU32 --- @param dir ImGuiDir --- @param scale? float +local arrow_a, arrow_b, arrow_c = ImVec2(), ImVec2(), ImVec2() -- scratch: the path is consumed by AddTriangleFilled before returning + function ImGui.RenderArrow(draw_list, pos, color, dir, scale) if scale == nil then scale = 1.0 end local h = draw_list._Data.FontSize * 1.00 local r = h * 0.40 * scale - local center = pos + ImVec2(h * 0.50, h * 0.50 * scale) - - local a, b, c + local cx, cy = pos.x + h * 0.50, pos.y + h * 0.50 * scale + local ax, ay, bx, by, qx, qy if dir == ImGuiDir.Up or dir == ImGuiDir.Down then if dir == ImGuiDir.Up then r = -r end - a = ImVec2( 0.000, 0.750) * r - b = ImVec2(-0.866, -0.750) * r - c = ImVec2( 0.866, -0.750) * r + ax, ay = 0.000 * r, 0.750 * r + bx, by = -0.866 * r, -0.750 * r + qx, qy = 0.866 * r, -0.750 * r elseif dir == ImGuiDir.Left or dir == ImGuiDir.Right then if dir == ImGuiDir.Left then r = -r end - a = ImVec2( 0.750, 0.000) * r - b = ImVec2(-0.750, 0.866) * r - c = ImVec2(-0.750, -0.866) * r - elseif dir == ImGuiDir.None or dir == ImGuiDir.COUNT then + ax, ay = 0.750 * r, 0.000 * r + bx, by = -0.750 * r, 0.866 * r + qx, qy = -0.750 * r, -0.866 * r + else IM_ASSERT(false) end - draw_list:AddTriangleFilled(center + a, center + b, center + c, color) + arrow_a.x, arrow_a.y = cx + ax, cy + ay + arrow_b.x, arrow_b.y = cx + bx, cy + by + arrow_c.x, arrow_c.y = cx + qx, cy + qy + draw_list:AddTriangleFilled(arrow_a, arrow_b, arrow_c, color) end --- @param draw_list ImDrawList diff --git a/imgui/imgui_h.lua b/imgui/imgui_h.lua index a3ba940..6bda70d 100644 --- a/imgui/imgui_h.lua +++ b/imgui/imgui_h.lua @@ -222,16 +222,21 @@ end --- @param y? number --- @return ImVec2 --- @nodiscard +@native function ImVec2(x, y) return setmetatable({ x = x or 0, y = y or 0 }, IM_VEC2) end +@native function IM_VEC2.__add(lhs, rhs) return setmetatable({ x = lhs.x + rhs.x, y = lhs.y + rhs.y }, IM_VEC2) end +@native function IM_VEC2.__sub(lhs, rhs) return setmetatable({ x = lhs.x - rhs.x, y = lhs.y - rhs.y }, IM_VEC2) end +@native function IM_VEC2.__mul(lhs, rhs) if type(rhs) == "number" then return setmetatable({ x = lhs.x * rhs, y = lhs.y * rhs }, IM_VEC2) end return setmetatable({ x = lhs.x * rhs.x, y = lhs.y * rhs.y }, IM_VEC2) end +@native function IM_VEC2.__div(lhs, rhs) if type(rhs) == "number" then return setmetatable({ x = lhs.x / rhs, y = lhs.y / rhs }, IM_VEC2) end return setmetatable({ x = lhs.x / rhs.x, y = lhs.y / rhs.y }, IM_VEC2) @@ -243,6 +248,7 @@ function IM_VEC2:__tostring() return string.format("ImVec2(%g, %g)", self.x, sel --- @param dest ImVec2 --- @param src ImVec2 +@native function ImVec2_Copy(dest, src) dest.x = src.x; dest.y = src.y end function ImVec2_CopyV(dest, src_x, src_y) dest.x = src_x; dest.y = src_y end @@ -281,6 +287,7 @@ end --- @return ImVec4 --- @nodiscard +@native function ImVec4(x, y, z, w) return setmetatable({ x = x or 0, y = y or 0, z = z or 0, w = w or 0 }, IM_VEC4) end function IM_VEC4.__add(lhs, rhs) return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w) end @@ -292,6 +299,7 @@ function IM_VEC4:__tostring() return string.format("ImVec4(%g, %g, %g, %g)", sel --- @param dest ImVec4 --- @param src ImVec4 +@native function ImVec4_Copy(dest, src) dest.x = src.x; dest.y = src.y; dest.z = src.z; dest.w = src.w end --- A compact ImVector clone diff --git a/imgui/imgui_internal.lua b/imgui/imgui_internal.lua index 58e0698..d3ac145 100644 --- a/imgui/imgui_internal.lua +++ b/imgui/imgui_internal.lua @@ -140,6 +140,7 @@ end --- @overload fun(lhs: number, rhs: number): number --- @overload fun(lhs: ImVec2, rhs: ImVec2): ImVec2 +@native function ImMin(lhs, rhs) if type(lhs) == "number" then return mathMin(lhs, rhs) end return ImVec2(mathMin(lhs.x, rhs.x), mathMin(lhs.y, rhs.y)) @@ -147,6 +148,7 @@ end --- @overload fun(lhs: number, rhs: number): number --- @overload fun(lhs: ImVec2, rhs: ImVec2): ImVec2 +@native function ImMax(lhs, rhs) if type(lhs) == "number" then return mathMax(lhs, rhs) end return ImVec2(mathMax(lhs.x, rhs.x), mathMax(lhs.y, rhs.y)) @@ -159,6 +161,7 @@ ImStd.ImQsort = function(base, count, cmp_func) if count > 0 then t_sort(base, c --- @overload fun(v: number, mn: number, mx: number): number --- @overload fun(v: ImVec2, mn: ImVec2, mx: ImVec2): ImVec2 +@native function ImClamp(v, mn, mx) if type(v) == "number" and type(mn) == "number" and type(mx) == "number" then return mathMin(mathMax(v, mn), mx) elseif type(v) == "table" and type(mn) == "table" and type(mx) == "table" then return ImVec2(mathMax(mn.x, mathMin(v.x, mx.x)), mathMax(mn.y, mathMin(v.y, mx.y))) @@ -168,6 +171,7 @@ end --- @overload fun(a: number, b: number, t: number): number --- @overload fun(a: ImVec2, b: ImVec2, t: number): ImVec2 --- @overload fun(a: ImVec2, b: ImVec2, t: ImVec2): ImVec2 +@native function ImLerp(a, b, t) if type(a) == "number" and type(b) == "number" and type(t) == "number" then return ((a) + ((b) - (a)) * (t)) elseif type(a) == "table" and type(b) == "table" and type(t) == "number" then return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t) @@ -682,12 +686,14 @@ IM_RECT.__newindex = function(t, k, v) IM_ASSERT(false, "ImRect: unknown field " --- @return ImRect --- @nodiscard +@native function ImRect(a, b, c, d) if c and d then return setmetatable({ Min = ImVec2(a, b), Max = ImVec2(c, d) }, IM_RECT) end return setmetatable({ Min = ImVec2(a and a.x or 0, a and a.y or 0), Max = ImVec2(b and b.x or 0, b and b.y or 0) }, IM_RECT) end function IM_RECT:__eq(other) return self.Min == other.Min and self.Max == other.Max end function IM_RECT:__tostring() return string.format("ImRect(Min: %g,%g, Max: %g,%g)", self.Min.x, self.Min.y, self.Max.x, self.Max.y) end --- @param p ImRect|ImVec2 +@native function IM_RECT:Contains(p) if p.Min then --- @cast p ImRect @@ -705,6 +711,7 @@ function IM_RECT:ContainsWithPad(p, pad) end --- @param r ImRect +@native function IM_RECT:Overlaps(r) return self.Min.x <= r.Max.x and self.Max.x >= r.Min.x and self.Min.y <= r.Max.y and self.Max.y >= r.Min.y end @@ -714,6 +721,7 @@ function IM_RECT:GetCenter() return ImVec2((self.Min.x + self.Max.x) * 0.5, (sel function IM_RECT:GetWidth() return self.Max.x - self.Min.x end function IM_RECT:GetHeight() return self.Max.y - self.Min.y end --- @nodiscard +@native function IM_RECT:GetSize() return ImVec2(self.Max.x - self.Min.x, self.Max.y - self.Min.y) end --- @nodiscard @@ -726,16 +734,19 @@ function IM_RECT:GetBL() return ImVec2(self.Min.x, self.Max.y) end function IM_RECT:GetBR() return ImVec2(self.Max.x, self.Max.y) end --- @param r ImRect +@native function IM_RECT:ClipWith(r) ImVec2_Copy(self.Min, ImMax(self.Min, r.Min)); ImVec2_Copy(self.Max, ImMin(self.Max, r.Max)) end --- @param r ImRect +@native function IM_RECT:ClipWithFull(r) ImVec2_Copy(self.Min, ImClamp(self.Min, r.Min, r.Max)); ImVec2_Copy(self.Max, ImClamp(self.Max, r.Min, r.Max)) end --- @param p ImRect|ImVec2 +@native function IM_RECT:Add(p) if p.Min then --- @cast p ImRect @@ -753,6 +764,7 @@ function IM_RECT:Add(p) end --- @param amount float|ImVec2 +@native function IM_RECT:Expand(amount) if type(amount) == "number" then self.Min.x = self.Min.x - amount; self.Min.y = self.Min.y - amount @@ -769,6 +781,7 @@ function IM_RECT:ToVec4() end --- @param d ImVec2 +@native function IM_RECT:Translate(d) self.Min.x = self.Min.x + d.x; self.Min.y = self.Min.y + d.y self.Max.x = self.Max.x + d.x; self.Max.y = self.Max.y + d.y @@ -793,6 +806,7 @@ function IM_RECT:AsVec4() return ImVec4(self.Min.x, self.Min.y, self.Max.x, self --- @param dest ImRect --- @param src ImRect +@native function ImRect_Copy(dest, src) dest.Min.x = src.Min.x; dest.Min.y = src.Min.y dest.Max.x = src.Max.x; dest.Max.y = src.Max.y @@ -857,6 +871,7 @@ end function MT.ImDrawList:PathClear() self._Path:clear() end --- @param pos ImVec2 +@native function MT.ImDrawList:PathLineTo(pos) self._Path:push_back(pos) end function MT.ImDrawList:PathLineToMergeDuplicate(pos) @@ -866,6 +881,7 @@ function MT.ImDrawList:PathLineToMergeDuplicate(pos) end end +@native function MT.ImDrawList:PathFillConvex(col) self:AddConvexPolyFilled(self._Path.Data, self._Path.Size, col) self._Path.Size = 0 diff --git a/imgui/imgui_widgets.lua b/imgui/imgui_widgets.lua index dae3c32..bc2a8f2 100644 --- a/imgui/imgui_widgets.lua +++ b/imgui/imgui_widgets.lua @@ -34,6 +34,7 @@ local IM_U32_MAX = UINT_MAX --- @param text string --- @param text_end? int --- @param flags? ImGuiTextFlags +@native function ImGui.TextEx(text, text_end, flags) if not flags then flags = 0 end @@ -144,6 +145,7 @@ end --- @param fmt string --- @param ... any +@native function ImGui.TextV(fmt, ...) local window = ImGui.GetCurrentWindow() if window.SkipItems then @@ -156,6 +158,7 @@ end --- @param fmt string --- @param ... any +@native function ImGui.Text(fmt, ...) if select('#', ...) > 0 then ImGui.TextV(fmt, ...) @@ -330,6 +333,7 @@ end --- @param bb ImRect --- @param id ImGuiID --- @param flags? ImGuiButtonFlags +@native function ImGui.ButtonBehavior(bb, id, flags) if flags == nil then flags = 0 end @@ -572,6 +576,7 @@ end --- @param size_arg? ImVec2 --- @param flags? ImGuiButtonFlags --- @return bool +@native function ImGui.ButtonEx(label, size_arg, flags) if size_arg == nil then size_arg = ImVec2(0, 0) end if flags == nil then flags = 0 end @@ -1108,6 +1113,7 @@ end --- @param v bool --- @return bool is_pressed --- @return bool is_checked # The updated `v` passed in +@native function ImGui.Checkbox(label, v) local window = ImGui.GetCurrentWindow() if window.SkipItems then @@ -6846,6 +6852,7 @@ end -- Overloads: TreeNode(label) / TreeNode(str_id, fmt, ...) --- @param label string +@native function ImGui.TreeNode(label, fmt, ...) if fmt ~= nil then return ImGui.TreeNodeEx(label, 0, fmt, ...) @@ -6967,6 +6974,7 @@ end --- @param flags ImGuiTreeNodeFlags --- @param label string --- @param label_end? int +@native function ImGui.TreeNodeBehavior(id, flags, label, label_end) local window = ImGui.GetCurrentWindow() if window.SkipItems then @@ -7380,6 +7388,7 @@ end -- Overloads: CollapsingHeader(label, flags?) -> is_open -- CollapsingHeader(label, p_visible: bool, flags?) -> is_open, p_visible --- @param label string +@native function ImGui.CollapsingHeader(label, a, b) local p_visible, flags if type(a) == "boolean" then p_visible, flags = a, b else flags = a end @@ -7434,6 +7443,7 @@ end --- @param size_arg? any --- @return bool is_pressed --- @return bool is_selected # Updated `selected` +@native function ImGui.Selectable(label, selected, flags, size_arg) if selected == nil then selected = false end if flags == nil then flags = 0 end diff --git a/tools/test/scripts/t_drawhash.luau b/tools/test/scripts/t_drawhash.luau new file mode 100644 index 0000000..c490d67 --- /dev/null +++ b/tools/test/scripts/t_drawhash.luau @@ -0,0 +1,29 @@ +-- Prints one checksum of all draw data (vertices, indices, clip rects) per run, with scripted mouse input. +-- Use to prove an internal rewrite renders identically: run before/after with the same --open-only and compare. +local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui")) +local ImGui = G.ImGui +ImGui.CreateContext() +G.ImGui_ImplRoblox.Init() +G.ImGui_ImplRoblox.SetRenderRate(0) +local io = ImGui.GetIO() +local frame, h = 0, 0 +local function mix(v) h = (h * 31 + math.floor(v * 64 + 0.5)) % 2147483647 end +game:GetService("RunService").Stepped:Connect(function() + frame += 1 + G.ImGui_ImplRoblox.NewFrame() + io.DeltaTime = 1 / 60 + io:AddMousePosEvent(100 + (frame * 37) % 500, 60 + (frame * 53) % 600) + if frame % 7 == 0 then io:AddMouseButtonEvent(0, frame % 14 == 0) end + if frame % 11 == 0 then io:AddMouseWheelEvent(0, -1) end + ImGui.NewFrame() + ImGui.ShowDemoWindow(true) + ImGui.Render() + for _, dl in ImGui.GetDrawData().CmdLists:iter() do + local vd, id = dl.VtxBuffer.Data, dl.IdxBuffer.Data + for i = 1, dl.VtxBuffer.Size do local v = vd[i]; mix(v[1].x); mix(v[1].y); mix(v[2].x * 4096); mix(v[2].y * 4096); mix(v[3] / 64) end + for i = 1, dl.IdxBuffer.Size do mix(id[i]) end + for _, c in dl.CmdBuffer:iter() do mix(c.ClipRect.x); mix(c.ClipRect.y); mix(c.ClipRect.z); mix(c.ClipRect.w); mix(c.ElemCount) end + end + G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData()) + if frame == ARG_FRAMES - 1 then print("DRAWHASH", h) end +end)