From af2026113a0af144301d850dc79245e9ca80d4c2 Mon Sep 17 00:00:00 2001 From: 4DBug <4DBug@github.com> Date: Thu, 15 Oct 2026 06:37:49 -0500 Subject: [PATCH] Draw list 1.93 strokes: _AddPolyline ported (texture AA lines, joins/miter/bevel, CapSquare, AALineEnds, Stroke Inside/Center/CenterBiased/Outside/Legacy), AddConvexPolyFilled AAFill, all primitives with 1.93 signatures/flags, PathRect/PathArcTo 1.93, baked line textures; Custom Rendering demo flags UI restored. check_all: t_pixels incremental==full redraw, headless screenshot (tools/test/shot.py) --- build/ImGui.linemap.json | 2 +- build/ImGui.luau | 2389 ++++++++++++++++++++++++-------- build/imgui_love.linemap.json | 2 +- build/imgui_love.lua | 2389 ++++++++++++++++++++++++-------- docs/PORTING.md | 2 +- docs/PORT_STATUS.txt | 1324 +++++++++--------- imgui_demo.lua | 62 +- imgui_draw.lua | 2325 +++++++++++++++++++++++-------- tools/test/check_all.sh | 2 + tools/test/love/run.sh | 2 +- tools/test/scripts/t_shot.luau | 138 ++ tools/test/shot.py | 14 + 12 files changed, 6168 insertions(+), 2483 deletions(-) create mode 100644 tools/test/scripts/t_shot.luau create mode 100755 tools/test/shot.py diff --git a/build/ImGui.linemap.json b/build/ImGui.linemap.json index 2dec518..d56e917 100644 --- a/build/ImGui.linemap.json +++ b/build/ImGui.linemap.json @@ -1 +1 @@ -[[46, "imgui.lua"], [21461, "imgui_demo.lua"], [33299, "imgui_draw.lua"], [39279, "imgui_h.lua"], [42185, "imgui_impl_roblox.lua"], [43833, "imgui_impl_roblox_fonts.lua"], [50236, "imgui_internal.lua"], [53945, "imgui_tables.lua"], [59004, "imgui_widgets.lua"], [70993, "imstb_rectpack.lua"], [71657, "imstb_textedit.lua"], [73199, "imstb_truetype.lua"], [75987, "imstd_minstdio.lua"]] \ No newline at end of file +[[46, "imgui.lua"], [21461, "imgui_demo.lua"], [33313, "imgui_draw.lua"], [40456, "imgui_h.lua"], [43362, "imgui_impl_roblox.lua"], [45010, "imgui_impl_roblox_fonts.lua"], [51413, "imgui_internal.lua"], [55122, "imgui_tables.lua"], [60181, "imgui_widgets.lua"], [72170, "imstb_rectpack.lua"], [72834, "imstb_textedit.lua"], [74376, "imstb_truetype.lua"], [77164, "imstd_minstdio.lua"]] \ No newline at end of file diff --git a/build/ImGui.luau b/build/ImGui.luau index 79253cf..947188f 100644 --- a/build/ImGui.luau +++ b/build/ImGui.luau @@ -1,7 +1,7 @@ --!optimize 2 -- GENERATED by tools/bundle.py from *.lua, backends/, fonts/ - do not edit -local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImDrawFlags, ImGuiInputFlags, ImRect_Copy, ImVec2_CopyV, ImGuiDataType, ImStb, IM_TRUNC, ImGuiKey, ImGuiStyleVarInfo, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, IM_ASSERT_USER_ERROR_RET, ImHashStr, ImS32, ImGuiColorEditFlags, ImIsPowerOfTwo, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, ImAbs, ImU16, ExampleNames, ImGuiWindowFlags, ImGuiItemFlags, ImGui_ImplRoblox_GetBackendData, stbte, ImColor, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ExampleTreeFuncs, ImCos, ImGuiListClipper, ImS16, ImGuiMultiSelectFlags, ImGuiTabBarFlags, ImSin, ImUpperPowerOfTwo, ImVec2_MulVX, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, DockSettingsRenameNodeReferences, ExampleDualListBoxMT, IM_ASSERT_PARANOID, ImCeil, ImGuiCol, ImGuiSelectionBasicStorage, ImLengthSqr, ImS8, ImU8, _PathArcToFastExXY, ExampleAppPropertyEditorMT, ImFabs, ImGuiChildFlags, ImGuiLastItemData_Copy, ImGuiScrollFlags, ImLog, ImMemchr, ImStrByte, ExampleSelectionWithDeletionMT, IMGUI_DEBUG_LOG_VIEWPORT, ImGuiInputEvent, ImGuiTextBuffer, ImPow, CustomConstraints, ExampleAppLogMT, IMGUI_DEBUG_LOG_FONT, IMGUI_DEBUG_LOG_SELECTION, ImAddClampOverflow, ImFontAtlasBuildInit, ImFontCalcWordWrapPositionEx, ImGuiComboFlags, ImGuiLastItemData, ImGuiPayload_IsDataType, ImGuiPopupFlags, ImGuiSliderFlags, ImGuiTextFilter, ImRotate, ImSubClampOverflow, ImTextClassifierSetCharClass, ImVec2_AddVA, ImVec2_SubVA, MyDocument, ShowExampleMenuFile, CalcNextScrollFromScrollTargetAndClamp, IMGUI_PAYLOAD_TYPE_WINDOW, IMGUI_VERSION, IM_DRAWLIST_TEX_LINES_WIDTH_MAX, ImCharIsBlankW, ImCharIsSeparatorW, ImFmod, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiNavMoveFlags, ImGuiStyle_Copy, ImHashData, ImVec1_Copy, ExampleAsset_s_current_sort_specs, ExampleMemberInfo, ExampleTree_CreateNode, IMGUI_PAYLOAD_TYPE_COLOR_4F, IM_F32_TO_INT8_UNBOUND, IM_PI, ImCharIsBlankA, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontDiscardBakes, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImFontAtlasRectId_GetIndex, ImFontAtlasTextureAdd, ImFontAtlasTextureBlockQueueUpload, ImFontAtlasTextureGrow, ImFontBaked_BuildGrowIndex, ImFontConfig, ImFontGlyph, ImGuiTableColumn, ImGuiWindowClass, ImGui_ImplRoblox_RenderDrawData, ImParseFormatFindStart, ImRect_CopyFromV4, ImSqrt, ImTextureRect, ImTextureRef, ImTrunc64, MyItem_s_current_sort_specs, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, DockSettingsHandler_ClearAll, EditTableSizingFlags, ExampleSelectionWithDeletion, IMGUI_DEBUG_LOG_TABLE, IMGUI_PAYLOAD_TYPE_COLOR_3F, IM_FREE, ImDrawListSplitter, ImFontAtlasBakedDiscard, ImFontAtlasBuildDestroy, ImFontAtlasBuildNotifySetFont, ImFontAtlasBuildSetTexture, ImFontAtlasBuildSetupFontBakedFallback, ImFontAtlasFontDestroySourceData, ImFontAtlasPackInit +local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImDrawFlags, ImGuiInputFlags, ImRect_Copy, ImGuiDataType, ImStb, IM_TRUNC, ImGuiKey, ImVec2_CopyV, ImGuiStyleVarInfo, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, IM_ASSERT_USER_ERROR_RET, ImHashStr, ImS32, ImGuiColorEditFlags, ImIsPowerOfTwo, ImAbs, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, ImU16, ExampleNames, ImGuiWindowFlags, ImGuiItemFlags, ImGui_ImplRoblox_GetBackendData, stbte, ImColor, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ExampleTreeFuncs, IM_ASSERT_PARANOID, ImCos, ImGuiListClipper, ImS16, _PathArcToFastExXY, ImGuiMultiSelectFlags, ImGuiTabBarFlags, ImSin, ImUpperPowerOfTwo, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, DockSettingsRenameNodeReferences, ExampleDualListBoxMT, IM_DRAWLIST_TEX_LINES_WIDTH_MAX, ImCeil, ImGuiCol, ImGuiSelectionBasicStorage, ImLengthSqr, ImS8, ImU8, ExampleAppPropertyEditorMT, ImFabs, ImGuiChildFlags, ImGuiLastItemData_Copy, ImGuiScrollFlags, ImLog, ImMemchr, ImStrByte, ExampleSelectionWithDeletionMT, IMGUI_DEBUG_LOG_VIEWPORT, ImGuiInputEvent, ImGuiTextBuffer, ImPow, ImTrunc64, CustomConstraints, ExampleAppLogMT, IMGUI_DEBUG_LOG_FONT, IMGUI_DEBUG_LOG_SELECTION, ImAddClampOverflow, ImFontAtlasBuildInit, ImFontCalcWordWrapPositionEx, ImGuiComboFlags, ImGuiLastItemData, ImGuiPayload_IsDataType, ImGuiPopupFlags, ImGuiSliderFlags, ImGuiTextFilter, ImRotate, ImSqrt, ImSubClampOverflow, ImTextClassifierSetCharClass, MyDocument, ShowExampleMenuFile, CalcNextScrollFromScrollTargetAndClamp, IMGUI_PAYLOAD_TYPE_WINDOW, IMGUI_VERSION, ImCharIsBlankW, ImCharIsSeparatorW, ImFmod, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiNavMoveFlags, ImGuiStyle_Copy, ImHashData, ImVec1_Copy, ExampleAsset_s_current_sort_specs, ExampleMemberInfo, ExampleTree_CreateNode, IMGUI_PAYLOAD_TYPE_COLOR_4F, IM_F32_TO_INT8_UNBOUND, IM_PI, ImCharIsBlankA, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontDiscardBakes, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImFontAtlasRectId_GetIndex, ImFontAtlasTextureAdd, ImFontAtlasTextureBlockQueueUpload, ImFontAtlasTextureGrow, ImFontBaked_BuildGrowIndex, ImFontConfig, ImFontGlyph, ImGuiTableColumn, ImGuiWindowClass, ImGui_ImplRoblox_RenderDrawData, ImParseFormatFindStart, ImRect_CopyFromV4, ImTextureRect, ImTextureRef, MyItem_s_current_sort_specs, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, DockSettingsHandler_ClearAll, EditTableSizingFlags, ExampleSelectionWithDeletion, IMGUI_DEBUG_LOG_TABLE, IMGUI_PAYLOAD_TYPE_COLOR_3F, IM_FREE, ImDrawListSplitter, ImFontAtlasBakedDiscard, ImFontAtlasBuildDestroy, ImFontAtlasBuildNotifySetFont, ImFontAtlasBuildSetTexture, ImFontAtlasBuildSetupFontBakedFallback, ImFontAtlasFontDestroySourceData, ImFontAtlasPackInit, ImFontAtlasTextureBlockCopy, ImFontAtlasTextureGetSizeEstimate, ImFontAtlasUpdateDrawListsSharedData local type, mathMin, mathMax, mathFloor, mathCeil, bitOr, bitLShift = type, math.min, math.max, math.floor, math.ceil, bit32.bor, bit32.lshift local function IM_COL32(R, G, B, A) return (bitOr(bitLShift(A, 24 --[[IM_COL32_A_SHIFT]]), bitLShift(B, 16 --[[IM_COL32_B_SHIFT]]), bitLShift(G, 8 --[[IM_COL32_G_SHIFT]]), bitLShift(R, 0 --[[IM_COL32_R_SHIFT]]))) end @native @@ -31873,6 +31873,9 @@ S.ShowExampleAppCustomRendering = { curve_segments_override_v = 8, colf_stroke = ImVec4(1.000, 0.384, 0.169, 1.000), colf_fill = ImVec4(0.416, 0.378, 0.420, 1.000), + prim_stroke_flags = ImDrawFlags.None, + prim_other_flags = ImDrawFlags.AALineEnds, + scope_flags = nil, -- First init copy current state (see below). -- Canvas points = ImVector(), scrolling = ImVec2(0.0, 0.0), @@ -31956,25 +31959,35 @@ function ShowExampleAppCustomRendering(p_open) ImGui.ColorEdit4("Stroke Color", s.colf_stroke) ImGui.ColorEdit4("Fill Color", s.colf_fill) - -- [Port] ImDrawFlags_StrokeInside/StrokeCenter/StrokeCenterBiased/StrokeOutside/StrokeLegacy, AAFill/AALines/AALineEnds, - -- [Port] JoinMiter/CapSquare, AllowInPushScope_ and ImDrawList::PushDrawFlag()/PopDrawFlag() don't exist in this port - -- [Port] (1.92 stroker): prim_stroke_flags/prim_other_flags/scope_flags and their widgets are not ported, flags = None. ImGui.SeparatorText("Per primitive flags (AddXXX functions)") - ImGui.TextDisabled("(not supported by this port)") -- [Port] + _, s.prim_stroke_flags = ImGui.RadioButton("Default", s.prim_stroke_flags, 0 --[[ImDrawFlags.None]]); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Inside", s.prim_stroke_flags, 131072 --[[ImDrawFlags.StrokeInside]]); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Center", s.prim_stroke_flags, 262144 --[[ImDrawFlags.StrokeCenter]]); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("CenterBiased", s.prim_stroke_flags, 393216 --[[ImDrawFlags.StrokeCenterBiased]]); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Outside", s.prim_stroke_flags, 524288 --[[ImDrawFlags.StrokeOutside]]); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Legacy", s.prim_stroke_flags, 917504 --[[ImDrawFlags.StrokeLegacy]]) + _, s.prim_other_flags = ImGui.CheckboxFlags("AAFill", s.prim_other_flags, 4096 --[[ImDrawFlags.AAFill]]); ImGui.SameLine() -- Note: Only for functions taking flags. + _, s.prim_other_flags = ImGui.CheckboxFlags("AALines", s.prim_other_flags, 8192 --[[ImDrawFlags.AALines]]); ImGui.SameLine() -- Note: Only for functions taking flags. + _, s.prim_other_flags = ImGui.CheckboxFlags("AALineEnds", s.prim_other_flags, 16384 --[[ImDrawFlags.AALineEnds]]); ImGui.SameLine() + _, s.prim_other_flags = ImGui.CheckboxFlags("JoinMiter", s.prim_other_flags, 1024 --[[ImDrawFlags.JoinMiter]]); ImGui.SameLine() + _, s.prim_other_flags = ImGui.CheckboxFlags("CapSquare", s.prim_other_flags, 2048 --[[ImDrawFlags.CapSquare]]) --ImGui.CheckboxFlags("UseTexForRoundCorners", &prim_other_flags, ImDrawFlags_UseTexForRoundCorners); + if s.scope_flags == nil then s.scope_flags = bit32.band(draw_list.Flags, 8286704 --[[ImDrawFlags.AllowInPushScope_]]) end -- First init copy current state. ImGui.SeparatorText("Current ImDrawList flags (e.g. PushDrawFlag function)") ImGui.PushID("ScopeFlags") - ImGui.TextDisabled("(not supported by this port)") -- [Port] + _, s.scope_flags = ImGui.CheckboxFlags("AAFill", s.scope_flags, 4096 --[[ImDrawFlags.AAFill]]); ImGui.SameLine() + _, s.scope_flags = ImGui.CheckboxFlags("AALines", s.scope_flags, 8192 --[[ImDrawFlags.AALines]]); ImGui.SameLine() + _, s.scope_flags = ImGui.CheckboxFlags("AALineEnds", s.scope_flags, 16384 --[[ImDrawFlags.AALineEnds]]); ImGui.SameLine() + _, s.scope_flags = ImGui.CheckboxFlags("StrokeLegacy", s.scope_flags, 917504 --[[ImDrawFlags.StrokeLegacy]]) --ImGui.CheckboxFlags("UseTexForRoundCorners", &scope_flags, ImDrawFlags_UseTexForRoundCorners); ImGui.PopID() ImGui.Spacing() - -- [Port] draw_list->PushDrawFlag(ImDrawFlags_AllowInPushScope_, false); // Reset all existing (very unusual: done for this demo) - -- [Port] draw_list->PushDrawFlag(scope_flags, true); - local flags = 0 --[[ImDrawFlags.None]] -- [Port] prim_stroke_flags | prim_other_flags + draw_list:PushDrawFlag(8286704 --[[ImDrawFlags.AllowInPushScope_]], false) -- Reset all existing (very unusual: done for this demo) + draw_list:PushDrawFlag(s.scope_flags, true) + local flags = bit32.bor(s.prim_stroke_flags, s.prim_other_flags) - -- [Port] In this port AddNgon/AddCircle/AddEllipse/AddTriangle/AddLine*/AddBezier*/PathFillConvex take no flags argument (dropped below). local sz = s.sz local start_pos = ImGui.GetCursorScreenPos() local pi = 3.141592 @@ -32042,7 +32055,7 @@ function ShowExampleAppCustomRendering(p_open) for n = 0, 4 - 1 do draw_list:PathLineTo(ImVec2(x + half_sz + rotating_square[n + 1].x, y + half_sz + rotating_square[n + 1].y)) end - draw_list:PathFillConvex(col) -- [Port] no flags argument + draw_list:PathFillConvex(col, flags) x = x + step -- Path @@ -32069,14 +32082,14 @@ function ShowExampleAppCustomRendering(p_open) -- First row uses a thickness of 1.0f, second row uses the configurable thickness local col = ImColor(s.colf_stroke):U32() local th = (row == 0) and 1.0 or thickness - draw_list:AddNgon(ImVec2(x + half_sz, y + half_sz), half_sz, col, s.ngon_segments, th); x = x + step -- N-gon - draw_list:AddCircle(ImVec2(x + half_sz, y + half_sz), half_sz, col, circle_segments, th); x = x + step -- Circle - draw_list:AddEllipse(ImVec2(x + half_sz, y + half_sz), ImVec2(half_sz, sz * 0.3), col, -0.3, circle_segments, th); x = x + step -- Ellipse + draw_list:AddNgon(ImVec2(x + half_sz, y + half_sz), half_sz, col, s.ngon_segments, th, flags); x = x + step -- N-gon + draw_list:AddCircle(ImVec2(x + half_sz, y + half_sz), half_sz, col, circle_segments, th, flags); x = x + step -- Circle + draw_list:AddEllipse(ImVec2(x + half_sz, y + half_sz), ImVec2(half_sz, sz * 0.3), col, -0.3, circle_segments, th, flags); x = x + step -- Ellipse draw_list:AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0, th, flags); x = x + step -- Square draw_list:AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, flags); x = x + step -- Square with all rounded corners draw_list:AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, bit32.bor(flags, corners_tl_br)); x = x + step -- Square with two rounded corners - draw_list:AddTriangle(ImVec2(x + sz * 0.5, y), ImVec2(x + sz, y + sz), ImVec2(x, y + sz), col, th); x = x + step -- Triangle - draw_list:AddTriangle(ImVec2(x + sz * 0.2, y), ImVec2(x + sz * 0.4, y + sz), ImVec2(x, y + sz), col, th); x = x + step - math.floor(sz * 0.6) -- Thin triangle + draw_list:AddTriangle(ImVec2(x + sz * 0.5, y), ImVec2(x + sz, y + sz), ImVec2(x, y + sz), col, th, flags); x = x + step -- Triangle + draw_list:AddTriangle(ImVec2(x + sz * 0.2, y), ImVec2(x + sz * 0.4, y + sz), ImVec2(x, y + sz), col, th, flags); x = x + step - math.floor(sz * 0.6) -- Thin triangle -- Concave shape: U for _, p in ipairs(concave_shape) do @@ -32094,15 +32107,15 @@ function ShowExampleAppCustomRendering(p_open) -- Horizontal/Vertical/Diagonal lines local off = math.floor(sz * 0.4) - draw_list:AddLineH(x, x + sz, y, col, th) - draw_list:AddLine(ImVec2(x, y + off), ImVec2(x + sz, y + off), col, th) + draw_list:AddLineH(x, x + sz, y, col, th, flags) + draw_list:AddLine(ImVec2(x, y + off), ImVec2(x + sz, y + off), col, th, flags) x = x + step - draw_list:AddLineV(x, y, y + sz, col, th) - draw_list:AddLine(ImVec2(x + off, y + sz), ImVec2(x + off, y), col, th) + draw_list:AddLineV(x, y, y + sz, col, th, flags) + draw_list:AddLine(ImVec2(x + off, y + sz), ImVec2(x + off, y), col, th, flags) x = x + step - math.floor(half_sz) if not (draw_fill and draw_strokes) then -- -V560 - draw_list:AddLine(ImVec2(x, y), ImVec2(x + half_sz, y + sz), col, th) - draw_list:AddLine(ImVec2(x, y + sz), ImVec2(x + half_sz, y), col, th) + draw_list:AddLine(ImVec2(x, y), ImVec2(x + half_sz, y + sz), col, th, flags) + draw_list:AddLine(ImVec2(x, y + sz), ImVec2(x + half_sz, y), col, th, flags) end x = x + step - math.floor(half_sz) @@ -32121,12 +32134,12 @@ function ShowExampleAppCustomRendering(p_open) x = x + step -- Quadratic Bezier Curve (3 control points) - draw_list:AddBezierQuadratic(ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), ImVec2(x + cp3[3].x, y + cp3[3].y), col, th, curve_segments) + draw_list:AddBezierQuadratic(ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), ImVec2(x + cp3[3].x, y + cp3[3].y), col, th, curve_segments, flags) x = x + step -- Cubic Bezier Curve (4 control points) if not (draw_fill and draw_strokes) then -- -V560 - draw_list:AddBezierCubic(ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), ImVec2(x + cp4[4].x, y + cp4[4].y), col, th, curve_segments) + draw_list:AddBezierCubic(ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), ImVec2(x + cp4[4].x, y + cp4[4].y), col, th, curve_segments, flags) x = x + step end end @@ -32134,7 +32147,8 @@ function ShowExampleAppCustomRendering(p_open) end ImGui.Dummy(ImVec2(step * 15.5, step * 4.0)) - -- [Port] draw_list->PopDrawFlag(); x2 + draw_list:PopDrawFlag() + draw_list:PopDrawFlag() ImGui.Text("ImDrawList Vector Rendering Reference:") ImGui.TextLinkOpenURL("https://github.com/ocornut/imgui/wiki/Draw-List") @@ -33332,7 +33346,6 @@ local function WV(b, i, x, y, u, v, col) local o = (i - 1) * 36 writef64(b, o, x); writef64(b, o + 8, y); writef64(b, o + 16, u); writef64(b, o + 24, v); writeu32(b, o + 32, col) end -local function WVP(b, i, p, uv, col) WV(b, i, p.x, p.y, uv.x, uv.y, col) end local IM_FONTGLYPH_INDEX_UNUSED = 0xFFFF -- ((ImU16)-1) local IM_FONTGLYPH_INDEX_NOT_FOUND = 0xFFFE -- ((ImU16)-2) @@ -35249,8 +35262,9 @@ end --- @param atlas ImFontAtlas function ImFontAtlasBuildUpdateTexData(atlas) - ImFontAtlasBuildUpdateTexDataLines(atlas) ImFontAtlasBuildUpdateTexDataBasic(atlas) + ImFontAtlasBuildUpdateTexDataLines(atlas) + -- [Port] TODO baked round corners: ImFontAtlasBuildUpdateTexDataCorners(atlas) end -- Create a new texture, discard previous one @@ -35388,59 +35402,121 @@ function ImFontAtlasBuildUpdateTexDataLines(atlas) if add_and_draw then -- Register texture region for thick lines -- The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row - local pack_size = ImVec2(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) -- ImVec2i + local pack_size = ImVec2(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + 13 --[[IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT]]) -- ImVec2i builder.PackIdLinesTexData = atlas:AddCustomRect(pack_size.x, pack_size.y, r) assert(builder.PackIdLinesTexData ~= (-1) --[[ImFontAtlasRectId_Invalid]]) end - for n = 0, IM_DRAWLIST_TEX_LINES_WIDTH_MAX do -- +1 because of the zero-width row - -- Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - local y = n - local line_width = n + do + -- FIXME: Lines 0 to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH(4) are currently never used and could be ditched, saving DETAILED_WIDTH*WIDTH_MAX + for n = 0, IM_DRAWLIST_TEX_LINES_WIDTH_MAX do -- +1 because of the zero-width row + -- Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle + local y = n + local line_width = n + local pad_left = math.floor((r.w - line_width) / 2) + local pad_right = r.w - (pad_left + line_width) + assert(pad_left + line_width + pad_right == r.w and y < r.h) -- Make sure we're inside the texture bounds before we start writing pixels - --- @type int # If not floored, will cause black and purple horizontal stripes appearing on the right of the arrow(triangle) in the upper-left most area of the atlas - local pad_left = math.floor((r.w - line_width) / 2) - --- @type int - local pad_right = r.w - (pad_left + line_width) - - assert(pad_left + line_width + pad_right == r.w and y < r.h) -- Make sure we're inside the texture bounds before we start writing pixels - - -- Write each slice - if (add_and_draw and tex.Format == 1 --[[ImTextureFormat.Alpha8]]) then - local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) - - for i = 0, pad_left - 1 do - write_ptr[write_ptr_base + i] = 0x00 + -- Write each slice + if add_and_draw and tex.Format == 1 --[[ImTextureFormat.Alpha8]] then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + for i = 0, pad_left - 1 do + write_ptr[write_ptr_base + i] = 0x00 + end + for i = 0, line_width - 1 do + write_ptr[write_ptr_base + pad_left + i] = 0xFF + end + for i = 0, pad_right - 1 do + write_ptr[write_ptr_base + pad_left + line_width + i] = 0x00 + end + elseif add_and_draw and tex.Format == 0 --[[ImTextureFormat.RGBA32]] then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + for i = 0, pad_left - 1 do + write_u32(write_ptr, write_ptr_base + i * 4, IM_COL32(255, 255, 255, 0)) + end + for i = 0, line_width - 1 do + write_u32(write_ptr, write_ptr_base + (pad_left + i) * 4, 4294967295 --[[IM_COL32_WHITE]]) + end + for i = 0, pad_right - 1 do + write_u32(write_ptr, write_ptr_base + (pad_left + line_width + i) * 4, IM_COL32(255, 255, 255, 0)) + end end - for i = 0, line_width - 1 do - write_ptr[write_ptr_base + pad_left + i] = 0xFF - end + -- Refresh UV coordinates + local uv0_x, uv0_y = (r.x + pad_left - 0.5) * atlas.TexUvScale.x, (r.y + y) * atlas.TexUvScale.y + local uv1_x, uv1_y = (r.x + pad_left + line_width + 0.5) * atlas.TexUvScale.x, (r.y + y + 1) * atlas.TexUvScale.y + local half_v = (uv0_y + uv1_y) * 0.5 -- Calculate a constant V in the middle of the row to avoid sampling artifacts + -- Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. + local inv_line_width = (line_width > 0) and (1.0 / line_width) or 0.0 + builder.TexUvLines[n] = ImVec4(uv0_x, uv1_x, half_v, inv_line_width) + end + end - for i = 0, pad_right - 1 do - write_ptr[write_ptr_base + pad_left + line_width + i] = 0x00 - end - elseif (add_and_draw and tex.Format == 0 --[[ImTextureFormat.RGBA32]]) then - local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + do + -- Calculate super sampled line textures for smaller line widths to make transition between sizes smoother. - for i = 0, pad_left - 1 do - write_u32(write_ptr, write_ptr_base + i * 4, IM_COL32(255, 255, 255, 0)) - end - - for i = 0, line_width - 1 do - write_u32(write_ptr, write_ptr_base + (pad_left + i) * 4, 4294967295 --[[IM_COL32_WHITE]]) - end - - for i = 0, pad_right - 1 do - write_u32(write_ptr, write_ptr_base + (pad_left + line_width + i) * 4, IM_COL32(255, 255, 255, 0)) - end + -- Calculate ramp used for all the textures. + local ramp = {} -- ImU8[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT], 0-based + for n = 0, 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] - 1 do + ramp[n] = math.floor((n / 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]]) * 255.0) end - -- Refresh UV coordinates - local uv0 = ImVec2(r.x + pad_left - 1, r.y + y) * atlas.TexUvScale - local uv1 = ImVec2(r.x + pad_left + line_width + 1, r.y + y + 1) * atlas.TexUvScale - local half_v = (uv0.y + uv1.y) * 0.5 -- Calculate a constant V in the middle of the row to avoid sampling artifacts - atlas.TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v) + for n = 0, 13 --[[IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT]] - 1 do + local y = IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n + -- For integer thickness lines the one pixel line texture looks like this: + -- [ 0 | 1 | 0 ] + -- :...........: + -- Since the texture samples are at the center of the texel, we only use range above. + -- The result after bilinear filtering is a triangle across the used uv range. + -- + -- To super 2x sample that signal, we end up with following texture: + -- [ 0 | .5 | 1 | .5 | 0 ] + -- :.......................: + -- One might think that there should be 2 opaque pixels in the middle section, but no since we're super sampling the triangle signal. + local line_width = 1 + n + assert(4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] + line_width + 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] <= r.w and y < r.h) -- Make sure we're inside the texture bounds before we start writing pixels + + -- Write each slice + if add_and_draw and tex.Format == 1 --[[ImTextureFormat.Alpha8]] then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + local idx = 0 + for i = 0, 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] - 1 do + write_ptr[write_ptr_base + idx] = ramp[i]; idx = idx + 1 + end + for _ = 0, line_width - 1 do + write_ptr[write_ptr_base + idx] = 0xFF; idx = idx + 1 + end + for i = 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] - 1, 0, -1 do + write_ptr[write_ptr_base + idx] = ramp[i]; idx = idx + 1 + end + while idx < r.w do + write_ptr[write_ptr_base + idx] = 0x0; idx = idx + 1 + end + elseif add_and_draw and tex.Format == 0 --[[ImTextureFormat.RGBA32]] then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + local idx = 0 + for i = 0, 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] - 1 do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, ramp[i])); idx = idx + 1 + end + for _ = 0, line_width - 1 do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, 255)); idx = idx + 1 + end + for i = 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] - 1, 0, -1 do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, ramp[i])); idx = idx + 1 + end + while idx < r.w do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, 0)); idx = idx + 1 + end + end + + -- Refresh UV coordinates + local uv0_x, uv0_y = (r.x + 0.5) * atlas.TexUvScale.x, (r.y + y) * atlas.TexUvScale.y + local uv1_x, uv1_y = (r.x - 0.5 + 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] * 2 + line_width) * atlas.TexUvScale.x, (r.y + y + 1) * atlas.TexUvScale.y + local half_v = (uv0_y + uv1_y) * 0.5 -- Calculate a constant V in the middle of the row to avoid sampling artifacts + -- Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. + local inv_line_width = 1.0 / (1.0 + n / 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]]) + builder.TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n] = ImVec4(uv0_x, uv1_x, half_v, inv_line_width) + end end end @@ -35518,7 +35594,7 @@ function ImFontAtlasUpdateDrawListsSharedData(atlas) for _, shared_data in atlas.DrawListSharedDatas:iter() do if (shared_data.FontAtlas == atlas) then shared_data.TexUvWhitePixel = atlas.TexUvWhitePixel - shared_data.TexUvLines = atlas.TexUvLines + shared_data.TexUvLines = atlas.Builder.TexUvLines end end end @@ -36872,6 +36948,37 @@ local function IM_FIXNORMAL2F(VX, VY) return VX, VY end +-- - The miter limit describes how much the miter cross section can be scaled before we turn it into a bevel instead. +-- The minimum value of miter limit is 1.0, which translates to 180 (all corners are bevels), the higher the number, +-- the closer to zero the angle is (never bevel a corner). The miter limit angle can be calculated as: +-- miter_limit_angle = 2 * acos(1 / miter_limit) +-- - We can use the miter limit angle to figure maximum number of beveled corners in a convex polygon, +-- to prevent having allocate much extra memory (for a reasonable miter limit). +-- (n - 2) * 360 > k * miter_limit_angle + (n - k) * 360 +-- Where, n = number of corners in a polygon, k = beveled corners. Solved for k, simplifies to: +-- k < 360 / (180 - miter_limit_angle) +-- To find value strictly less than: +-- max_bevels = k = floor(360/(180-miter_limit_angle)) +local IM_POLYLINE_MITER_ANGLE_LIMIT = -0.9999619 -- Safeguard for miter corner calculation to avoid div by zero. cos(179.5) +local IM_POLYLINE_MITER_LIMIT = 4.0 -- (~29 deg corner) +local IM_POLYLINE_CONVEX_POLY_MAX_BEVELS = 2 -- For miter value 4.0 + +--- @param col ImU32 +--- @param alpha_mul float +local function ImAlphaMultiply(col, alpha_mul) + IM_ASSERT_PARANOID(alpha_mul >= 0.0 and alpha_mul < 1.0) -- We don't clamp! + local a = bit32.rshift(bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]), 24 --[[IM_COL32_A_SHIFT]]) + a = math.floor(a * alpha_mul) -- We don't need to clamp 0..255 because alpha is in 0..1 range. + return bit32.bor(bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])), bit32.lshift(a, 24 --[[IM_COL32_A_SHIFT]])) +end + +-- #define _GetStrokePos(_FLAGS, _DEFAULT_STROKE_POS) +local function _GetStrokePos(flags, default_stroke_pos) + local stroke_pos = bit32.band(flags, 917504 --[[ImDrawFlags.StrokeMask_]]) + if stroke_pos ~= 0 then return stroke_pos end + return default_stroke_pos +end + ------------------------------------------------------------------------------- -- [SECTION] ImDrawData ------------------------------------------------------------------------------- @@ -36923,6 +37030,25 @@ local function IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(_RAD, _MAXERROR) return ImCl local function IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(_N, _MAXERROR) return (_MAXERROR / (1 - ImCos(3.141592653589793 --[[IM_PI]] / ImMax(_N, 3.141592653589793 --[[IM_PI]])))) end local function IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_ERROR(_N, _RAD) return ((1 - ImCos(3.141592653589793 --[[IM_PI]] / ImMax(_N, 3.141592653589793 --[[IM_PI]]))) / _RAD) end +-- Calculates arc step and step count for 90 degree arc. The calculated arc step will complete a 90 degree arc when stepped arc_step_count times. +-- [Luau] returns arc_step, arc_step_count (C++ out-references) +local function CalcCornerArcStepAndCount(segment_count) + local quarter = 48 --[[IM_DRAWLIST_ARCFAST_TABLE_SIZE]] / 4 + local arc_step = ImClamp(math.floor(48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / segment_count), 1, quarter) + local arc_step_count = math.floor(quarter / arc_step) + if (arc_step * arc_step_count) ~= quarter then -- Make sure that the arc step can iterate 90 degree arc. + arc_step = math.floor(quarter / arc_step_count) + end + return arc_step, arc_step_count +end + +local function ApproxApothem(num_segments) + -- Calculates approximation (within 0.001%) distance from the center of the circumscribed circle to the edge of the polygon, cos(pi / n) + IM_ASSERT_PARANOID(num_segments >= 3) + local u = 1.0 / num_segments + return ((4.058712 * u * u) - 4.934802) * u * u + 1.0 +end + ------------------------------------------------------------------------------- -- [SECTION] ImDrawList ------------------------------------------------------------------------------- @@ -37391,101 +37517,6 @@ function MT.ImDrawList:ChannelsMerge() self._Splitter:Merge(self) end --- @param n int # 0-based function MT.ImDrawList:ChannelsSetCurrent(n) self._Splitter:SetCurrentChannel(self, n) end --- - We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds. --- - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. ---- @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, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then - return - end - - local uv = self._Data.TexUvWhitePixel - - if bit32.band(self.Flags, 4096 --[[ImDrawFlags.AAFill]]) ~= 0 then - local AA_SIZE = self._FringeScale - local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) - local idx_count = (points_count - 2) * 3 + points_count * 6 - local vtx_count = points_count * 2 - self:PrimReserve(idx_count, vtx_count) - - local vtx_inner_idx = self._VtxCurrentIdx - local vtx_outer_idx = self._VtxCurrentIdx + 1 - local idx_data = self.IdxBuffer.Data - local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops - for i = 2, points_count - 1 do - idx_data[idx_write_ptr + 0] = vtx_inner_idx; idx_data[idx_write_ptr + 1] = vtx_inner_idx + ((i - 1) * 2); idx_data[idx_write_ptr + 2] = vtx_inner_idx + (i * 2) - idx_write_ptr = idx_write_ptr + 3 - end - - -- Compute normals - local temp_buffer = self._Data.TempBuffer - if points_count > temp_buffer.Capacity then temp_buffer:reserve_discard(points_count) end -- [Luau] skip the method lookup when no growth is needed - local temp_normals = self._Data.TempBuffer.Data - - local i0 = points_count - for i1 = 1, points_count do - local p0 = points[i0] - local p1 = points[i1] - local dx = p1.x - p0.x - local dy = p1.y - p0.y - dx, dy = IM_NORMALIZE2F_OVER_ZERO(dx, dy) - temp_normals[i0].x = dy - temp_normals[i0].y = -dx - - i0 = i1 - end - - local vtx_data = self.VtxBuffer.Buf - local vtx_write_ptr = self._VtxWritePtr - local uv_x, uv_y = uv.x, uv.y - i0 = points_count - for i1 = 1, points_count do - local n0 = temp_normals[i0] - local n1 = temp_normals[i1] - local dm_x = (n0.x + n1.x) * 0.5 - local dm_y = (n0.y + n1.y) * 0.5 - dm_x, dm_y = IM_FIXNORMAL2F(dm_x, dm_y) - dm_x = dm_x * AA_SIZE * 0.5 - dm_y = dm_y * AA_SIZE * 0.5 - - local p1 = points[i1] - WV(vtx_data, vtx_write_ptr + 0, p1.x - dm_x, p1.y - dm_y, uv_x, uv_y, col) - WV(vtx_data, vtx_write_ptr + 1, p1.x + dm_x, p1.y + dm_y, uv_x, uv_y, col_trans) - vtx_write_ptr = vtx_write_ptr + 2 - - idx_data[idx_write_ptr + 0] = vtx_inner_idx + ((i1 - 1) * 2); idx_data[idx_write_ptr + 1] = vtx_inner_idx + ((i0 - 1) * 2); idx_data[idx_write_ptr + 2] = vtx_outer_idx + ((i0 - 1) * 2) - idx_data[idx_write_ptr + 3] = vtx_outer_idx + ((i0 - 1) * 2); idx_data[idx_write_ptr + 4] = vtx_outer_idx + ((i1 - 1) * 2); idx_data[idx_write_ptr + 5] = vtx_inner_idx + ((i1 - 1) * 2) - idx_write_ptr = idx_write_ptr + 6 - - i0 = i1 - end - self._VtxWritePtr = vtx_write_ptr - self._IdxWritePtr = idx_write_ptr - self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count - else - local idx_count = (points_count - 2) * 3 - local vtx_count = points_count - self:PrimReserve(idx_count, vtx_count) - - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - for i = 1, vtx_count do - local vtx_write_ptr = self._VtxWritePtr - WVP(vtx_data, vtx_write_ptr + 0, points[i], uv, col) - self._VtxWritePtr = vtx_write_ptr + 1 - end - for i = 3, points_count do - local idx_write_ptr = self._IdxWritePtr - idx_data[idx_write_ptr + 0] = self._VtxCurrentIdx; idx_data[idx_write_ptr + 1] = self._VtxCurrentIdx + i - 2; idx_data[idx_write_ptr + 2] = self._VtxCurrentIdx + i - 1 - self._IdxWritePtr = idx_write_ptr + 3 - end - - self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count - end -end - function MT.ImDrawList:PushTexture(tex_ref) self._TextureStack:push_back(tex_ref) self._CmdHeader.TexRef = tex_ref @@ -37656,249 +37687,641 @@ function MT.ImDrawList:PrimRectUV(a, c, uv_a, uv_c, col) self._IdxWritePtr = idx_write_ptr + 6 end ---- @param points ImVec2[] ---- @param points_count int ---- @param col ImU32 ---- @param thickness float ---- @param flags ImDrawFlags ---- @param max_inner_offset float # [Luau] unused by the pre-1.93 stroker +--- @param thickness float +--- @return float +-- [Luau] local function (also MT.ImDrawList._CalculateCenterBiasedOffset) +local function _CalculateCenterBiasedOffset(self, thickness) + -- Old jumpy one. + -- return IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + + -- Calculate outside offset for stroke position ImDrawFlags_StrokeCenterBiased + -- so that one side the of the line is always at pixel boundary. + -- On integer thickness both sides of the line are on pixel boundary. + local screen_thickness = thickness * self._InvFringeScale + local s_thickness = math.floor(screen_thickness) -- (int) of a positive value + return (math.floor(s_thickness / 2) + (s_thickness % 2) * (screen_thickness - s_thickness)) * self._FringeScale +end +MT.ImDrawList._CalculateCenterBiasedOffset = _CalculateCenterBiasedOffset + +-- Check if R is considered to be worthy rounding. Can be only used within ImDrawList due to use of _FringeScale. +-- #define IM_HAS_ROUNDING(RAD) ((RAD) >= (0.25f * _FringeScale)) -- [Luau] written inline at each use + +-- Select texture and adjust fringe size for specified line thickness. +-- [Luau] returns uv0.x, uv0.y, uv1.x, uv1.y, fringe instead of writing to out parameters +--- @param screen_thickness float +--- @param flags ImDrawFlags +local function _SelectLineTexture(self, screen_thickness, flags) + -- We assume that the screen_thickness has been clamped to 1.0 by the caller. + IM_ASSERT_PARANOID(screen_thickness >= 1.0) + + if bit32.band(flags, 8192 --[[ImDrawFlags.AALines]]) ~= 0 then + -- Handle the thickness between 1.0 - IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. + -- We use super sampled textures in this range to make texture changes less noticeable. + -- There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT-1 maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). + local texture_idx + if screen_thickness <= 4 --[[IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]] then + texture_idx = math.floor((screen_thickness - 1.0) * 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] + 0.5) + (IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + elseif screen_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1.5 then + texture_idx = math.floor(screen_thickness + 0.5) + else + texture_idx = IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1 -- Catches large values of screen_thickness (and +inf/NaN) that a ImMin() wouldn't. + end + + -- Scale the fringe so that the texture matches the line thickness. + local tex_uvs = self._Data.TexUvLines[texture_idx] -- 0-based table + return tex_uvs.x, tex_uvs.z, tex_uvs.y, tex_uvs.z, self._FringeScale * screen_thickness * tex_uvs.w -- tex_uvs.w = 1 / thickness + else + -- For no anti-alias we use fully opaque texture, and no fringe expansion. + local uv = self._Data.TexUvWhitePixel + return uv.x, uv.y, uv.x, uv.y, 0.0 + end +end +MT.ImDrawList._SelectLineTexture = _SelectLineTexture + +-- [Luau] scratch arrays (normals x/y and squared segment lengths) used instead of _Data->TempBuffer: no ImVec2 per point +local poly_normals_x, poly_normals_y, poly_sqr_lengths = {}, {}, {} + +--- @param points ImVec2[] # 1-based +--- @param points_count int +--- @param col ImU32 +--- @param thickness float +--- @param flags ImDrawFlags +--- @param max_inner_offset float +-- [Luau] IM_APPEND_VTX()/IM_APPEND_TRI() are expanded inline: vertex/index write pointers and _VtxCurrentIdx kept in locals, stored back at the end @native function MT.ImDrawList:_AddPolyline(points, points_count, col, thickness, flags, max_inner_offset) - if points_count < 2 or bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - local closed = bit32.band(flags, 512 --[[ImDrawFlags.Closed]]) ~= 0 - local opaque_uv = self._Data.TexUvWhitePixel - local count = closed and points_count or points_count - 1 -- Number of line segments - local thick_line = thickness > self._FringeScale + local screen_thickness = thickness * self._InvFringeScale + if screen_thickness < 1.0 then + if screen_thickness < 1.0 / 255.0 then + return + end + col = ImAlphaMultiply(col, screen_thickness) + screen_thickness = 1.0 + thickness = self._FringeScale + end + -- If this assert triggers on legacy code: -- - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. -- - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. -- Read more details near AddRect() + see "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. + flags = bit32.bor(flags, self.Flags) IM_ASSERT_USER_ERROR_RET(bit32.band(flags, 2147483663 --[[ImDrawFlags.InvalidMask_]]) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?") - if bit32.band(self.Flags, 8192 --[[ImDrawFlags.AALines]]) ~= 0 then - -- Anti-aliased stroke - local AA_SIZE = self._FringeScale - local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) + -- Allocate data for temp buffers + local normals_x, normals_y, sqr_lengths = poly_normals_x, poly_normals_y, poly_sqr_lengths - -- Thicknesses <1.0 should behave like thickness 1.0 - thickness = ImMax(thickness, 1.0) - local integer_thickness = ImFloor(thickness) - local fractional_thickness = thickness - integer_thickness - - -- Do we want to draw this line using a texture? - -- [Luau] pre-1.93 stroker kept until the 1.93 one is ported. ImDrawListFlags_AntiAliasedLinesUseTex no longer exists (== AALines) - -- and was never set by this port's SetupDrawListSharedData() before, so the texture path stays disabled (unchanged output). - local use_texture = false - - IM_ASSERT_PARANOID((not use_texture) or bit32.band(self._Data.Font.OwnerAtlas.Flags, 4 --[[ImFontAtlasFlags.NoBakedLines]]) == 0) - - local idx_count = use_texture and (count * 6) or (thick_line and count * 18 or count * 12) - local vtx_count = use_texture and (points_count * 2) or (thick_line and points_count * 4 or points_count * 3) - PrimReserve(self, idx_count, vtx_count) - - -- Temporary buffer - local temp_buffer_size = points_count * ((use_texture or not thick_line) and 3 or 5) - local temp_buffer = self._Data.TempBuffer - if temp_buffer_size > temp_buffer.Capacity then temp_buffer:reserve_discard(temp_buffer_size) end -- [Luau] skip the method lookup when no growth is needed - local temp_normals = self._Data.TempBuffer.Data - local temp_points = self._Data.TempBuffer.Data; local temp_points_start = points_count + 1 - - -- Calculate normals for each line segment - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local p1 = points[i1] - local p2 = points[i2] - local dx = p2.x - p1.x - local dy = p2.y - p1.y - dx, dy = IM_NORMALIZE2F_OVER_ZERO(dx, dy) - temp_normals[i1].x = dy - temp_normals[i1].y = -dx - end - if not closed then - ImVec2_Copy(temp_normals[points_count], temp_normals[points_count - 1]) - end - - -- If we are drawing a one-pixel-wide line without a texture, or a textured line of any width - if use_texture or not thick_line then - -- [PATH 1] Texture-based lines (thick or non-thick) - -- [PATH 2] Non texture-based lines (non-thick) - local half_draw_size = use_texture and (thickness * 0.5 + 1) or AA_SIZE - - -- If line is not closed, the first and last points need to be generated differently - if not closed then - ImVec2_CopyV(temp_points[temp_points_start + 0], ImVec2_AddVA(points[1], ImVec2_MulVX(temp_normals[1], half_draw_size))) - ImVec2_CopyV(temp_points[temp_points_start + 1], ImVec2_SubVA(points[1], ImVec2_MulVX(temp_normals[1], half_draw_size))) - ImVec2_CopyV(temp_points[temp_points_start + (points_count - 1) * 2 + 0], ImVec2_AddVA(points[points_count], ImVec2_MulVX(temp_normals[points_count], half_draw_size))) - ImVec2_CopyV(temp_points[temp_points_start + (points_count - 1) * 2 + 1], ImVec2_SubVA(points[points_count], ImVec2_MulVX(temp_normals[points_count], half_draw_size))) - end - - -- Generate indices and vertices - local idx1 = self._VtxCurrentIdx - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local idx2 = (i1 == points_count) and self._VtxCurrentIdx or (idx1 + (use_texture and 2 or 3)) - - -- Average normals - local dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5 - local dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5 - dm_x, dm_y = IM_FIXNORMAL2F(dm_x, dm_y) - dm_x = dm_x * half_draw_size - dm_y = dm_y * half_draw_size - - -- Add temporary vertices for the outer edges - local out_off = (i2 - 1) * 2 - temp_points[temp_points_start + out_off + 0].x = points[i2].x + dm_x - temp_points[temp_points_start + out_off + 0].y = points[i2].y + dm_y - temp_points[temp_points_start + out_off + 1].x = points[i2].x - dm_x - temp_points[temp_points_start + out_off + 1].y = points[i2].y - dm_y - - if use_texture then - -- Add indices for two triangles - idx_data[idx_write_ptr + 0] = idx2 + 0; idx_data[idx_write_ptr + 1] = idx1 + 0; idx_data[idx_write_ptr + 2] = idx1 + 1 - idx_data[idx_write_ptr + 3] = idx2 + 1; idx_data[idx_write_ptr + 4] = idx1 + 1; idx_data[idx_write_ptr + 5] = idx2 + 0 - idx_write_ptr = idx_write_ptr + 6 - else - -- Add indices for four triangles - idx_data[idx_write_ptr + 0] = idx2 + 0; idx_data[idx_write_ptr + 1] = idx1 + 0; idx_data[idx_write_ptr + 2] = idx1 + 2 - idx_data[idx_write_ptr + 3] = idx1 + 2; idx_data[idx_write_ptr + 4] = idx2 + 2; idx_data[idx_write_ptr + 5] = idx2 + 0 - idx_data[idx_write_ptr + 6] = idx2 + 1; idx_data[idx_write_ptr + 7] = idx1 + 1; idx_data[idx_write_ptr + 8] = idx1 + 0 - idx_data[idx_write_ptr + 9] = idx1 + 0; idx_data[idx_write_ptr + 10] = idx2 + 0; idx_data[idx_write_ptr + 11] = idx2 + 1 - idx_write_ptr = idx_write_ptr + 12 - end - - idx1 = idx2 - end - self._IdxWritePtr = idx_write_ptr - - -- Add vertices - local vtx_write_ptr = self._VtxWritePtr - if use_texture then - -- Texture-based: need to implement TexUvLines lookup - local tex_uvs = self._Data.TexUvLines[integer_thickness] - local tex_uv0 = ImVec2(tex_uvs.x, tex_uvs.y) - local tex_uv1 = ImVec2(tex_uvs.z, tex_uvs.w) - for i = 0, points_count - 1 do - WVP(vtx_data, vtx_write_ptr + 0, temp_points[temp_points_start + i * 2 + 0], tex_uv0, col) - WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 2 + 1], tex_uv1, col) - vtx_write_ptr = vtx_write_ptr + 2 - end - else - -- If we're not using a texture, we need the center vertex as well - for i = 0, points_count - 1 do - WVP(vtx_data, vtx_write_ptr + 0, points[i + 1], opaque_uv, col) - WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 2 + 0], opaque_uv, col_trans) - WVP(vtx_data, vtx_write_ptr + 2, temp_points[temp_points_start + i * 2 + 1], opaque_uv, col_trans) - vtx_write_ptr = vtx_write_ptr + 3 - end - end - self._VtxWritePtr = vtx_write_ptr - else - -- [PATH 3] Non texture-based lines (thick) - local half_inner_thickness = (thickness - AA_SIZE) * 0.5 - - -- If line is not closed, handle first and last points - if not closed then - local points_last = points_count - 1 - ImVec2_CopyV(temp_points[temp_points_start + 0], ImVec2_AddVA(points[1], ImVec2_MulVX(temp_normals[1], (half_inner_thickness + AA_SIZE)))) - ImVec2_CopyV(temp_points[temp_points_start + 1], ImVec2_AddVA(points[1], ImVec2_MulVX(temp_normals[1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + 2], ImVec2_SubVA(points[1], ImVec2_MulVX(temp_normals[1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + 3], ImVec2_SubVA(points[1], ImVec2_MulVX(temp_normals[1], (half_inner_thickness + AA_SIZE)))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 0], ImVec2_AddVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], (half_inner_thickness + AA_SIZE)))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 1], ImVec2_AddVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 2], ImVec2_SubVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 3], ImVec2_SubVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], (half_inner_thickness + AA_SIZE)))) - end - - -- Generate indices and vertices - local idx1 = self._VtxCurrentIdx - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local idx2 = (i1 == points_count) and self._VtxCurrentIdx or (idx1 + 4) - - -- Average normals - local dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5 - local dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5 - dm_x, dm_y = IM_FIXNORMAL2F(dm_x, dm_y) - local dm_out_x = dm_x * (half_inner_thickness + AA_SIZE) - local dm_out_y = dm_y * (half_inner_thickness + AA_SIZE) - local dm_in_x = dm_x * half_inner_thickness - local dm_in_y = dm_y * half_inner_thickness - - -- Add temporary vertices - local out_off = (i2 - 1) * 4 - temp_points[temp_points_start + out_off + 0].x = points[i2].x + dm_out_x - temp_points[temp_points_start + out_off + 0].y = points[i2].y + dm_out_y - temp_points[temp_points_start + out_off + 1].x = points[i2].x + dm_in_x - temp_points[temp_points_start + out_off + 1].y = points[i2].y + dm_in_y - temp_points[temp_points_start + out_off + 2].x = points[i2].x - dm_in_x - temp_points[temp_points_start + out_off + 2].y = points[i2].y - dm_in_y - temp_points[temp_points_start + out_off + 3].x = points[i2].x - dm_out_x - temp_points[temp_points_start + out_off + 3].y = points[i2].y - dm_out_y - - -- Add indices - idx_data[idx_write_ptr + 0] = idx2 + 1; idx_data[idx_write_ptr + 1] = idx1 + 1; idx_data[idx_write_ptr + 2] = idx1 + 2 - idx_data[idx_write_ptr + 3] = idx1 + 2; idx_data[idx_write_ptr + 4] = idx2 + 2; idx_data[idx_write_ptr + 5] = idx2 + 1 - idx_data[idx_write_ptr + 6] = idx2 + 1; idx_data[idx_write_ptr + 7] = idx1 + 1; idx_data[idx_write_ptr + 8] = idx1 + 0 - idx_data[idx_write_ptr + 9] = idx1 + 0; idx_data[idx_write_ptr + 10] = idx2 + 0; idx_data[idx_write_ptr + 11] = idx2 + 1 - idx_data[idx_write_ptr + 12] = idx2 + 2; idx_data[idx_write_ptr + 13] = idx1 + 2; idx_data[idx_write_ptr + 14] = idx1 + 3 - idx_data[idx_write_ptr + 15] = idx1 + 3; idx_data[idx_write_ptr + 16] = idx2 + 3; idx_data[idx_write_ptr + 17] = idx2 + 2 - idx_write_ptr = idx_write_ptr + 18 - - idx1 = idx2 - end - self._IdxWritePtr = idx_write_ptr - - -- Add vertices - local vtx_write_ptr = self._VtxWritePtr - for i = 0, points_count - 1 do - WVP(vtx_data, vtx_write_ptr + 0, temp_points[temp_points_start + i * 4 + 0], opaque_uv, col_trans) - WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 4 + 1], opaque_uv, col) - WVP(vtx_data, vtx_write_ptr + 2, temp_points[temp_points_start + i * 4 + 2], opaque_uv, col) - WVP(vtx_data, vtx_write_ptr + 3, temp_points[temp_points_start + i * 4 + 3], opaque_uv, col_trans) - vtx_write_ptr = vtx_write_ptr + 4 - end - self._VtxWritePtr = vtx_write_ptr - end - self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count + -- Calculate normals for each line segment + for i = 1, points_count - 1 do + local p0 = points[i] + local p1 = points[i + 1] + local dx = p1.x - p0.x + local dy = p1.y - p0.y + local d2 = dx * dx + dy * dy + local inv_len = 0.0 + if d2 > 0.0 then inv_len = 1.0 / math.sqrt(d2) end -- ImRsqrtPrecise() + normals_x[i] = -dy * inv_len + normals_y[i] = dx * inv_len + sqr_lengths[i] = d2 + end + if bit32.band(flags, 512 --[[ImDrawFlags.Closed]]) ~= 0 then + local p0 = points[points_count] + local p1 = points[1] + local dx = p1.x - p0.x + local dy = p1.y - p0.y + local d2 = dx * dx + dy * dy + local inv_len = 0.0 + if d2 > 0.0 then inv_len = 1.0 / math.sqrt(d2) end -- ImRsqrtPrecise() + normals_x[points_count] = -dy * inv_len + normals_y[points_count] = dx * inv_len + sqr_lengths[points_count] = d2 else - -- [PATH 4] Non texture-based, Non anti-aliased lines - local idx_count = count * 6 - local vtx_count = count * 4 - PrimReserve(self, idx_count, vtx_count) + normals_x[points_count] = normals_x[points_count - 1] + normals_y[points_count] = normals_y[points_count - 1] + sqr_lengths[points_count] = 0.0 + end - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local p1 = points[i1] - local p2 = points[i2] + local u0, v0, u1, v1, fringe = _SelectLineTexture(self, screen_thickness, flags) - local dx = p2.x - p1.x - local dy = p2.y - p1.y - dx, dy = IM_NORMALIZE2F_OVER_ZERO(dx, dy) - dx = dx * (thickness * 0.5) - dy = dy * (thickness * 0.5) + local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) - local vtx_write_ptr = self._VtxWritePtr - WV(vtx_data, vtx_write_ptr + 0, p1.x + dy, p1.y - dx, opaque_uv.x, opaque_uv.y, col) - WV(vtx_data, vtx_write_ptr + 1, p2.x + dy, p2.y - dx, opaque_uv.x, opaque_uv.y, col) - WV(vtx_data, vtx_write_ptr + 2, p2.x - dy, p2.y + dx, opaque_uv.x, opaque_uv.y, col) - WV(vtx_data, vtx_write_ptr + 3, p1.x - dy, p1.y + dx, opaque_uv.x, opaque_uv.y, col) - self._VtxWritePtr = vtx_write_ptr + 4 + local stroke_pos = _GetStrokePos(flags, 262144 --[[ImDrawFlags.StrokeCenter]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + flags = bit32.band(bit32.bor(flags, 1024 --[[ImDrawFlags.JoinMiter]]), bit32.bnot(16384 --[[ImDrawFlags.AALineEnds]])) + end - local idx_write_ptr = self._IdxWritePtr - idx_data[idx_write_ptr + 0] = self._VtxCurrentIdx + 0; idx_data[idx_write_ptr + 1] = self._VtxCurrentIdx + 1; idx_data[idx_write_ptr + 2] = self._VtxCurrentIdx + 2 - idx_data[idx_write_ptr + 3] = self._VtxCurrentIdx + 0; idx_data[idx_write_ptr + 4] = self._VtxCurrentIdx + 2; idx_data[idx_write_ptr + 5] = self._VtxCurrentIdx + 3 - self._IdxWritePtr = idx_write_ptr + 6 - self._VtxCurrentIdx = self._VtxCurrentIdx + 4 + local closed = bit32.band(flags, 512 --[[ImDrawFlags.Closed]]) ~= 0 + local miters_only = bit32.band(flags, 1024 --[[ImDrawFlags.JoinMiter]]) ~= 0 + local miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT + + local thickness0 = (thickness + fringe) * 0.5 -- Center (or legacy) + if stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + thickness0 = thickness + fringe * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + thickness0 = _CalculateCenterBiasedOffset(self, thickness) + fringe * 0.5 + elseif stroke_pos == 131072 --[[ImDrawFlags.StrokeInside]] then + thickness0 = fringe * 0.5 + end + local thickness1 = (thickness + fringe) - thickness0 + + local idx_count = 0 + local vtx_count = 0 + local max_verts_per_point = miters_only and 2 or 4 + local max_tris_per_point = miters_only and 2 or 5 + if closed then + vtx_count = --[[body]] points_count * max_verts_per_point + --[[closing]] 2 + idx_count = (--[[body]] points_count * max_tris_per_point + --[[closing]] 2) * 3 + else + vtx_count = --[[body]] (points_count - 2) * max_verts_per_point + --[[caps]] (4 * 2) + idx_count = (--[[body]] (points_count - 2) * max_tris_per_point + --[[last seg]] 2 + --[[caps]] (2 * 2)) * 3 + end + PrimReserve(self, idx_count, vtx_count) + + local ratio = thickness0 / (thickness0 + thickness1) -- The points that use uv2 are placed directly on the path (offset = 0), calculate the texture position from stroke offsets. + local u2, v2 = u0 + (u1 - u0) * ratio, v0 + + -- Clamp inner offset and adjust texture coordinates to match the change. + -- This is used by some of the convex shape rendering to avoid rendering artifacts when thickness is about to fill the whole shape. + if thickness1 > max_inner_offset then + thickness1 = max_inner_offset + local s = (thickness0 + thickness1) / (thickness + fringe) + local du = u1 - u0 + u1 = u0 + du * s + end + + local half_aa = self._FringeScale * 0.5 -- Used for end caps, does not use "fringe" since end cap AA is not using the texture. + local use_aa_ends = bit32.band(flags, bit32.bor(8192 --[[ImDrawFlags.AALines]], 16384 --[[ImDrawFlags.AALineEnds]])) == bit32.bor(8192 --[[ImDrawFlags.AALines]], 16384 --[[ImDrawFlags.AALineEnds]]) + + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr -- 1-based write position in VtxBuffer + local iw = self._IdxWritePtr -- 1-based write position in IdxBuffer + local vtx_idx = self._VtxCurrentIdx + local first_vtx_offset = vw + local first_vtx_idx = vtx_idx -- [Luau] VtxBuffer position of index 'n' is first_vtx_offset + (n - first_vtx_idx) + local base_idx = vtx_idx + + local p1x, p1y + local n1x, n1y + local len_sqr1 + + local point_idx = 1 -- [Luau] 1-based, iterate point_idx..point_last inclusive + local point_last = points_count + if not closed then + -- Start cap + point_idx = point_idx + 1 + point_last = point_last - 1 + local p = points[1] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[1], normals_y[1] + len_sqr1 = sqr_lengths[1] + + local dir_x = n1y + local dir_y = -n1x + if bit32.band(flags, 2048 --[[ImDrawFlags.CapSquare]]) ~= 0 then + p1x = p1x - dir_x * thickness * 0.5 + p1y = p1y - dir_y * thickness * 0.5 end + + if not use_aa_ends then + base_idx = vtx_idx + WV(vb, vw + 0, p1x - n1x * thickness0, p1y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 1, p1x + n1x * thickness1, p1y + n1y * thickness1, u1, v1, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + else + local pa_x = p1x - dir_x * half_aa + local pa_y = p1y - dir_y * half_aa + local pb_x = p1x + dir_x * half_aa + local pb_y = p1y + dir_y * half_aa + + base_idx = vtx_idx + WV(vb, vw + 0, pa_x - n1x * thickness0, pa_y - n1y * thickness0, u0, v0, col_trans) + WV(vb, vw + 1, pa_x + n1x * thickness1, pa_y + n1y * thickness1, u1, v1, col_trans) + + local next_base_idx = vtx_idx + 2 + WV(vb, vw + 2, pb_x - n1x * thickness0, pb_y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 3, pb_x + n1x * thickness1, pb_y + n1y * thickness1, u1, v1, col) + vw = vw + 4; vtx_idx = vtx_idx + 4 + + -- AA cap + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + base_idx = next_base_idx + end + else + -- Wrap around segment + local p = points[points_count] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[points_count], normals_y[points_count] + len_sqr1 = sqr_lengths[points_count] + + -- This will be filled later, allocate space. + base_idx = vtx_idx + WV(vb, vw + 0, 0, 0, u0, v0, col) + WV(vb, vw + 1, 0, 0, u1, v1, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + end + + if miters_only then + for pi = point_idx, point_last do + local n0x, n0y = n1x, n1y + local p = points[pi] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[pi], normals_y[pi] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor + local miter_offset_y = (n0y + n1y) * miter_scale_factor + + -- Miter + local next_base_idx = vtx_idx + WV(vb, vw + 0, p1x - miter_offset_x * thickness0, p1y - miter_offset_y * thickness0, u0, v0, col) -- 2 + WV(vb, vw + 1, p1x + miter_offset_x * thickness1, p1y + miter_offset_y * thickness1, u1, v1, col) -- 3 + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + + base_idx = next_base_idx + end + else + for pi = point_idx, point_last do + local n0x, n0y = n1x, n1y + local len_sqr0 = len_sqr1 + local p = points[pi] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[pi], normals_y[pi] + len_sqr1 = sqr_lengths[pi] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + local sin_theta = n0y * n1x - n0x * n1y + + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor + local miter_offset_y = (n0y + n1y) * miter_scale_factor + + local miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y + + if miter_distance_sqr > miter_distance_limit_sqr then + -- Clipped bevel + + -- Limit the inner miter offset to avoid overshooting. + local ref_thickness = (sin_theta < 0.0) and thickness1 or thickness0 + local ref_thickness_sqr = ref_thickness * ref_thickness + local limit_sqr = ImMax(ImMax(len_sqr0, len_sqr1), ref_thickness_sqr) + local ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr + if ref_miter_dist_sqr > limit_sqr then + local scale = ImSqrt(limit_sqr / ref_miter_dist_sqr) + miter_offset_x = miter_offset_x * scale + miter_offset_y = miter_offset_y * scale + end + + local bn_x, bn_y = IM_NORMALIZE2F_OVER_ZERO(n0x + n1x, n0y + n1y) + + local side_offset = ((n0x * bn_x + n0y * bn_y) - 1.0) / (n0y * bn_x - n0x * bn_y) + local sd_x = bn_y * side_offset + local sd_y = -bn_x * side_offset + + -- Bevel + local next_base_idx = vtx_idx + 2 + if sin_theta < 0.0 then + WV(vb, vw + 0, p1x - (bn_x + sd_x) * thickness0, p1y - (bn_y + sd_y) * thickness0, u0, v0, col) -- 2 + WV(vb, vw + 1, p1x, p1y, u2, v2, col) -- 3 + WV(vb, vw + 2, p1x - (bn_x - sd_x) * thickness0, p1y - (bn_y - sd_y) * thickness0, u0, v0, col) -- 4 + WV(vb, vw + 3, p1x + miter_offset_x * thickness1, p1y + miter_offset_y * thickness1, u1, v1, col) -- 5 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 5 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 5; ib[iw + 5] = base_idx + 1 + -- Bevel + ib[iw + 6] = base_idx + 3; ib[iw + 7] = base_idx + 5; ib[iw + 8] = base_idx + 2 + ib[iw + 9] = base_idx + 3; ib[iw + 10] = base_idx + 2; ib[iw + 11] = base_idx + 4 + ib[iw + 12] = base_idx + 3; ib[iw + 13] = base_idx + 4; ib[iw + 14] = base_idx + 5 + else + WV(vb, vw + 0, p1x + (bn_x + sd_x) * thickness1, p1y + (bn_y + sd_y) * thickness1, u1, v1, col) -- 2 + WV(vb, vw + 1, p1x, p1y, u2, v2, col) -- 3 + WV(vb, vw + 2, p1x - miter_offset_x * thickness0, p1y - miter_offset_y * thickness0, u0, v0, col) -- 4 + WV(vb, vw + 3, p1x + (bn_x - sd_x) * thickness1, p1y + (bn_y - sd_y) * thickness1, u1, v1, col) -- 5 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 4; ib[iw + 2] = base_idx + 2 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 2; ib[iw + 5] = base_idx + 1 + -- Bevel + ib[iw + 6] = base_idx + 3; ib[iw + 7] = base_idx + 2; ib[iw + 8] = base_idx + 4 + ib[iw + 9] = base_idx + 3; ib[iw + 10] = base_idx + 4; ib[iw + 11] = base_idx + 5 + ib[iw + 12] = base_idx + 3; ib[iw + 13] = base_idx + 5; ib[iw + 14] = base_idx + 2 + end + vw = vw + 4; vtx_idx = vtx_idx + 4 + iw = iw + 15 + base_idx = next_base_idx + else + -- Miter + local next_base_idx = vtx_idx + WV(vb, vw + 0, p1x - miter_offset_x * thickness0, p1y - miter_offset_y * thickness0, u0, v0, col) -- 2 + WV(vb, vw + 1, p1x + miter_offset_x * thickness1, p1y + miter_offset_y * thickness1, u1, v1, col) -- 3 + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + base_idx = next_base_idx + end + end + end + + -- End cap + if not closed then + local p = points[points_count] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[points_count], normals_y[points_count] + + -- End cap + local dir_x = n1y + local dir_y = -n1x + if bit32.band(flags, 2048 --[[ImDrawFlags.CapSquare]]) ~= 0 then + p1x = p1x + dir_x * thickness * 0.5 + p1y = p1y + dir_y * thickness * 0.5 + end + + if not use_aa_ends then + WV(vb, vw + 0, p1x - n1x * thickness0, p1y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 1, p1x + n1x * thickness1, p1y + n1y * thickness1, u1, v1, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Connect + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + else + local pa_x = p1x - dir_x * half_aa + local pa_y = p1y - dir_y * half_aa + local pb_x = p1x + dir_x * half_aa + local pb_y = p1y + dir_y * half_aa + + local next_base_idx = vtx_idx + WV(vb, vw + 0, pa_x - n1x * thickness0, pa_y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 1, pa_x + n1x * thickness1, pa_y + n1y * thickness1, u1, v1, col) + + WV(vb, vw + 2, pb_x - n1x * thickness0, pb_y - n1y * thickness0, u0, v0, col_trans) + WV(vb, vw + 3, pb_x + n1x * thickness1, pb_y + n1y * thickness1, u1, v1, col_trans) + vw = vw + 4; vtx_idx = vtx_idx + 4 + + -- Connect + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + base_idx = next_base_idx + + -- AA cap + ib[iw + 6] = base_idx + 0; ib[iw + 7] = base_idx + 2; ib[iw + 8] = base_idx + 3 + ib[iw + 9] = base_idx + 0; ib[iw + 10] = base_idx + 3; ib[iw + 11] = base_idx + 1 + iw = iw + 12 + end + else + -- Connect the path. + -- [Luau] VtxBuffer.Data[first_vtx_offset + 0..1] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 0..1] (2 vertices of 36 bytes) + buffer.copy(vb, (first_vtx_offset - 1) * 36, vb, (first_vtx_offset + (base_idx - first_vtx_idx) - 1) * 36, 2 * 36) + end + + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = vtx_idx + + -- Restore unused memory + local remaining_idx_count = self.IdxBuffer.Size - (iw - 1) + local remaining_vtx_count = self.VtxBuffer.Size - (vw - 1) + IM_ASSERT_PARANOID(remaining_idx_count >= 0 and remaining_vtx_count >= 0) + if remaining_idx_count > 0 or remaining_vtx_count > 0 then -- [Luau] skip the call when nothing is left + self:PrimUnreserve(remaining_idx_count, remaining_vtx_count) + end +end + +-- - We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds. +-- - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. +--- @param points ImVec2[] # 1-based +--- @param points_count int +--- @param col ImU32 +--- @param flags? ImDrawFlags +@native +function MT.ImDrawList:AddConvexPolyFilled(points, points_count, col, flags) + if points_count < 3 or bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + if flags == nil then flags = 0 end + + local uv = self._Data.TexUvWhitePixel + local uv_x, uv_y = uv.x, uv.y + flags = bit32.bor(flags, self.Flags) + if bit32.band(flags, 4096 --[[ImDrawFlags.AAFill]]) ~= 0 then + local half_aa = self._FringeScale * 0.5 + local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) + local miters_only = bit32.band(flags, 1024 --[[ImDrawFlags.JoinMiter]]) ~= 0 + local miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT + + local idx_count = ((points_count - 2) + points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) * 3 + local vtx_count = points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local start_vtx_ptr = self._VtxWritePtr + local start_idx_ptr = self._IdxWritePtr + local iw = start_idx_ptr + + -- Compute normals and segment lengths + local temp_normals_x, temp_normals_y, temp_sqr_lengths = poly_normals_x, poly_normals_y, poly_sqr_lengths + local i0 = points_count + for i1 = 1, points_count do + local p0 = points[i0] + local p1 = points[i1] + local dx = p1.x - p0.x + local dy = p1.y - p0.y + local d2 = dx * dx + dy * dy + if d2 > 0.0 then + local inv_len = 1.0 / math.sqrt(d2) -- ImRsqrtPrecise() + dx = dx * inv_len + dy = dy * inv_len + end + temp_normals_x[i0] = dy + temp_normals_y[i0] = -dx + temp_sqr_lengths[i0] = d2 + i0 = i1 + end + + local vtx_inner_idx = self._VtxCurrentIdx + local inner_vtx_ptr = start_vtx_ptr + local vw = start_vtx_ptr + points_count + local vtx_idx = vtx_inner_idx + points_count + + local prev_outer_idx = 0 -- This should be the index of the last vertex, but we don't know it yet. Will need to patch once we're done. + if miters_only then + i0 = points_count + for i1 = 1, points_count do + local p1 = points[i1] + local p1x, p1y = p1.x, p1.y + local n0x, n0y = temp_normals_x[i0], temp_normals_y[i0] + local n1x, n1y = temp_normals_x[i1], temp_normals_y[i1] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor * half_aa + local miter_offset_y = (n0y + n1y) * miter_scale_factor * half_aa + + -- Inner + WV(vb, inner_vtx_ptr, p1x - miter_offset_x, p1y - miter_offset_y, uv_x, uv_y, col) + inner_vtx_ptr = inner_vtx_ptr + 1 + + local prev_inner_idx = vtx_inner_idx + (i0 - 1) + local inner_idx = vtx_inner_idx + (i1 - 1) + local outer_idx = vtx_idx + + -- Outer + WV(vb, vw, p1x + miter_offset_x, p1y + miter_offset_y, uv_x, uv_y, col_trans) + vw = vw + 1; vtx_idx = vtx_idx + 1 + -- Connect with previous + ib[iw + 0] = prev_outer_idx; ib[iw + 1] = outer_idx; ib[iw + 2] = inner_idx + ib[iw + 3] = prev_outer_idx; ib[iw + 4] = inner_idx; ib[iw + 5] = prev_inner_idx + iw = iw + 6 + + prev_outer_idx = outer_idx + i0 = i1 + end + else + local bevel_count = 0 + i0 = points_count + for i1 = 1, points_count do + local p1 = points[i1] + local p1x, p1y = p1.x, p1.y + local n0x, n0y = temp_normals_x[i0], temp_normals_y[i0] + local n1x, n1y = temp_normals_x[i1], temp_normals_y[i1] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor + local miter_offset_y = (n0y + n1y) * miter_scale_factor + + local miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y + local bevel = miter_distance_sqr > miter_distance_limit_sqr + if bevel then + -- Limit inner bevel so that it is does not shoot out outside the polygon. + -- (using ImMax is intentional: using min limited too much in Mikko's tests when you have short edge next to long one. + -- The idea of that check is to keep that worst case overshoot no bigger than the polygon) + local ref_thickness_sqr = half_aa * half_aa + local limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]) + local ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr + if ref_miter_dist_sqr > limit_sqr then + local scale = ImSqrt(limit_sqr / ref_miter_dist_sqr) + miter_offset_x = miter_offset_x * scale + miter_offset_y = miter_offset_y * scale + end + end + + miter_offset_x = miter_offset_x * half_aa + miter_offset_y = miter_offset_y * half_aa + + -- Inner + WV(vb, inner_vtx_ptr, p1x - miter_offset_x, p1y - miter_offset_y, uv_x, uv_y, col) + inner_vtx_ptr = inner_vtx_ptr + 1 + + local prev_inner_idx = vtx_inner_idx + (i0 - 1) + local inner_idx = vtx_inner_idx + (i1 - 1) + local outer_idx = vtx_idx + + -- Outer + if bevel and bevel_count < IM_POLYLINE_CONVEX_POLY_MAX_BEVELS then + -- Because the polygon is convex, we know the maximum number of bevel corners we can hit (which is very small number). + -- We keep track of them just in case the calculations disagree. + bevel_count = bevel_count + 1 + + WV(vb, vw + 0, p1x + n0x * half_aa, p1y + n0y * half_aa, uv_x, uv_y, col_trans) + WV(vb, vw + 1, p1x + n1x * half_aa, p1y + n1y * half_aa, uv_x, uv_y, col_trans) + vw = vw + 2; vtx_idx = vtx_idx + 2 + -- Connect with previous + ib[iw + 0] = prev_outer_idx; ib[iw + 1] = outer_idx; ib[iw + 2] = inner_idx + ib[iw + 3] = prev_outer_idx; ib[iw + 4] = inner_idx; ib[iw + 5] = prev_inner_idx + -- Fill bevel + ib[iw + 6] = outer_idx; ib[iw + 7] = outer_idx + 1; ib[iw + 8] = inner_idx + iw = iw + 9 + outer_idx = outer_idx + 1 + else + WV(vb, vw, p1x + miter_offset_x, p1y + miter_offset_y, uv_x, uv_y, col_trans) + vw = vw + 1; vtx_idx = vtx_idx + 1 + -- Connect with previous + ib[iw + 0] = prev_outer_idx; ib[iw + 1] = outer_idx; ib[iw + 2] = inner_idx + ib[iw + 3] = prev_outer_idx; ib[iw + 4] = inner_idx; ib[iw + 5] = prev_inner_idx + iw = iw + 6 + end + + prev_outer_idx = outer_idx + i0 = i1 + end + end + + -- Patch first segment to wrap around + ib[start_idx_ptr + 0] = prev_outer_idx + ib[start_idx_ptr + 3] = prev_outer_idx + + -- Add indices for fill + for i = 2, points_count - 1 do + ib[iw + 0] = vtx_inner_idx; ib[iw + 1] = vtx_inner_idx + i - 1; ib[iw + 2] = vtx_inner_idx + i + iw = iw + 3 + end + + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = vtx_idx + + local idx_used = iw - start_idx_ptr + local vtx_used = vw - start_vtx_ptr + assert(idx_used <= idx_count and vtx_used <= vtx_count) + if idx_used < idx_count or vtx_used < vtx_count then + self:PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used) + end + else + -- Non Anti-aliased Fill + local idx_count = (points_count - 2) * 3 + local vtx_count = points_count + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + local base_idx = self._VtxCurrentIdx + for i = 1, vtx_count do + local p = points[i] + WV(vb, vw, p.x, p.y, uv_x, uv_y, col) + vw = vw + 1 + end + for i = 2, points_count - 1 do + ib[iw + 0] = base_idx; ib[iw + 1] = base_idx + i - 1; ib[iw + 2] = base_idx + i + iw = iw + 3 + end + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = base_idx + vtx_count end end @@ -37919,15 +38342,16 @@ local function PathRectXY(self, ax, ay, bx, by, rounding, flags) if not rounding then rounding = 0.0 end if not flags then flags = 0 end - if rounding >= 0.5 then - assert(bit32.band(flags, 0x0F) == 0, "Misuse of legacy hardcoded ImDrawCornerFlags values!") + local has_rounding = rounding >= 0.25 * self._FringeScale -- IM_HAS_ROUNDING(rounding) + if has_rounding then if bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 0 then flags = bit32.bor(flags, 240 --[[ImDrawFlags.RoundAll]]) end + rounding = ImMin(rounding, ImAbs(bx - ax) * (((bit32.band(flags, 48 --[[ImDrawFlags.RoundTop]]) == 48 --[[ImDrawFlags.RoundTop]]) or (bit32.band(flags, 192 --[[ImDrawFlags.RoundBottom]]) == 192 --[[ImDrawFlags.RoundBottom]])) and 0.5 or 1.0) - 1.0) rounding = ImMin(rounding, ImAbs(by - ay) * (((bit32.band(flags, 80 --[[ImDrawFlags.RoundLeft]]) == 80 --[[ImDrawFlags.RoundLeft]]) or (bit32.band(flags, 160 --[[ImDrawFlags.RoundRight]]) == 160 --[[ImDrawFlags.RoundRight]])) and 0.5 or 1.0) - 1.0) end - if rounding < 0.5 or (bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]]) then + if not has_rounding or (bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]]) then PathLineToXY(self, ax, ay) PathLineToXY(self, bx, ay) PathLineToXY(self, bx, by) @@ -37937,7 +38361,7 @@ local function PathRectXY(self, ax, ay, bx, by, rounding, flags) local rounding_tr = (bit32.band(flags, 32 --[[ImDrawFlags.RoundTopRight]]) ~= 0) and rounding or 0.0 local rounding_br = (bit32.band(flags, 128 --[[ImDrawFlags.RoundBottomRight]]) ~= 0) and rounding or 0.0 local rounding_bl = (bit32.band(flags, 64 --[[ImDrawFlags.RoundBottomLeft]]) ~= 0) and rounding or 0.0 - -- [Luau] inline PathArcToFast() (radius < 0.5 handled by _PathArcToFastExXY()) + -- [Luau] inline PathArcToFast() (small radius handled by _PathArcToFastExXY()) _PathArcToFastExXY(self, ax + rounding_tl, ay + rounding_tl, rounding_tl, 6 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 9 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) _PathArcToFastExXY(self, bx - rounding_tr, ay + rounding_tr, rounding_tr, 9 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 12 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) _PathArcToFastExXY(self, bx - rounding_br, by - rounding_br, rounding_br, 0 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 3 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) @@ -37949,19 +38373,654 @@ function MT.ImDrawList:PathRect(a, b, rounding, flags) PathRectXY(self, a.x, a.y, b.x, b.y, rounding, flags) end +-- [Luau] scalar _AddLine (p1 = p1x,p1y; p2 = p2x,p2y); _AddLine() below wraps it +@native +local function _AddLineXY(self, p1x, p1y, p2x, p2y, col, thickness, flags) + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + local screen_thickness = thickness * self._InvFringeScale + if screen_thickness < 1.0 then + if screen_thickness < 1.0 / 255.0 then + return + end + col = ImAlphaMultiply(col, screen_thickness) + screen_thickness = 1.0 + thickness = self._FringeScale + end + + flags = bit32.bor(flags, self.Flags) + local u0, v0, u1, v1, fringe = _SelectLineTexture(self, screen_thickness, flags) + + local dir_x = p2x - p1x + local dir_y = p2y - p1y + local d2 = dir_x * dir_x + dir_y * dir_y + local inv_len = 0.0 + if d2 > 0.0 then inv_len = 1.0 / math.sqrt(d2) end -- ImRsqrtPrecise() + dir_x = dir_x * inv_len + dir_y = dir_y * inv_len + local norm_x = -dir_y + local norm_y = dir_x + + local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) + + local stroke_pos = _GetStrokePos(flags, 262144 --[[ImDrawFlags.StrokeCenter]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + flags = bit32.band(bit32.bor(flags, 1024 --[[ImDrawFlags.JoinMiter]]), bit32.bnot(16384 --[[ImDrawFlags.AALineEnds]])) + end + + local thickness0 = (thickness + fringe) * 0.5 -- Center (or legacy) + if stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + thickness0 = thickness + fringe * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + thickness0 = _CalculateCenterBiasedOffset(self, thickness) + fringe * 0.5 + elseif stroke_pos == 131072 --[[ImDrawFlags.StrokeInside]] then + thickness0 = fringe * 0.5 + end + local thickness1 = (thickness + fringe) - thickness0 + + -- [Luau] uv2 (ratio) is unused by _AddLine(): not computed + + local half_aa = self._FringeScale * 0.5 -- Used for end caps, does not use "fringe" since end cap AA is not using the texture. + local use_aa_ends = bit32.band(flags, bit32.bor(8192 --[[ImDrawFlags.AALines]], 16384 --[[ImDrawFlags.AALineEnds]])) == bit32.bor(8192 --[[ImDrawFlags.AALines]], 16384 --[[ImDrawFlags.AALineEnds]]) + + if not use_aa_ends then + PrimReserve(self, 2 * 3, 4) + else + PrimReserve(self, 6 * 3, 8) + end + + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + local base_idx = self._VtxCurrentIdx + + if bit32.band(flags, 2048 --[[ImDrawFlags.CapSquare]]) ~= 0 then + local half_thickness = thickness * 0.5 + p1x = p1x - dir_x * half_thickness + p1y = p1y - dir_y * half_thickness + p2x = p2x + dir_x * half_thickness + p2y = p2y + dir_y * half_thickness + end + + -- Start cap + if not use_aa_ends then + WV(vb, vw + 0, p1x - norm_x * thickness0, p1y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 1, p1x + norm_x * thickness1, p1y + norm_y * thickness1, u1, v1, col) + WV(vb, vw + 2, p2x - norm_x * thickness0, p2y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 3, p2x + norm_x * thickness1, p2y + norm_y * thickness1, u1, v1, col) + + -- Connect + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + self._VtxWritePtr = vw + 4 + self._IdxWritePtr = iw + 6 + self._VtxCurrentIdx = base_idx + 4 + else + -- Start + local pa_x = p1x - dir_x * half_aa + local pa_y = p1y - dir_y * half_aa + local pb_x = p1x + dir_x * half_aa + local pb_y = p1y + dir_y * half_aa + + WV(vb, vw + 0, pa_x - norm_x * thickness0, pa_y - norm_y * thickness0, u0, v0, col_trans) + WV(vb, vw + 1, pa_x + norm_x * thickness1, pa_y + norm_y * thickness1, u1, v1, col_trans) + + WV(vb, vw + 2, pb_x - norm_x * thickness0, pb_y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 3, pb_x + norm_x * thickness1, pb_y + norm_y * thickness1, u1, v1, col) + + -- AA cap + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + base_idx = base_idx + 2 + + -- End + pa_x = p2x - dir_x * half_aa + pa_y = p2y - dir_y * half_aa + pb_x = p2x + dir_x * half_aa + pb_y = p2y + dir_y * half_aa + + WV(vb, vw + 4, pa_x - norm_x * thickness0, pa_y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 5, pa_x + norm_x * thickness1, pa_y + norm_y * thickness1, u1, v1, col) + WV(vb, vw + 6, pb_x - norm_x * thickness0, pb_y - norm_y * thickness0, u0, v0, col_trans) + WV(vb, vw + 7, pb_x + norm_x * thickness1, pb_y + norm_y * thickness1, u1, v1, col_trans) + + -- Connect + ib[iw + 6] = base_idx + 0; ib[iw + 7] = base_idx + 2; ib[iw + 8] = base_idx + 3 + ib[iw + 9] = base_idx + 0; ib[iw + 10] = base_idx + 3; ib[iw + 11] = base_idx + 1 + base_idx = base_idx + 2 + + -- AA cap + ib[iw + 12] = base_idx + 0; ib[iw + 13] = base_idx + 2; ib[iw + 14] = base_idx + 3 + ib[iw + 15] = base_idx + 0; ib[iw + 16] = base_idx + 3; ib[iw + 17] = base_idx + 1 + self._VtxWritePtr = vw + 8 + self._IdxWritePtr = iw + 18 + self._VtxCurrentIdx = base_idx + 4 + end +end + +function MT.ImDrawList:_AddLine(p1, p2, col, thickness, flags) + _AddLineXY(self, p1.x, p1.y, p2.x, p2.y, col, thickness, flags) +end + +--- @param p1 ImVec2 +--- @param p2 ImVec2 +--- @param col ImU32 +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddLine(p1, p2, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 262144 --[[ImDrawFlags.StrokeCenter]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + _AddLineXY(self, p1.x + 0.5, p1.y + 0.5, p2.x + 0.5, p2.y + 0.5, col, thickness, flags) + return + end + + _AddLineXY(self, p1.x, p1.y, p2.x, p2.y, col, thickness, flags) +end + +-- AddLineH(10, 20, 5, col, 1.0f) == AddLine( {10,5.5}, {20,5.5}, col, 1.0f) +-- == AddLine( {10,5}, {20,5}, col, 1.0f, ImDrawFlags_StrokeLegacy); // or before 1.93.0 +-- Thickness extend inside/bottom by default. Faster than equivalent AddLine() call. +--- @param min_x float +--- @param max_x float +--- @param y float +--- @param col ImU32 +--- @param thickness? float +--- @param flags? ImDrawFlags +@native +function MT.ImDrawList:AddLineH(min_x, max_x, y, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + _AddLineXY(self, min_x + 0.5, y + 0.5, max_x + 0.5, y + 0.5, col, thickness, flags) + return + end + + local top_y = y -- Inside + if stroke_pos == 262144 --[[ImDrawFlags.StrokeCenter]] then + top_y = top_y - thickness * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + top_y = top_y - _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + top_y = top_y - thickness + end + + local is_truncated = min_x % 1 == 0 and max_x % 1 == 0 and top_y % 1 == 0 and thickness % 1 == 0 -- IM_IS_TRUNCATED4() + if self._FringeScaleIsInteger and is_truncated and bit32.band(flags, 2048 --[[ImDrawFlags.CapSquare]]) == 0 then + if thickness < self._FringeScale then + if thickness < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, thickness * self._InvFringeScale) + thickness = self._FringeScale + end + + -- For pixel aligned case, use simple rectangle. + PrimReserve(self, 6, 4) + PrimRectXY(self, min_x, top_y, max_x, top_y + thickness, col) + else + -- Replace stroke pos with the already calculated one, as AddPolyline() default is different. + flags = bit32.bor(bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]])), stroke_pos) + + -- For generic case use line, since it needs less triangles than AA rectangle. + _AddLineXY(self, min_x, y, max_x, y, col, thickness, flags) + end +end + +-- AddLineV(5, 10, 20, col, 1.0f) == AddLine( {5.5,10}, {5.5,20}, col, 1.0f) +-- == AddLine( {5,10}, {5,20}, col, 1.0f, ImDrawFlags_StrokeLegacy); // or before 1.93.0 +-- Thickness extend inside/right by default. Faster than equivalent AddLine() call. +--- @param x float +--- @param min_y float +--- @param max_y float +--- @param col ImU32 +--- @param thickness? float +--- @param flags? ImDrawFlags +@native +function MT.ImDrawList:AddLineV(x, min_y, max_y, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + _AddLineXY(self, x + 0.5, min_y + 0.5, x + 0.5, max_y + 0.5, col, thickness, flags) + return + end + + local left_x = x -- Inside + if stroke_pos == 262144 --[[ImDrawFlags.StrokeCenter]] then + left_x = left_x - thickness * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + left_x = left_x - _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + left_x = left_x - thickness + end + + local is_truncated = left_x % 1 == 0 and min_y % 1 == 0 and max_y % 1 == 0 and thickness % 1 == 0 -- IM_IS_TRUNCATED4() + if self._FringeScaleIsInteger and is_truncated and bit32.band(flags, 2048 --[[ImDrawFlags.CapSquare]]) == 0 then + if thickness < self._FringeScale then + if thickness < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, thickness * self._InvFringeScale) + thickness = self._FringeScale + end + + -- For pixel aligned case, use simple rectangle. + PrimReserve(self, 6, 4) + PrimRectXY(self, left_x, min_y, left_x + thickness, max_y, col) + else + -- Replace stroke pos with the already calculated one, as AddPolyline() default is different. + flags = bit32.bor(bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]])), stroke_pos) + + -- For generic case use line, since it needs less triangles than AA rectangle. + -- Drawing for bottom to top, so that the inside stroke pos expands to right. + _AddLineXY(self, x, max_y, x, min_y, col, thickness, flags) + end +end + +-- [Port] TODO baked round corners: _AddRectBaked() (UseTexForRoundCorners) not ported yet, AddRect() falls back to the geometry path. + +local corner_offset_x = { 1, -1, -1, 1 } -- static const ImVec2 corner_offset[4] = { {1,1}, {-1,1}, {-1,-1}, {1,-1} }; +local corner_offset_y = { 1, 1, -1, -1 } +local corner_flags = { ImDrawFlags.RoundTopLeft, ImDrawFlags.RoundTopRight, ImDrawFlags.RoundBottomRight, ImDrawFlags.RoundBottomLeft } +local corner_pos_x, corner_pos_y = { 0, 0, 0, 0 }, { 0, 0, 0, 0 } -- [Luau] scratch + +-- Draws rounded rectangle where thickness > rounding. If rendered using the regular polyline, the stroke will fold and leave artifacts on the corners if rendered with transparency. +-- The stroke is positioned inside the rectangle. +--- @param p_min ImVec2 +--- @param p_max ImVec2 +--- @param col ImU32 +--- @param rounding float +--- @param thickness float +--- @param flags ImDrawFlags +function MT.ImDrawList:_AddRectTinyRounding(p_min, p_max, col, rounding, thickness, flags) + local screen_thickness = thickness * self._InvFringeScale + if screen_thickness < 1.0 then + if screen_thickness < 1.0 / 255.0 then + return + end + col = ImAlphaMultiply(col, screen_thickness) + screen_thickness = 1.0 + thickness = self._FringeScale + end + + flags = bit32.bor(flags, self.Flags) + local outer_u, outer_v, inner_u, inner_v, fringe = _SelectLineTexture(self, screen_thickness, flags) + + local auto_seg_count = self:_CalcCircleAutoSegmentCount(rounding) + local arc_step, arc_step_count = CalcCornerArcStepAndCount(auto_seg_count) + local arc_point_count = arc_step_count + 1 + + local vtx_count = (2 + arc_point_count) * 4 + 3 + local idx_count = (4 + arc_point_count - 1) * 4 * 3 + + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local start_vtx_ptr = self._VtxWritePtr + local start_idx_ptr = self._IdxWritePtr + local vw, iw = start_vtx_ptr, start_idx_ptr + local vtx_idx = self._VtxCurrentIdx + local first_vtx_idx = vtx_idx -- [Luau] VtxBuffer position of index 'n' is start_vtx_ptr + (n - first_vtx_idx) + + local half_fringe = fringe * 0.5 + corner_pos_x[1] = p_min.x - half_fringe; corner_pos_y[1] = p_min.y - half_fringe + corner_pos_x[2] = p_max.x + half_fringe; corner_pos_y[2] = p_min.y - half_fringe + corner_pos_x[3] = p_max.x + half_fringe; corner_pos_y[3] = p_max.y + half_fringe + corner_pos_x[4] = p_min.x - half_fringe; corner_pos_y[4] = p_max.y + half_fringe + thickness = thickness + fringe + rounding = rounding + half_fringe + + local stem_offset = ImMin(rounding, thickness) -- The function might get called with rounding slightly bigger than thickness, which is ok, but we should keep the stem inside the shape. + local ratio = stem_offset / thickness + local stem_u, stem_v = outer_u + (inner_u - outer_u) * ratio, outer_v + + -- Previous vertices, copied from the last vertices. + local base_idx = vtx_idx + WV(vb, vw + 0, 0, 0, inner_u, inner_v, col) + WV(vb, vw + 1, 0, 0, stem_u, stem_v, col) + WV(vb, vw + 2, 0, 0, outer_u, outer_v, col) + vw = vw + 3; vtx_idx = vtx_idx + 3 + + local arc_fast_vtx = self._Data.ArcFastVtx -- 1-based + for corner = 1, 4 do + local outer_x = corner_pos_x[corner] + local outer_y = corner_pos_y[corner] + local cox, coy = corner_offset_x[corner], corner_offset_y[corner] + local stem_x = outer_x + cox * stem_offset + local stem_y = outer_y + coy * stem_offset + local inner_x = outer_x + cox * thickness + local inner_y = outer_y + coy * thickness + local is_rounded = bit32.band(flags, corner_flags[corner]) ~= 0 + + local arc_idx = vtx_idx + if is_rounded then + local center_x = outer_x + cox * rounding + local center_y = outer_y + coy * rounding + local a = (48 --[[IM_DRAWLIST_ARCFAST_TABLE_SIZE]] / 4) * (corner - 1) + for _ = 0, arc_point_count - 1 do + local s = arc_fast_vtx[a + 1] + WV(vb, vw, center_x - s.x * rounding, center_y - s.y * rounding, outer_u, outer_v, col) + vw = vw + 1; vtx_idx = vtx_idx + 1 + a = (a + arc_step) % 48 --[[IM_DRAWLIST_ARCFAST_TABLE_SIZE]] + end + else + WV(vb, vw, outer_x, outer_y, outer_u, outer_v, col) + vw = vw + 1; vtx_idx = vtx_idx + 1 + end + + local stem_idx = vtx_idx + WV(vb, vw, stem_x, stem_y, stem_u, stem_v, col) + local inner_idx = vtx_idx + 1 + WV(vb, vw + 1, inner_x, inner_y, inner_u, inner_v, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Arc + if is_rounded then + for i = 0, arc_point_count - 2 do + ib[iw + 0] = stem_idx; ib[iw + 1] = arc_idx + i; ib[iw + 2] = arc_idx + i + 1 + iw = iw + 3 + end + end + + -- Connect with previous + ib[iw + 0] = base_idx + 0; ib[iw + 1] = arc_idx; ib[iw + 2] = stem_idx + ib[iw + 3] = base_idx + 0; ib[iw + 4] = stem_idx; ib[iw + 5] = base_idx + 1 + ib[iw + 6] = base_idx + 1; ib[iw + 7] = stem_idx; ib[iw + 8] = inner_idx + ib[iw + 9] = base_idx + 1; ib[iw + 10] = inner_idx; ib[iw + 11] = base_idx + 2 + iw = iw + 12 + + base_idx = stem_idx - 1 + end + + -- Close + -- [Luau] start_vtx_ptr[0..2] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 0..2] (3 vertices of 36 bytes) + buffer.copy(vb, (start_vtx_ptr - 1) * 36, vb, (start_vtx_ptr + (base_idx - first_vtx_idx) - 1) * 36, 3 * 36) + + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = vtx_idx + + local idx_used = iw - start_idx_ptr + local vtx_used = vw - start_vtx_ptr + IM_ASSERT_PARANOID(idx_used <= idx_count and vtx_used <= vtx_count) + if idx_used < idx_count or vtx_used < vtx_count then + self:PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used) + end +end + +local prim_points = { ImVec2(), ImVec2(), ImVec2(), ImVec2() } -- [Luau] scratch for C++ 'const ImVec2 points[N]' locals (callees only read them) + +-- p_min = upper-left, p_max = lower-right +--- @param p_min ImVec2 +--- @param p_max ImVec2 +--- @param col ImU32 +--- @param rounding? float +--- @param thickness? float +--- @param flags? ImDrawFlags +@native +function MT.ImDrawList:AddRect(p_min, p_max, col, rounding, thickness, flags) + if rounding == nil then rounding = 0.0 end + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + -- If this assert triggers on legacy code: + -- - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. + -- - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. + -- - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundXXX values. + -- If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags! + -- - Hard coded support for ~0 == ImDrawFlags_RoundAll. + -- - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. + -- - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundNone or use 'float rounding = 0.0f'. + -- See "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. + IM_ASSERT_USER_ERROR_RET(bit32.band(flags, 2147483663 --[[ImDrawFlags.InvalidMask_]]) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?") -- Or misuse of legacy hard-coded ImDrawCornerFlags values + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + if bit32.band(flags, 8192 --[[ImDrawFlags.AALines]]) ~= 0 then + PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.50, p_max.y - 0.50, rounding, flags) -- [Luau] scalar math, no temporary ImVec2 + else + PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.49, p_max.y - 0.49, rounding, flags) -- Better looking lower-right corner and rounded non-AA shapes. + end + self:PathStroke(col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], 262144 --[[ImDrawFlags.StrokeCenter]], bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]])))) + return + end + + local fringe_scale = self._FringeScale + local has_rounding = rounding >= 0.25 * fringe_scale -- IM_HAS_ROUNDING(rounding) + + -- Calculate the outer boundary of the rectangle based on the stroke position. + local offset = 0.0 -- Inside + if stroke_pos == 262144 --[[ImDrawFlags.StrokeCenter]] then + offset = thickness * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + offset = _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + offset = thickness + end + local outer_min_x = p_min.x - offset + local outer_min_y = p_min.y - offset + local outer_max_x = p_max.x + offset + local outer_max_y = p_max.y + offset + local outer_rounding = rounding + (has_rounding and offset or 0.0) + + local width = outer_max_x - outer_min_x + local height = outer_max_y - outer_min_y + local half_min_dim = ImMin(width, height) * 0.5 + if half_min_dim <= thickness then + -- Rectangle has collapsed into filled rectangle. + self:AddRectFilled(ImVec2(outer_min_x, outer_min_y), ImVec2(outer_max_x, outer_max_y), col, outer_rounding, flags) + return + end + + if bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 0 then + flags = bit32.bor(flags, 240 --[[ImDrawFlags.RoundAll]]) + end + if has_rounding then + -- Constrain rounding to rect dimensions + outer_rounding = ImMin(outer_rounding, ImAbs(width) * (((bit32.band(flags, 48 --[[ImDrawFlags.RoundTop]]) == 48 --[[ImDrawFlags.RoundTop]]) or (bit32.band(flags, 192 --[[ImDrawFlags.RoundBottom]]) == 192 --[[ImDrawFlags.RoundBottom]])) and 0.5 or 1.0) - 1.0) + outer_rounding = ImMin(outer_rounding, ImAbs(height) * (((bit32.band(flags, 80 --[[ImDrawFlags.RoundLeft]]) == 80 --[[ImDrawFlags.RoundLeft]]) or (bit32.band(flags, 160 --[[ImDrawFlags.RoundRight]]) == 160 --[[ImDrawFlags.RoundRight]])) and 0.5 or 1.0) - 1.0) + end + + local is_truncated = outer_min_x % 1 == 0 and outer_min_y % 1 == 0 and outer_max_x % 1 == 0 and outer_max_y % 1 == 0 and outer_rounding % 1 == 0 and thickness % 1 == 0 -- IM_IS_TRUNCATED4() x2 + if self._FringeScaleIsInteger and is_truncated then + local s_rounding = ImTrunc64(outer_rounding * self._InvFringeScale) + local s_thickness = ImTrunc64(thickness * self._InvFringeScale) + if s_thickness <= 0 then + return + end + + if s_rounding <= 0 or bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]] then + -- Pixel aligned non-rounded rect. + local opaque_uv = self._Data.TexUvWhitePixel + local u, v = opaque_uv.x, opaque_uv.y + local t = s_thickness * fringe_scale + + PrimReserve(self, 8 * 3, 8) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + local idx = self._VtxCurrentIdx + WV(vb, vw + 0, outer_min_x, outer_min_y, u, v, col) + WV(vb, vw + 1, outer_min_x + t, outer_min_y + t, u, v, col) + WV(vb, vw + 2, outer_max_x, outer_min_y, u, v, col) + WV(vb, vw + 3, outer_max_x - t, outer_min_y + t, u, v, col) + WV(vb, vw + 4, outer_max_x, outer_max_y, u, v, col) + WV(vb, vw + 5, outer_max_x - t, outer_max_y - t, u, v, col) + WV(vb, vw + 6, outer_min_x, outer_max_y, u, v, col) + WV(vb, vw + 7, outer_min_x + t, outer_max_y - t, u, v, col) + + ib[iw + 0] = idx + 0; ib[iw + 1] = idx + 3; ib[iw + 2] = idx + 1 + ib[iw + 3] = idx + 0; ib[iw + 4] = idx + 2; ib[iw + 5] = idx + 3 + + ib[iw + 6] = idx + 2; ib[iw + 7] = idx + 5; ib[iw + 8] = idx + 3 + ib[iw + 9] = idx + 2; ib[iw + 10] = idx + 4; ib[iw + 11] = idx + 5 + + ib[iw + 12] = idx + 4; ib[iw + 13] = idx + 7; ib[iw + 14] = idx + 5 + ib[iw + 15] = idx + 4; ib[iw + 16] = idx + 6; ib[iw + 17] = idx + 7 + + ib[iw + 18] = idx + 6; ib[iw + 19] = idx + 1; ib[iw + 20] = idx + 7 + ib[iw + 21] = idx + 6; ib[iw + 22] = idx + 0; ib[iw + 23] = idx + 1 + + self._VtxWritePtr = vw + 8 + self._IdxWritePtr = iw + 24 + self._VtxCurrentIdx = idx + 8 + return + end + -- Textured corners are baked with AA, do not use them if no-AA is requested. + -- [Port] TODO baked round corners: allow_tex_corners -> _AddRectBaked(), falls back to the geometry path below. + end + + if not has_rounding or bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]] then + local points = prim_points + points[1].x = outer_min_x; points[1].y = outer_min_y + points[2].x = outer_max_x; points[2].y = outer_min_y + points[3].x = outer_max_x; points[3].y = outer_max_y + points[4].x = outer_min_x; points[4].y = outer_max_y + self:_AddPolyline(points, 4, col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], 131072 --[[ImDrawFlags.StrokeInside]]), half_min_dim) + self._Path.Size = 0 + else + -- Switch to specialized rendering a bit early to avoid overdraw visible in transparent shapes. + if thickness + (0.75 * fringe_scale) > outer_rounding then + -- Special case rendering to avoid rendering artifacts at the corners. + -- If rendered using the regular polyline, the stroke will fold and leave artifacts on the corners if rendered with transparency. + self:_AddRectTinyRounding(ImVec2(outer_min_x, outer_min_y), ImVec2(outer_max_x, outer_max_y), col, outer_rounding, thickness, flags) + return + end + local rounding_tl = (bit32.band(flags, 16 --[[ImDrawFlags.RoundTopLeft]]) ~= 0) and outer_rounding or 0.0 + local rounding_tr = (bit32.band(flags, 32 --[[ImDrawFlags.RoundTopRight]]) ~= 0) and outer_rounding or 0.0 + local rounding_br = (bit32.band(flags, 128 --[[ImDrawFlags.RoundBottomRight]]) ~= 0) and outer_rounding or 0.0 + local rounding_bl = (bit32.band(flags, 64 --[[ImDrawFlags.RoundBottomLeft]]) ~= 0) and outer_rounding or 0.0 + -- [Luau] inline PathArcToFast() + _PathArcToFastExXY(self, outer_min_x + rounding_tl, outer_min_y + rounding_tl, rounding_tl, 6 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 9 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) + _PathArcToFastExXY(self, outer_max_x - rounding_tr, outer_min_y + rounding_tr, rounding_tr, 9 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 12 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) + _PathArcToFastExXY(self, outer_max_x - rounding_br, outer_max_y - rounding_br, rounding_br, 0 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 3 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) + _PathArcToFastExXY(self, outer_min_x + rounding_bl, outer_max_y - rounding_bl, rounding_bl, 3 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 6 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) + + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], 131072 --[[ImDrawFlags.StrokeInside]], bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]]))), half_min_dim) + self._Path.Size = 0 + end +end + +-- [Port] TODO baked round corners: _AddRectFilledBaked() (UseTexForRoundCorners) not ported yet, AddRectFilled() falls back to the geometry path. + +--- @param p_min ImVec2 +--- @param p_max ImVec2 +--- @param col ImU32 +--- @param rounding? float +--- @param flags? ImDrawFlags @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 - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end - if rounding < 0.5 or (bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]]) then + local ax, ay, bx, by = p_min.x, p_min.y, p_max.x, p_max.y + local width = bx - ax + local height = by - ay + local min_dim = ImMin(width, height) + flags = bit32.bor(flags, self.Flags) + + if bit32.band(flags, 917504 --[[ImDrawFlags.StrokeMask_]]) == 917504 --[[ImDrawFlags.StrokeLegacy]] and rounding < 0.5 then PrimReserve(self, 6, 4) -- [Luau] local calls - PrimRectXY(self, p_min.x, p_min.y, p_max.x, p_max.y, col) + PrimRectXY(self, ax, ay, bx, by, col) + return + end + + -- The rect is smaller than pixel in one dimension. + local fringe_scale = self._FringeScale + if min_dim < fringe_scale then + if min_dim < (1.0 / 255.0) * fringe_scale then + return + end + if width < fringe_scale then + col = ImAlphaMultiply(col, width * self._InvFringeScale) + width = fringe_scale + end + if height < fringe_scale then + col = ImAlphaMultiply(col, height * self._InvFringeScale) + height = fringe_scale + end + local points = prim_points + points[1].x = ax; points[1].y = ay + points[2].x = ax + width; points[2].y = ay + points[3].x = ax + width; points[3].y = ay + height + points[4].x = ax; points[4].y = ay + height + self:AddConvexPolyFilled(points, 4, col, 1024 --[[ImDrawFlags.JoinMiter]]) + return + end + + -- When rounding non-integer and under threshold, has_rounding may be false but still affect is_truncated. Likely not worth catering for. + local has_rounding = bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) ~= 256 --[[ImDrawFlags.RoundNone]] and rounding >= 0.25 * fringe_scale -- IM_HAS_ROUNDING(rounding) + local is_truncated = ax % 1 == 0 and ay % 1 == 0 and bx % 1 == 0 and by % 1 == 0 and rounding % 1 == 0 -- IM_IS_TRUNCATED4() && IM_IS_TRUNCATED() + + if self._FringeScaleIsInteger and is_truncated then + -- Fast path for the non-AA non-rounded rect. + if not has_rounding then + PrimReserve(self, 6, 4) + PrimRectXY(self, ax, ay, bx, by, col) + return + end + + if bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 0 then + flags = bit32.bor(flags, 240 --[[ImDrawFlags.RoundAll]]) + end + + rounding = ImMin(rounding, ImAbs(width) * (((bit32.band(flags, 48 --[[ImDrawFlags.RoundTop]]) == 48 --[[ImDrawFlags.RoundTop]]) or (bit32.band(flags, 192 --[[ImDrawFlags.RoundBottom]]) == 192 --[[ImDrawFlags.RoundBottom]])) and 0.5 or 1.0) - 1.0) + rounding = ImMin(rounding, ImAbs(height) * (((bit32.band(flags, 80 --[[ImDrawFlags.RoundLeft]]) == 80 --[[ImDrawFlags.RoundLeft]]) or (bit32.band(flags, 160 --[[ImDrawFlags.RoundRight]]) == 160 --[[ImDrawFlags.RoundRight]])) and 0.5 or 1.0) - 1.0) + + local s_rounding = ImTrunc64(rounding * self._InvFringeScale) + if s_rounding <= 0 then + PrimReserve(self, 6, 4) + PrimRectXY(self, ax, ay, bx, by, col) + return + end + -- Textured corners are baked with AA, do not use them if no-AA is requested. + -- [Port] TODO baked round corners: allow_tex_corners -> _AddRectFilledBaked(), falls back to the geometry path below. + end + + if not has_rounding then + local points = prim_points + points[1].x = ax; points[1].y = ay + points[2].x = bx; points[2].y = ay + points[3].x = bx; points[3].y = by + points[4].x = ax; points[4].y = by + self:AddConvexPolyFilled(points, 4, col, 1024 --[[ImDrawFlags.JoinMiter]]) else - PathRectXY(self, p_min.x, p_min.y, p_max.x, p_max.y, rounding, flags) - self:PathFillConvex(col) + PathRectXY(self, ax, ay, bx, by, rounding, flags) + self:PathFillConvex(col, 1024 --[[ImDrawFlags.JoinMiter]]) end end @@ -37994,189 +39053,353 @@ function MT.ImDrawList:AddRectFilledMultiColor(p_min, p_max, col_upr_left, col_u self._VtxCurrentIdx = idx + 4 end --- p_min = upper-left, p_max = lower-right ---- @param p_min ImVec2 ---- @param p_max ImVec2 ---- @param col ImU32 ---- @param rounding float --- @param thickness? float --- @param flags? ImDrawFlags -@native -function MT.ImDrawList:AddRect(p_min, p_max, col, rounding, thickness, flags) +function MT.ImDrawList:AddQuad(p1, p2, p3, p4, col, thickness, flags) if thickness == nil then thickness = 1.0 end if flags == nil then flags = 0 end - -- If this assert triggers on legacy code: - -- - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. - -- - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. - -- - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundXXX values. - -- If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags! - -- - Hard coded support for ~0 == ImDrawFlags_RoundAll. - -- - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. - -- - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundNone or use 'float rounding = 0.0f'. - -- See "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. - IM_ASSERT_USER_ERROR_RET(bit32.band(flags, 2147483663 --[[ImDrawFlags.InvalidMask_]]) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?") -- Or misuse of legacy hard-coded ImDrawCornerFlags values - - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then - return - end - -- [Luau] square corners + whole-pixel thickness: 4 filled rects instead of a stroked path (same pixels, far cheaper here) - if (rounding == nil or rounding < 0.5 or bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]]) - and thickness >= 1.0 and thickness % 1 == 0 and p_max.x - p_min.x > 2 * thickness and p_max.y - p_min.y > 2 * thickness then - local x0, y0, x1, y1, t = p_min.x, p_min.y, p_max.x, p_max.y, thickness - PrimReserve(self, 24, 16) - PrimRectXY(self, x0, y0, x1, y0 + t, col) - PrimRectXY(self, x0, y1 - t, x1, y1, col) - PrimRectXY(self, x0, y0 + t, x0 + t, y1 - t, col) - PrimRectXY(self, x1 - t, y0 + t, x1, y1 - t, col) - return - end - if bit32.band(self.Flags, 8192 --[[ImDrawFlags.AALines]]) ~= 0 then - PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.50, p_max.y - 0.50, rounding, flags) -- [Luau] scalar math, no temporary ImVec2 - else - PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.49, p_max.y - 0.49, rounding, flags) -- Better looking lower-right corner and rounded non-AA shapes. - end - - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) -end - -function MT.ImDrawList:AddLine(p1, p2, col, thickness) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - - PathLineToXY(self, p1.x + 0.5, p1.y + 0.5) -- [Luau] scalar math, no temporary ImVec2 - PathLineToXY(self, p2.x + 0.5, p2.y + 0.5) - self:PathStroke(col, thickness) -end - ---- @param min_x float ---- @param max_x float ---- @param y float ---- @param col ImU32 ---- @param thickness? float -function MT.ImDrawList:AddLineH(min_x, max_x, y, col, thickness) - if thickness == nil then thickness = 1.0 end - - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then - return - end - PathLineToXY(self, min_x + 0.5, y + 0.5) - PathLineToXY(self, max_x + 0.5, y + 0.5) - self:PathStroke(col, thickness) -end - ---- @param x float ---- @param min_y float ---- @param max_y float ---- @param col ImU32 ---- @param thickness? float -function MT.ImDrawList:AddLineV(x, min_y, max_y, col, thickness) - if thickness == nil then thickness = 1.0 end - - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then - return - end - PathLineToXY(self, x + 0.5, min_y + 0.5) - PathLineToXY(self, x + 0.5, max_y + 0.5) - self:PathStroke(col, thickness) -end - -function MT.ImDrawList:AddTriangle(p1, p2, p3, col, thickness) if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) -end + --flags |= Flags; // Not needed. + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + flags = bit32.bor(bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]])), stroke_pos) -- As AddPolyline() default is different -function MT.ImDrawList:AddQuad(p1, p2, p3, p4, col, thickness) - if thickness == nil then thickness = 1.0 end - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathLineTo(p4) - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3); ImVec2_Copy(points[4], p4) + self:_AddPolyline(points, 4, col, thickness, bit32.bor(flags, 512 --[[ImDrawFlags.Closed]]), 3.4028234663853e+38 --[[FLT_MAX]]) end function MT.ImDrawList:AddQuadFilled(p1, p2, p3, p4, col) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathLineTo(p4) - self:PathFillConvex(col) + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3); ImVec2_Copy(points[4], p4) + self:AddConvexPolyFilled(points, 4, col) +end + +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddTriangle(p1, p2, p3, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + --flags |= Flags; // Not needed. + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + flags = bit32.bor(bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]])), stroke_pos) -- As AddPolyline() default is different + + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3) + self:_AddPolyline(points, 3, col, thickness, bit32.bor(flags, 512 --[[ImDrawFlags.Closed]]), 3.4028234663853e+38 --[[FLT_MAX]]) end function MT.ImDrawList:AddTriangleFilled(p1, p2, p3, col) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathFillConvex(col) -end - ---- @param center ImVec2 ---- @param radius float ---- @param col ImU32 ---- @param num_segments int ---- @param thickness float -function MT.ImDrawList:AddCircle(center, radius, col, num_segments, thickness) - if num_segments == nil then num_segments = 0 end - if thickness == nil then thickness = 1.0 end - - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or radius < 0.5 then + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - if num_segments <= 0 then + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3) + self:AddConvexPolyFilled(points, 3, col) +end + +--- @param center ImVec2 +--- @param radius float +--- @param col ImU32 +--- @param num_segments? int +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddCircle(center, radius, col, num_segments, thickness, flags) + if num_segments == nil then num_segments = 0 end + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + radius = radius - 0.5 + stroke_pos = 262144 --[[ImDrawFlags.StrokeCenter]] + end + radius = ImMax(0.01, radius) + + local outer_radius = radius + if stroke_pos == 262144 --[[ImDrawFlags.StrokeCenter]] then + outer_radius = radius + thickness * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + outer_radius = radius + _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == 131072 --[[ImDrawFlags.StrokeInside]] then + outer_radius = radius + elseif stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + outer_radius = radius + thickness + end + + if outer_radius <= thickness then + -- The circle has collapsed into a filled circle. + self:AddCircleFilled(center, outer_radius, col, num_segments) + return + end + + if num_segments <= 0 and outer_radius <= self._Data.ArcFastRadiusCutoff then -- Use arc with automatic segment count - _PathArcToFastExXY(self, center.x, center.y, radius - 0.5, 0, 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]], 0) + local a_step = math.floor(48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / self:_CalcCircleAutoSegmentCount(outer_radius)) -- Use outer_radius for segment count to be consistent with rounded rect. + _PathArcToFastExXY(self, center.x, center.y, radius, 0, 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]], a_step) self._Path.Size = self._Path.Size - 1 else - -- Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) + -- Explicit segment count or above fast arc cutoff (still clamp to avoid drawing insanely tessellated shapes) + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(outer_radius) + end num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1) / num_segments - self:PathArcTo(center, radius - 0.5, 0.0, a_max, num_segments - 1) + local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments + self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) end - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) + if self._Path.Size >= 3 then + -- Calculate distance to the edge of the polygon describing the circle. + -- This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. + local unit_apothem = ApproxApothem(self._Path.Size) + local edge_dist = radius * unit_apothem + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], stroke_pos), edge_dist) + end + self._Path.Size = 0 end ---- @param center ImVec2 ---- @param radius float ---- @param col ImU32 ---- @param num_segments int +--- @param center ImVec2 +--- @param radius float +--- @param col ImU32 +--- @param num_segments? int function MT.ImDrawList:AddCircleFilled(center, radius, col, num_segments) if num_segments == nil then num_segments = 0 end - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or radius < 0.5 then + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - if num_segments <= 0 then + local diameter = radius * 2.0 + if diameter < self._FringeScale then + if diameter < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, diameter * self._InvFringeScale) + radius = self._FringeScale * 0.5 + end + + if num_segments <= 0 and radius <= self._Data.ArcFastRadiusCutoff then -- Use arc with automatic segment count _PathArcToFastExXY(self, center.x, center.y, radius, 0, 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]], 0) self._Path.Size = self._Path.Size - 1 else - -- Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) + -- Explicit segment count or above cutoff (still clamp to avoid drawing insanely tessellated shapes) + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(radius) + end num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1) / num_segments + local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) end + self:PathFillConvex(col, 1024 --[[ImDrawFlags.JoinMiter]]) +end + +-- Guaranteed to honor 'num_segments' +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddNgon(center, radius, col, num_segments, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or num_segments <= 2 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + radius = radius - 0.5 + stroke_pos = 262144 --[[ImDrawFlags.StrokeCenter]] + end + radius = ImMax(0.01, radius) + + -- Calculate distance to the edge of the polygon describing the circle. + -- This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. + local unit_apothem = ApproxApothem(num_segments) + local edge_dist = radius * unit_apothem + + -- Check overestimate first before testing details. + if radius < thickness * 2.0 then + local outer_radius = radius + if stroke_pos == 262144 --[[ImDrawFlags.StrokeCenter]] then + outer_radius = radius + thickness * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + outer_radius = radius + _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == 131072 --[[ImDrawFlags.StrokeInside]] then + outer_radius = radius + elseif stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + outer_radius = radius + thickness + end + if outer_radius * unit_apothem <= thickness then + -- The polygon has collapsed into a filled polygon. + self:AddNgonFilled(center, outer_radius, col, num_segments) + return + end + end + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments + self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], stroke_pos), edge_dist) + self._Path.Size = 0 +end + +-- Guaranteed to honor 'num_segments' +function MT.ImDrawList:AddNgonFilled(center, radius, col, num_segments) + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or num_segments <= 2 then + return + end + + local diameter = radius * 2.0 + if diameter < self._FringeScale then + if diameter < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, diameter * self._InvFringeScale) + radius = self._FringeScale * 0.5 + end + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments + self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) + self:PathFillConvex(col, 1024 --[[ImDrawFlags.JoinMiter]]) +end + +-- Ellipse +--- @param rot? float +--- @param num_segments? int +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddEllipse(center, radius, col, rot, num_segments, thickness, flags) + if rot == nil then rot = 0.0 end + if num_segments == nil then num_segments = 0 end + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + stroke_pos = 262144 --[[ImDrawFlags.StrokeCenter]] + end + + local rad = ImVec2(radius.x, radius.y) + rad.x = ImMax(0.01, rad.x) + rad.y = ImMax(0.01, rad.y) + + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(ImMax(rad.x, rad.y)) -- A bit pessimistic, maybe there's a better computation to do here. + end + + self._Path:reserve(self._Path.Size + (num_segments + 1)) + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = 3.141592653589793 --[[IM_PI]] * 2.0 * (num_segments - 1.0) / num_segments + self:PathEllipticalArcTo(center, rad, rot, 0.0, a_max, num_segments - 1) + + -- Prevent the inside part of the stroke to be expanded too much to prevent some rendering artifacts. + local max_inner_offset = ImMin(rad.x, rad.y) + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], stroke_pos), max_inner_offset) + self._Path.Size = 0 +end + +--- @param rot? float +--- @param num_segments? int +function MT.ImDrawList:AddEllipseFilled(center, radius, col, rot, num_segments) + if rot == nil then rot = 0.0 end + if num_segments == nil then num_segments = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + local rad = ImVec2(radius.x, radius.y) + local diameter_x = rad.x * 2.0 + local diameter_y = rad.y * 2.0 + local min_dim = ImMin(diameter_x, diameter_y) + + -- The ellipse is smaller than pixel in one dimension. + if min_dim < self._FringeScale then + if min_dim < (1.0 / 255.0) * self._FringeScale then + return + end + if diameter_x < self._FringeScale then + col = ImAlphaMultiply(col, diameter_x * self._InvFringeScale) + rad.x = self._FringeScale * 0.5 + end + if diameter_y < self._FringeScale then + col = ImAlphaMultiply(col, diameter_y * self._InvFringeScale) + rad.y = self._FringeScale * 0.5 + end + end + + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)) -- A bit pessimistic, maybe there's a better computation to do here. + end + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = 3.141592653589793 --[[IM_PI]] * 2.0 * (num_segments - 1.0) / num_segments + self:PathEllipticalArcTo(center, radius, rot, 0.0, a_max, num_segments - 1) -- [Port] upstream passes 'radius' (not 'rad') here, kept as-is self:PathFillConvex(col) end +-- Cubic Bezier takes 4 controls points +--- @param num_segments? int +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments, flags) + if num_segments == nil then num_segments = 0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + self:PathLineTo(p1) + self:PathBezierCubicCurveTo(p2, p3, p4, num_segments) + self:PathStroke(col, thickness, flags) +end + +-- Quadratic Bezier takes 3 controls points +--- @param num_segments? int +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddBezierQuadratic(p1, p2, p3, col, thickness, num_segments, flags) + if num_segments == nil then num_segments = 0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + self:PathLineTo(p1) + self:PathBezierQuadraticCurveTo(p2, p3, num_segments) + self:PathStroke(col, thickness, flags) +end + --- @param font ImFont --- @param font_size float --- @param pos ImVec2 @@ -38266,6 +39489,7 @@ function MT.ImDrawList:AddImageRounded(tex_ref, p_min, p_max, uv_min, uv_max, co if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end + if flags == nil then flags = 0 end assert(bit32.band(flags, 0x0F) == 0, "Misuse of legacy hardcoded ImDrawCornerFlags values!") -- If this assert triggers on legacy code: see comments in ImDrawList:PathRect(). if bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 0 then @@ -38284,7 +39508,7 @@ function MT.ImDrawList:AddImageRounded(tex_ref, p_min, p_max, uv_min, uv_max, co local vert_start_idx = self.VtxBuffer.Size + 1 self:PathRect(p_min, p_max, rounding, flags) - self:PathFillConvex(col) + self:PathFillConvex(col, 1024 --[[ImDrawFlags.JoinMiter]]) local vert_end_idx = self.VtxBuffer.Size + 1 ImGui.ShadeVertsLinearUV(self, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true) @@ -38354,19 +39578,21 @@ end -- [Luau] scalar center (cx, cy); assigned to the forward-declared local, _PathArcToFastEx() wraps it @native function _PathArcToFastExXY(self, cx, cy, radius, a_min_sample, a_max_sample, a_step) - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) PathLineToXY(self, cx, cy) return end -- Calculate arc auto segment step size if a_step <= 0 then - a_step = math.floor(48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / self:_CalcCircleAutoSegmentCount(radius)) + -- Calculate step size using the common function. No need to clamp, the step is already in range. + local auto_seg_count = self:_CalcCircleAutoSegmentCount(radius) + a_step = CalcCornerArcStepAndCount(auto_seg_count) + else + -- Make sure we never do steps larger than one quarter of the circle + a_step = ImClamp(a_step, 1, math.floor(48 --[[IM_DRAWLIST_ARCFAST_TABLE_SIZE]] / 4)) end - -- Make sure we never do steps larger than one quarter of the circle - a_step = ImClamp(a_step, 1, math.floor(48 --[[IM_DRAWLIST_ARCFAST_TABLE_SIZE]] / 4)) - local sample_range = ImAbs(a_max_sample - a_min_sample) local a_next_step = a_step @@ -38458,7 +39684,7 @@ end -- 0: East, 3: South, 6: West, 9: North, 12: East @native function MT.ImDrawList:PathArcToFast(center, radius, a_min_of_12, a_max_of_12) - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) self._Path:push_back(center) return end @@ -38472,7 +39698,7 @@ end --- @param a_max float --- @param num_segments int function MT.ImDrawList:_PathArcToN(center, radius, a_min, a_max, num_segments) - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) self._Path:push_back(center) return end @@ -38500,7 +39726,7 @@ end function MT.ImDrawList:PathArcTo(center, radius, a_min, a_max, num_segments) if num_segments == nil then num_segments = 0 end - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) self._Path:push_back(center) return end @@ -38514,13 +39740,21 @@ function MT.ImDrawList:PathArcTo(center, radius, a_min, a_max, num_segments) if radius <= self._Data.ArcFastRadiusCutoff then local a_is_reverse = a_max < a_min + -- Calculate number of segments per circle. + -- First we calculate the step count as usual, then match to the precomputed directions. + -- This will give us the spacing between the samples (step), and how many segments to use in total for a full circle. + local auto_seg_count = self:_CalcCircleAutoSegmentCount(radius) + local arc_step, arc_step_count = CalcCornerArcStepAndCount(auto_seg_count) + local arc_seg_count = arc_step_count * 4 -- Seg count for full circle. + -- We are going to use precomputed values for mid samples. -- Determine first and last sample in lookup table that belong to the arc. - local a_min_sample_f = 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] * a_min / (3.141592653589793 --[[IM_PI]] * 2.0) - local a_max_sample_f = 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] * a_max / (3.141592653589793 --[[IM_PI]] * 2.0) + -- In first step we use the coarser segment count, and after rounding, convert back to the precomputed array index. + local a_min_sample_f = arc_seg_count * a_min / (3.141592653589793 --[[IM_PI]] * 2.0) + local a_max_sample_f = arc_seg_count * a_max / (3.141592653589793 --[[IM_PI]] * 2.0) - local a_min_sample = a_is_reverse and ImFloor(a_min_sample_f) or ImCeil(a_min_sample_f) - local a_max_sample = a_is_reverse and ImCeil(a_max_sample_f) or ImFloor(a_max_sample_f) + local a_min_sample = (a_is_reverse and ImFloor(a_min_sample_f) or ImCeil(a_min_sample_f)) * arc_step + local a_max_sample = (a_is_reverse and ImCeil(a_max_sample_f) or ImFloor(a_max_sample_f)) * arc_step local a_mid_samples = a_is_reverse and ImMax(a_min_sample - a_max_sample, 0) or ImMax(a_max_sample - a_min_sample, 0) local a_min_segment_angle = a_min_sample * 3.141592653589793 --[[IM_PI]] * 2.0 / 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] @@ -38531,7 +39765,7 @@ function MT.ImDrawList:PathArcTo(center, radius, a_min, a_max, num_segments) self._Path:reserve(self._Path.Size + (a_mid_samples + 1 + (a_emit_start and 1 or 0) + (a_emit_end and 1 or 0))) if a_emit_start then PathLineToXY(self, center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius) end -- [Luau] no temporary ImVec2 - if a_mid_samples > 0 then _PathArcToFastExXY(self, center.x, center.y, radius, a_min_sample, a_max_sample, 0) end + if a_mid_samples > 0 then _PathArcToFastExXY(self, center.x, center.y, radius, a_min_sample, a_max_sample, arc_step) end if a_emit_end then PathLineToXY(self, center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius) end else local arc_length = ImAbs(a_max - a_min) @@ -38565,7 +39799,8 @@ function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_b if y > clip_rect.w then return end - if bit32.band(draw_list.Flags, 1048576 --[[ImDrawFlags.TextNoPixelSnap]]) == 0 then + flags = bit32.bor(flags, draw_list.Flags) + if bit32.band(flags, 1048576 --[[ImDrawFlags.TextNoPixelSnap]]) == 0 then x = IM_TRUNC(x) y = IM_TRUNC(y) end @@ -39082,9 +40317,8 @@ function ImGui.RenderRectFilledWithHole(draw_list, outer, inner, col, rounding) end ------------------------------------------------------------------------------- --- Extra primitives (Ngon, Ellipse, Bezier, concave fill) +-- Extra path functions (elliptical arc, bezier curves) and concave fill ------------------------------------------------------------------------------- --- [port] These follow the 1.92 stroker this port uses (no ImDrawFlags_StrokeXXX placement flags). function MT.ImDrawList:PathEllipticalArcTo(center, radius, rot, a_min, a_max, num_segments) if num_segments == nil or num_segments <= 0 then @@ -39167,63 +40401,6 @@ function MT.ImDrawList:PathBezierQuadraticCurveTo(p2, p3, num_segments) end end --- Guaranteed to honor 'num_segments' -function MT.ImDrawList:AddNgon(center, radius, col, num_segments, thickness) - if thickness == nil then thickness = 1.0 end - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or num_segments <= 2 then return end - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments - self:PathArcTo(center, radius - 0.5, 0.0, a_max, num_segments - 1) - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) -end - --- Guaranteed to honor 'num_segments' -function MT.ImDrawList:AddNgonFilled(center, radius, col, num_segments) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or num_segments <= 2 then return end - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments - self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) - self:PathFillConvex(col) -end - --- Ellipse -function MT.ImDrawList:AddEllipse(center, radius, col, rot, num_segments, thickness) - if rot == nil then rot = 0.0 end - if thickness == nil then thickness = 1.0 end - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - if num_segments == nil or num_segments <= 0 then num_segments = self:_CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)) end -- A bit pessimistic, maybe there's a better computation to do here. - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = 3.141592653589793 --[[IM_PI]] * 2.0 * (num_segments - 1.0) / num_segments - self:PathEllipticalArcTo(center, radius, rot, 0.0, a_max, num_segments - 1) - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) -end - -function MT.ImDrawList:AddEllipseFilled(center, radius, col, rot, num_segments) - if rot == nil then rot = 0.0 end - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - if num_segments == nil or num_segments <= 0 then num_segments = self:_CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)) end -- A bit pessimistic, maybe there's a better computation to do here. - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = 3.141592653589793 --[[IM_PI]] * 2.0 * (num_segments - 1.0) / num_segments - self:PathEllipticalArcTo(center, radius, rot, 0.0, a_max, num_segments - 1) - self:PathFillConvex(col) -end - --- Cubic Bezier takes 4 controls points -function MT.ImDrawList:AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - self:PathLineTo(p1) - self:PathBezierCubicCurveTo(p2, p3, p4, num_segments or 0) - self:PathStroke(col, thickness or 1.0) -end - --- Quadratic Bezier takes 3 controls points -function MT.ImDrawList:AddBezierQuadratic(p1, p2, p3, col, thickness, num_segments) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - self:PathLineTo(p1) - self:PathBezierQuadraticCurveTo(p2, p3, num_segments or 0) - self:PathStroke(col, thickness or 1.0) -end - -- ponytail: ear-clipping, no anti-aliased fringe (upstream's AA triangulator not ported). O(n^2) is fine for UI polygons. --- @param points ImVec2[] # 1-based function MT.ImDrawList:AddConcavePolyFilled(points, points_count, col) diff --git a/build/imgui_love.linemap.json b/build/imgui_love.linemap.json index 25bd06b..3fda8d9 100644 --- a/build/imgui_love.linemap.json +++ b/build/imgui_love.linemap.json @@ -1 +1 @@ -[[183, "imgui.lua"], [21598, "imgui_demo.lua"], [33436, "imgui_draw.lua"], [39416, "imgui_h.lua"], [42322, "imgui_impl_love.lua"], [42862, "imgui_internal.lua"], [46571, "imgui_tables.lua"], [51630, "imgui_widgets.lua"], [63619, "imstb_rectpack.lua"], [64283, "imstb_textedit.lua"], [65825, "imstb_truetype.lua"], [68613, "imstd_minstdio.lua"]] \ No newline at end of file +[[183, "imgui.lua"], [21598, "imgui_demo.lua"], [33450, "imgui_draw.lua"], [40593, "imgui_h.lua"], [43499, "imgui_impl_love.lua"], [44039, "imgui_internal.lua"], [47748, "imgui_tables.lua"], [52807, "imgui_widgets.lua"], [64796, "imstb_rectpack.lua"], [65460, "imstb_textedit.lua"], [67002, "imstb_truetype.lua"], [69790, "imstd_minstdio.lua"]] \ No newline at end of file diff --git a/build/imgui_love.lua b/build/imgui_love.lua index 2fa76fb..7266561 100644 --- a/build/imgui_love.lua +++ b/build/imgui_love.lua @@ -138,7 +138,7 @@ if not utf8 then end -local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImGuiKey, ImDrawFlags, ImGuiInputFlags, ImRect_Copy, ImVec2_CopyV, ImGuiDataType, ImStb, IM_TRUNC, ImGuiStyleVarInfo, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, IM_ASSERT_USER_ERROR_RET, ImHashStr, ImS32, ImGuiColorEditFlags, ImIsPowerOfTwo, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, ImAbs, ImU16, ExampleNames, ImGuiWindowFlags, ImGuiItemFlags, stbte, ImColor, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ExampleTreeFuncs, ImCos, ImGuiListClipper, ImS16, ImGuiMultiSelectFlags, ImGuiTabBarFlags, ImSin, ImUpperPowerOfTwo, ImVec2_MulVX, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, ImGuiMouseCursor, DockSettingsRenameNodeReferences, ExampleDualListBoxMT, IM_ASSERT_PARANOID, ImCeil, ImGuiCol, ImGuiSelectionBasicStorage, ImLengthSqr, ImS8, ImU8, _PathArcToFastExXY, ExampleAppPropertyEditorMT, ImFabs, ImGuiScrollFlags, ImGui_ImplLOVE_GetBackendData, ImLog, ExampleSelectionWithDeletionMT, CustomConstraints, ExampleAppLogMT, IMGUI_DEBUG_LOG_FONT, ImFontAtlasBuildInit, ImGuiComboFlags, ImGuiPopupFlags, ImGuiSliderFlags, ImTextClassifierSetCharClass, ImVec2_AddVA, ImVec2_SubVA, MyDocument, ShowExampleMenuFile, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiStyle_Copy, ExampleAsset_s_current_sort_specs, ExampleMemberInfo, ExampleTree_CreateNode, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImFontAtlasTextureAdd, ImGuiTableColumn, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, IM_FREE, ImDrawListSplitter, stb_text_locate_coord, stb_text_makeundo_insert, stb_text_makeundo_replace, stb_textedit_find_charpos, stbtt_GetGlyphShape, ImGuiTableColumnSortSpecs, ImGuiTableInstanceData, ImGuiTableSettings, stb_text_makeundo_delete, stb_text_redo, stb_text_undo, stb_textedit_click, stb_textedit_drag, stbtt_FindGlyphIndex, stbtt_GetFontOffsetForIndex, stbtt_GetFontVMetrics, stbtt_GetGlyphBitmapBox, stbtt_GetGlyphHMetrics, stbtt_InitFont, stbtt_MakeGlyphBitmapSubpixelPrefilter, stbtt_ScaleForPixelHeight, stbtt__GetGlyphInfoT2, stbtt__GetGlyphShapeT2, stbtt_fontinfo, ImGuiOldColumnData, ImGuiOldColumnFlags, ImGuiOldColumns, ImGuiTable, ImGuiTableColumnSettings, ImGuiTableSortSpecs, ImGuiTableTempData, ImGui_ImplLOVE_CreateTexture, ImGui_ImplLOVE_DestroyTexture, ImGui_ImplLOVE_Init, ImGui_ImplLOVE_NewFrame, ImGui_ImplLOVE_RenderDrawData, ImGui_ImplLOVE_Shutdown, ImGui_ImplLOVE_UpdateTexture, stbrp_init_target, IMGUI_TABLE_MAX_COLUMNS, IMSTB_TEXTEDIT_UNDOSTATECOUNT, IM_COL32_DISABLE, ImGui_ImplLove, STB_TEXTEDIT_K_BACKSPACE, STB_TEXTEDIT_K_DELETE, STB_TEXTEDIT_K_DOWN, STB_TEXTEDIT_K_LEFT, STB_TEXTEDIT_K_LINEEND, STB_TEXTEDIT_K_LINESTART, STB_TEXTEDIT_K_PGDOWN, STB_TEXTEDIT_K_PGUP, STB_TEXTEDIT_K_REDO, STB_TEXTEDIT_K_RIGHT, STB_TEXTEDIT_K_SHIFT, STB_TEXTEDIT_K_TEXTEND, STB_TEXTEDIT_K_TEXTSTART, STB_TEXTEDIT_K_UNDO +local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImGuiKey, ImDrawFlags, ImGuiInputFlags, ImRect_Copy, ImGuiDataType, ImStb, IM_TRUNC, ImVec2_CopyV, ImGuiStyleVarInfo, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, IM_ASSERT_USER_ERROR_RET, ImHashStr, ImS32, ImGuiColorEditFlags, ImIsPowerOfTwo, ImAbs, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, ImU16, ExampleNames, ImGuiWindowFlags, ImGuiItemFlags, stbte, ImColor, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ExampleTreeFuncs, IM_ASSERT_PARANOID, ImCos, ImGuiListClipper, ImS16, _PathArcToFastExXY, ImGuiMultiSelectFlags, ImGuiTabBarFlags, ImSin, ImUpperPowerOfTwo, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, ImGuiMouseCursor, DockSettingsRenameNodeReferences, ExampleDualListBoxMT, IM_DRAWLIST_TEX_LINES_WIDTH_MAX, ImCeil, ImGuiCol, ImGuiSelectionBasicStorage, ImLengthSqr, ImS8, ImU8, ExampleAppPropertyEditorMT, ImFabs, ImGuiScrollFlags, ImGui_ImplLOVE_GetBackendData, ImLog, ExampleSelectionWithDeletionMT, CustomConstraints, ExampleAppLogMT, ImFontAtlasBuildInit, ImGuiComboFlags, ImGuiPopupFlags, ImGuiSliderFlags, ImTextClassifierSetCharClass, MyDocument, ShowExampleMenuFile, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiStyle_Copy, ExampleAsset_s_current_sort_specs, ExampleMemberInfo, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImFontAtlasTextureAdd, ImFontAtlasTextureBlockQueueUpload, ImFontBaked_BuildGrowIndex, ImFontConfig, ImFontGlyph, ImGuiTableColumn, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, IM_FREE, ImDrawListSplitter, ImGuiDragDropFlags, stb_text_locate_coord, stb_text_makeundo_insert, stb_text_makeundo_replace, stb_textedit_find_charpos, stbtt_GetGlyphShape, ImGuiTableColumnSortSpecs, ImGuiTableInstanceData, ImGuiTableSettings, stb_text_makeundo_delete, stb_text_redo, stb_text_undo, stb_textedit_click, stb_textedit_drag, stbtt_FindGlyphIndex, stbtt_GetFontOffsetForIndex, stbtt_GetFontVMetrics, stbtt_GetGlyphBitmapBox, stbtt_GetGlyphHMetrics, stbtt_InitFont, stbtt_MakeGlyphBitmapSubpixelPrefilter, stbtt_ScaleForPixelHeight, stbtt__GetGlyphInfoT2, stbtt__GetGlyphShapeT2, stbtt_fontinfo, ImGuiOldColumnData, ImGuiOldColumnFlags, ImGuiOldColumns, ImGuiTable, ImGuiTableColumnSettings, ImGuiTableSortSpecs, ImGuiTableTempData, ImGui_ImplLOVE_CreateTexture, ImGui_ImplLOVE_DestroyTexture, ImGui_ImplLOVE_Init, ImGui_ImplLOVE_NewFrame, ImGui_ImplLOVE_RenderDrawData, ImGui_ImplLOVE_Shutdown, ImGui_ImplLOVE_UpdateTexture, stbrp_init_target, IMGUI_TABLE_MAX_COLUMNS, IMSTB_TEXTEDIT_UNDOSTATECOUNT, IM_COL32_DISABLE, ImGui_ImplLove, STB_TEXTEDIT_K_BACKSPACE, STB_TEXTEDIT_K_DELETE, STB_TEXTEDIT_K_DOWN, STB_TEXTEDIT_K_LEFT, STB_TEXTEDIT_K_LINEEND, STB_TEXTEDIT_K_LINESTART, STB_TEXTEDIT_K_PGDOWN, STB_TEXTEDIT_K_PGUP, STB_TEXTEDIT_K_REDO, STB_TEXTEDIT_K_RIGHT, STB_TEXTEDIT_K_SHIFT, STB_TEXTEDIT_K_TEXTEND, STB_TEXTEDIT_K_TEXTSTART local type, mathMin, mathMax, mathFloor, mathCeil, bitOr, bitLShift = type, math.min, math.max, math.floor, math.ceil, bit32.bor, bit32.lshift local function IM_COL32(R, G, B, A) return (bitOr(bitLShift(A, 24 --[[IM_COL32_A_SHIFT]]), bitLShift(B, 16 --[[IM_COL32_B_SHIFT]]), bitLShift(G, 8 --[[IM_COL32_G_SHIFT]]), bitLShift(R, 0 --[[IM_COL32_R_SHIFT]]))) end @@ -32010,6 +32010,9 @@ S.ShowExampleAppCustomRendering = { curve_segments_override_v = 8, colf_stroke = ImVec4(1.000, 0.384, 0.169, 1.000), colf_fill = ImVec4(0.416, 0.378, 0.420, 1.000), + prim_stroke_flags = ImDrawFlags.None, + prim_other_flags = ImDrawFlags.AALineEnds, + scope_flags = nil, -- First init copy current state (see below). -- Canvas points = ImVector(), scrolling = ImVec2(0.0, 0.0), @@ -32093,25 +32096,35 @@ function ShowExampleAppCustomRendering(p_open) ImGui.ColorEdit4("Stroke Color", s.colf_stroke) ImGui.ColorEdit4("Fill Color", s.colf_fill) - -- [Port] ImDrawFlags_StrokeInside/StrokeCenter/StrokeCenterBiased/StrokeOutside/StrokeLegacy, AAFill/AALines/AALineEnds, - -- [Port] JoinMiter/CapSquare, AllowInPushScope_ and ImDrawList::PushDrawFlag()/PopDrawFlag() don't exist in this port - -- [Port] (1.92 stroker): prim_stroke_flags/prim_other_flags/scope_flags and their widgets are not ported, flags = None. ImGui.SeparatorText("Per primitive flags (AddXXX functions)") - ImGui.TextDisabled("(not supported by this port)") -- [Port] + _, s.prim_stroke_flags = ImGui.RadioButton("Default", s.prim_stroke_flags, 0 --[[ImDrawFlags.None]]); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Inside", s.prim_stroke_flags, 131072 --[[ImDrawFlags.StrokeInside]]); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Center", s.prim_stroke_flags, 262144 --[[ImDrawFlags.StrokeCenter]]); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("CenterBiased", s.prim_stroke_flags, 393216 --[[ImDrawFlags.StrokeCenterBiased]]); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Outside", s.prim_stroke_flags, 524288 --[[ImDrawFlags.StrokeOutside]]); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Legacy", s.prim_stroke_flags, 917504 --[[ImDrawFlags.StrokeLegacy]]) + _, s.prim_other_flags = ImGui.CheckboxFlags("AAFill", s.prim_other_flags, 4096 --[[ImDrawFlags.AAFill]]); ImGui.SameLine() -- Note: Only for functions taking flags. + _, s.prim_other_flags = ImGui.CheckboxFlags("AALines", s.prim_other_flags, 8192 --[[ImDrawFlags.AALines]]); ImGui.SameLine() -- Note: Only for functions taking flags. + _, s.prim_other_flags = ImGui.CheckboxFlags("AALineEnds", s.prim_other_flags, 16384 --[[ImDrawFlags.AALineEnds]]); ImGui.SameLine() + _, s.prim_other_flags = ImGui.CheckboxFlags("JoinMiter", s.prim_other_flags, 1024 --[[ImDrawFlags.JoinMiter]]); ImGui.SameLine() + _, s.prim_other_flags = ImGui.CheckboxFlags("CapSquare", s.prim_other_flags, 2048 --[[ImDrawFlags.CapSquare]]) --ImGui.CheckboxFlags("UseTexForRoundCorners", &prim_other_flags, ImDrawFlags_UseTexForRoundCorners); + if s.scope_flags == nil then s.scope_flags = bit32.band(draw_list.Flags, 8286704 --[[ImDrawFlags.AllowInPushScope_]]) end -- First init copy current state. ImGui.SeparatorText("Current ImDrawList flags (e.g. PushDrawFlag function)") ImGui.PushID("ScopeFlags") - ImGui.TextDisabled("(not supported by this port)") -- [Port] + _, s.scope_flags = ImGui.CheckboxFlags("AAFill", s.scope_flags, 4096 --[[ImDrawFlags.AAFill]]); ImGui.SameLine() + _, s.scope_flags = ImGui.CheckboxFlags("AALines", s.scope_flags, 8192 --[[ImDrawFlags.AALines]]); ImGui.SameLine() + _, s.scope_flags = ImGui.CheckboxFlags("AALineEnds", s.scope_flags, 16384 --[[ImDrawFlags.AALineEnds]]); ImGui.SameLine() + _, s.scope_flags = ImGui.CheckboxFlags("StrokeLegacy", s.scope_flags, 917504 --[[ImDrawFlags.StrokeLegacy]]) --ImGui.CheckboxFlags("UseTexForRoundCorners", &scope_flags, ImDrawFlags_UseTexForRoundCorners); ImGui.PopID() ImGui.Spacing() - -- [Port] draw_list->PushDrawFlag(ImDrawFlags_AllowInPushScope_, false); // Reset all existing (very unusual: done for this demo) - -- [Port] draw_list->PushDrawFlag(scope_flags, true); - local flags = 0 --[[ImDrawFlags.None]] -- [Port] prim_stroke_flags | prim_other_flags + draw_list:PushDrawFlag(8286704 --[[ImDrawFlags.AllowInPushScope_]], false) -- Reset all existing (very unusual: done for this demo) + draw_list:PushDrawFlag(s.scope_flags, true) + local flags = bit32.bor(s.prim_stroke_flags, s.prim_other_flags) - -- [Port] In this port AddNgon/AddCircle/AddEllipse/AddTriangle/AddLine*/AddBezier*/PathFillConvex take no flags argument (dropped below). local sz = s.sz local start_pos = ImGui.GetCursorScreenPos() local pi = 3.141592 @@ -32179,7 +32192,7 @@ function ShowExampleAppCustomRendering(p_open) for n = 0, 4 - 1 do draw_list:PathLineTo(ImVec2(x + half_sz + rotating_square[n + 1].x, y + half_sz + rotating_square[n + 1].y)) end - draw_list:PathFillConvex(col) -- [Port] no flags argument + draw_list:PathFillConvex(col, flags) x = x + step -- Path @@ -32206,14 +32219,14 @@ function ShowExampleAppCustomRendering(p_open) -- First row uses a thickness of 1.0f, second row uses the configurable thickness local col = ImColor(s.colf_stroke):U32() local th = (row == 0) and 1.0 or thickness - draw_list:AddNgon(ImVec2(x + half_sz, y + half_sz), half_sz, col, s.ngon_segments, th); x = x + step -- N-gon - draw_list:AddCircle(ImVec2(x + half_sz, y + half_sz), half_sz, col, circle_segments, th); x = x + step -- Circle - draw_list:AddEllipse(ImVec2(x + half_sz, y + half_sz), ImVec2(half_sz, sz * 0.3), col, -0.3, circle_segments, th); x = x + step -- Ellipse + draw_list:AddNgon(ImVec2(x + half_sz, y + half_sz), half_sz, col, s.ngon_segments, th, flags); x = x + step -- N-gon + draw_list:AddCircle(ImVec2(x + half_sz, y + half_sz), half_sz, col, circle_segments, th, flags); x = x + step -- Circle + draw_list:AddEllipse(ImVec2(x + half_sz, y + half_sz), ImVec2(half_sz, sz * 0.3), col, -0.3, circle_segments, th, flags); x = x + step -- Ellipse draw_list:AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0, th, flags); x = x + step -- Square draw_list:AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, flags); x = x + step -- Square with all rounded corners draw_list:AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, bit32.bor(flags, corners_tl_br)); x = x + step -- Square with two rounded corners - draw_list:AddTriangle(ImVec2(x + sz * 0.5, y), ImVec2(x + sz, y + sz), ImVec2(x, y + sz), col, th); x = x + step -- Triangle - draw_list:AddTriangle(ImVec2(x + sz * 0.2, y), ImVec2(x + sz * 0.4, y + sz), ImVec2(x, y + sz), col, th); x = x + step - math.floor(sz * 0.6) -- Thin triangle + draw_list:AddTriangle(ImVec2(x + sz * 0.5, y), ImVec2(x + sz, y + sz), ImVec2(x, y + sz), col, th, flags); x = x + step -- Triangle + draw_list:AddTriangle(ImVec2(x + sz * 0.2, y), ImVec2(x + sz * 0.4, y + sz), ImVec2(x, y + sz), col, th, flags); x = x + step - math.floor(sz * 0.6) -- Thin triangle -- Concave shape: U for _, p in ipairs(concave_shape) do @@ -32231,15 +32244,15 @@ function ShowExampleAppCustomRendering(p_open) -- Horizontal/Vertical/Diagonal lines local off = math.floor(sz * 0.4) - draw_list:AddLineH(x, x + sz, y, col, th) - draw_list:AddLine(ImVec2(x, y + off), ImVec2(x + sz, y + off), col, th) + draw_list:AddLineH(x, x + sz, y, col, th, flags) + draw_list:AddLine(ImVec2(x, y + off), ImVec2(x + sz, y + off), col, th, flags) x = x + step - draw_list:AddLineV(x, y, y + sz, col, th) - draw_list:AddLine(ImVec2(x + off, y + sz), ImVec2(x + off, y), col, th) + draw_list:AddLineV(x, y, y + sz, col, th, flags) + draw_list:AddLine(ImVec2(x + off, y + sz), ImVec2(x + off, y), col, th, flags) x = x + step - math.floor(half_sz) if not (draw_fill and draw_strokes) then -- -V560 - draw_list:AddLine(ImVec2(x, y), ImVec2(x + half_sz, y + sz), col, th) - draw_list:AddLine(ImVec2(x, y + sz), ImVec2(x + half_sz, y), col, th) + draw_list:AddLine(ImVec2(x, y), ImVec2(x + half_sz, y + sz), col, th, flags) + draw_list:AddLine(ImVec2(x, y + sz), ImVec2(x + half_sz, y), col, th, flags) end x = x + step - math.floor(half_sz) @@ -32258,12 +32271,12 @@ function ShowExampleAppCustomRendering(p_open) x = x + step -- Quadratic Bezier Curve (3 control points) - draw_list:AddBezierQuadratic(ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), ImVec2(x + cp3[3].x, y + cp3[3].y), col, th, curve_segments) + draw_list:AddBezierQuadratic(ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), ImVec2(x + cp3[3].x, y + cp3[3].y), col, th, curve_segments, flags) x = x + step -- Cubic Bezier Curve (4 control points) if not (draw_fill and draw_strokes) then -- -V560 - draw_list:AddBezierCubic(ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), ImVec2(x + cp4[4].x, y + cp4[4].y), col, th, curve_segments) + draw_list:AddBezierCubic(ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), ImVec2(x + cp4[4].x, y + cp4[4].y), col, th, curve_segments, flags) x = x + step end end @@ -32271,7 +32284,8 @@ function ShowExampleAppCustomRendering(p_open) end ImGui.Dummy(ImVec2(step * 15.5, step * 4.0)) - -- [Port] draw_list->PopDrawFlag(); x2 + draw_list:PopDrawFlag() + draw_list:PopDrawFlag() ImGui.Text("ImDrawList Vector Rendering Reference:") ImGui.TextLinkOpenURL("https://github.com/ocornut/imgui/wiki/Draw-List") @@ -33469,7 +33483,6 @@ local function WV(b, i, x, y, u, v, col) local o = (i - 1) * 36 writef64(b, o, x); writef64(b, o + 8, y); writef64(b, o + 16, u); writef64(b, o + 24, v); writeu32(b, o + 32, col) end -local function WVP(b, i, p, uv, col) WV(b, i, p.x, p.y, uv.x, uv.y, col) end local IM_FONTGLYPH_INDEX_UNUSED = 0xFFFF -- ((ImU16)-1) local IM_FONTGLYPH_INDEX_NOT_FOUND = 0xFFFE -- ((ImU16)-2) @@ -35386,8 +35399,9 @@ end --- @param atlas ImFontAtlas function ImFontAtlasBuildUpdateTexData(atlas) - ImFontAtlasBuildUpdateTexDataLines(atlas) ImFontAtlasBuildUpdateTexDataBasic(atlas) + ImFontAtlasBuildUpdateTexDataLines(atlas) + -- [Port] TODO baked round corners: ImFontAtlasBuildUpdateTexDataCorners(atlas) end -- Create a new texture, discard previous one @@ -35525,59 +35539,121 @@ function ImFontAtlasBuildUpdateTexDataLines(atlas) if add_and_draw then -- Register texture region for thick lines -- The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row - local pack_size = ImVec2(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) -- ImVec2i + local pack_size = ImVec2(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + 13 --[[IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT]]) -- ImVec2i builder.PackIdLinesTexData = atlas:AddCustomRect(pack_size.x, pack_size.y, r) assert(builder.PackIdLinesTexData ~= (-1) --[[ImFontAtlasRectId_Invalid]]) end - for n = 0, IM_DRAWLIST_TEX_LINES_WIDTH_MAX do -- +1 because of the zero-width row - -- Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - local y = n - local line_width = n + do + -- FIXME: Lines 0 to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH(4) are currently never used and could be ditched, saving DETAILED_WIDTH*WIDTH_MAX + for n = 0, IM_DRAWLIST_TEX_LINES_WIDTH_MAX do -- +1 because of the zero-width row + -- Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle + local y = n + local line_width = n + local pad_left = math.floor((r.w - line_width) / 2) + local pad_right = r.w - (pad_left + line_width) + assert(pad_left + line_width + pad_right == r.w and y < r.h) -- Make sure we're inside the texture bounds before we start writing pixels - --- @type int # If not floored, will cause black and purple horizontal stripes appearing on the right of the arrow(triangle) in the upper-left most area of the atlas - local pad_left = math.floor((r.w - line_width) / 2) - --- @type int - local pad_right = r.w - (pad_left + line_width) - - assert(pad_left + line_width + pad_right == r.w and y < r.h) -- Make sure we're inside the texture bounds before we start writing pixels - - -- Write each slice - if (add_and_draw and tex.Format == 1 --[[ImTextureFormat.Alpha8]]) then - local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) - - for i = 0, pad_left - 1 do - write_ptr[write_ptr_base + i] = 0x00 + -- Write each slice + if add_and_draw and tex.Format == 1 --[[ImTextureFormat.Alpha8]] then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + for i = 0, pad_left - 1 do + write_ptr[write_ptr_base + i] = 0x00 + end + for i = 0, line_width - 1 do + write_ptr[write_ptr_base + pad_left + i] = 0xFF + end + for i = 0, pad_right - 1 do + write_ptr[write_ptr_base + pad_left + line_width + i] = 0x00 + end + elseif add_and_draw and tex.Format == 0 --[[ImTextureFormat.RGBA32]] then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + for i = 0, pad_left - 1 do + write_u32(write_ptr, write_ptr_base + i * 4, IM_COL32(255, 255, 255, 0)) + end + for i = 0, line_width - 1 do + write_u32(write_ptr, write_ptr_base + (pad_left + i) * 4, 4294967295 --[[IM_COL32_WHITE]]) + end + for i = 0, pad_right - 1 do + write_u32(write_ptr, write_ptr_base + (pad_left + line_width + i) * 4, IM_COL32(255, 255, 255, 0)) + end end - for i = 0, line_width - 1 do - write_ptr[write_ptr_base + pad_left + i] = 0xFF - end + -- Refresh UV coordinates + local uv0_x, uv0_y = (r.x + pad_left - 0.5) * atlas.TexUvScale.x, (r.y + y) * atlas.TexUvScale.y + local uv1_x, uv1_y = (r.x + pad_left + line_width + 0.5) * atlas.TexUvScale.x, (r.y + y + 1) * atlas.TexUvScale.y + local half_v = (uv0_y + uv1_y) * 0.5 -- Calculate a constant V in the middle of the row to avoid sampling artifacts + -- Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. + local inv_line_width = (line_width > 0) and (1.0 / line_width) or 0.0 + builder.TexUvLines[n] = ImVec4(uv0_x, uv1_x, half_v, inv_line_width) + end + end - for i = 0, pad_right - 1 do - write_ptr[write_ptr_base + pad_left + line_width + i] = 0x00 - end - elseif (add_and_draw and tex.Format == 0 --[[ImTextureFormat.RGBA32]]) then - local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + do + -- Calculate super sampled line textures for smaller line widths to make transition between sizes smoother. - for i = 0, pad_left - 1 do - write_u32(write_ptr, write_ptr_base + i * 4, IM_COL32(255, 255, 255, 0)) - end - - for i = 0, line_width - 1 do - write_u32(write_ptr, write_ptr_base + (pad_left + i) * 4, 4294967295 --[[IM_COL32_WHITE]]) - end - - for i = 0, pad_right - 1 do - write_u32(write_ptr, write_ptr_base + (pad_left + line_width + i) * 4, IM_COL32(255, 255, 255, 0)) - end + -- Calculate ramp used for all the textures. + local ramp = {} -- ImU8[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT], 0-based + for n = 0, 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] - 1 do + ramp[n] = math.floor((n / 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]]) * 255.0) end - -- Refresh UV coordinates - local uv0 = ImVec2(r.x + pad_left - 1, r.y + y) * atlas.TexUvScale - local uv1 = ImVec2(r.x + pad_left + line_width + 1, r.y + y + 1) * atlas.TexUvScale - local half_v = (uv0.y + uv1.y) * 0.5 -- Calculate a constant V in the middle of the row to avoid sampling artifacts - atlas.TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v) + for n = 0, 13 --[[IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT]] - 1 do + local y = IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n + -- For integer thickness lines the one pixel line texture looks like this: + -- [ 0 | 1 | 0 ] + -- :...........: + -- Since the texture samples are at the center of the texel, we only use range above. + -- The result after bilinear filtering is a triangle across the used uv range. + -- + -- To super 2x sample that signal, we end up with following texture: + -- [ 0 | .5 | 1 | .5 | 0 ] + -- :.......................: + -- One might think that there should be 2 opaque pixels in the middle section, but no since we're super sampling the triangle signal. + local line_width = 1 + n + assert(4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] + line_width + 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] <= r.w and y < r.h) -- Make sure we're inside the texture bounds before we start writing pixels + + -- Write each slice + if add_and_draw and tex.Format == 1 --[[ImTextureFormat.Alpha8]] then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + local idx = 0 + for i = 0, 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] - 1 do + write_ptr[write_ptr_base + idx] = ramp[i]; idx = idx + 1 + end + for _ = 0, line_width - 1 do + write_ptr[write_ptr_base + idx] = 0xFF; idx = idx + 1 + end + for i = 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] - 1, 0, -1 do + write_ptr[write_ptr_base + idx] = ramp[i]; idx = idx + 1 + end + while idx < r.w do + write_ptr[write_ptr_base + idx] = 0x0; idx = idx + 1 + end + elseif add_and_draw and tex.Format == 0 --[[ImTextureFormat.RGBA32]] then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + local idx = 0 + for i = 0, 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] - 1 do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, ramp[i])); idx = idx + 1 + end + for _ = 0, line_width - 1 do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, 255)); idx = idx + 1 + end + for i = 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] - 1, 0, -1 do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, ramp[i])); idx = idx + 1 + end + while idx < r.w do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, 0)); idx = idx + 1 + end + end + + -- Refresh UV coordinates + local uv0_x, uv0_y = (r.x + 0.5) * atlas.TexUvScale.x, (r.y + y) * atlas.TexUvScale.y + local uv1_x, uv1_y = (r.x - 0.5 + 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] * 2 + line_width) * atlas.TexUvScale.x, (r.y + y + 1) * atlas.TexUvScale.y + local half_v = (uv0_y + uv1_y) * 0.5 -- Calculate a constant V in the middle of the row to avoid sampling artifacts + -- Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. + local inv_line_width = 1.0 / (1.0 + n / 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]]) + builder.TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n] = ImVec4(uv0_x, uv1_x, half_v, inv_line_width) + end end end @@ -35655,7 +35731,7 @@ function ImFontAtlasUpdateDrawListsSharedData(atlas) for _, shared_data in atlas.DrawListSharedDatas:iter() do if (shared_data.FontAtlas == atlas) then shared_data.TexUvWhitePixel = atlas.TexUvWhitePixel - shared_data.TexUvLines = atlas.TexUvLines + shared_data.TexUvLines = atlas.Builder.TexUvLines end end end @@ -37009,6 +37085,37 @@ local function IM_FIXNORMAL2F(VX, VY) return VX, VY end +-- - The miter limit describes how much the miter cross section can be scaled before we turn it into a bevel instead. +-- The minimum value of miter limit is 1.0, which translates to 180 (all corners are bevels), the higher the number, +-- the closer to zero the angle is (never bevel a corner). The miter limit angle can be calculated as: +-- miter_limit_angle = 2 * acos(1 / miter_limit) +-- - We can use the miter limit angle to figure maximum number of beveled corners in a convex polygon, +-- to prevent having allocate much extra memory (for a reasonable miter limit). +-- (n - 2) * 360 > k * miter_limit_angle + (n - k) * 360 +-- Where, n = number of corners in a polygon, k = beveled corners. Solved for k, simplifies to: +-- k < 360 / (180 - miter_limit_angle) +-- To find value strictly less than: +-- max_bevels = k = floor(360/(180-miter_limit_angle)) +local IM_POLYLINE_MITER_ANGLE_LIMIT = -0.9999619 -- Safeguard for miter corner calculation to avoid div by zero. cos(179.5) +local IM_POLYLINE_MITER_LIMIT = 4.0 -- (~29 deg corner) +local IM_POLYLINE_CONVEX_POLY_MAX_BEVELS = 2 -- For miter value 4.0 + +--- @param col ImU32 +--- @param alpha_mul float +local function ImAlphaMultiply(col, alpha_mul) + IM_ASSERT_PARANOID(alpha_mul >= 0.0 and alpha_mul < 1.0) -- We don't clamp! + local a = bit32.rshift(bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]), 24 --[[IM_COL32_A_SHIFT]]) + a = math.floor(a * alpha_mul) -- We don't need to clamp 0..255 because alpha is in 0..1 range. + return bit32.bor(bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])), bit32.lshift(a, 24 --[[IM_COL32_A_SHIFT]])) +end + +-- #define _GetStrokePos(_FLAGS, _DEFAULT_STROKE_POS) +local function _GetStrokePos(flags, default_stroke_pos) + local stroke_pos = bit32.band(flags, 917504 --[[ImDrawFlags.StrokeMask_]]) + if stroke_pos ~= 0 then return stroke_pos end + return default_stroke_pos +end + ------------------------------------------------------------------------------- -- [SECTION] ImDrawData ------------------------------------------------------------------------------- @@ -37060,6 +37167,25 @@ local function IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(_RAD, _MAXERROR) return ImCl local function IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(_N, _MAXERROR) return (_MAXERROR / (1 - ImCos(3.141592653589793 --[[IM_PI]] / ImMax(_N, 3.141592653589793 --[[IM_PI]])))) end local function IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_ERROR(_N, _RAD) return ((1 - ImCos(3.141592653589793 --[[IM_PI]] / ImMax(_N, 3.141592653589793 --[[IM_PI]]))) / _RAD) end +-- Calculates arc step and step count for 90 degree arc. The calculated arc step will complete a 90 degree arc when stepped arc_step_count times. +-- [Luau] returns arc_step, arc_step_count (C++ out-references) +local function CalcCornerArcStepAndCount(segment_count) + local quarter = 48 --[[IM_DRAWLIST_ARCFAST_TABLE_SIZE]] / 4 + local arc_step = ImClamp(math.floor(48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / segment_count), 1, quarter) + local arc_step_count = math.floor(quarter / arc_step) + if (arc_step * arc_step_count) ~= quarter then -- Make sure that the arc step can iterate 90 degree arc. + arc_step = math.floor(quarter / arc_step_count) + end + return arc_step, arc_step_count +end + +local function ApproxApothem(num_segments) + -- Calculates approximation (within 0.001%) distance from the center of the circumscribed circle to the edge of the polygon, cos(pi / n) + IM_ASSERT_PARANOID(num_segments >= 3) + local u = 1.0 / num_segments + return ((4.058712 * u * u) - 4.934802) * u * u + 1.0 +end + ------------------------------------------------------------------------------- -- [SECTION] ImDrawList ------------------------------------------------------------------------------- @@ -37528,101 +37654,6 @@ function MT.ImDrawList:ChannelsMerge() self._Splitter:Merge(self) end --- @param n int # 0-based function MT.ImDrawList:ChannelsSetCurrent(n) self._Splitter:SetCurrentChannel(self, n) end --- - We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds. --- - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. ---- @param points ImVec2[] ---- @param points_count int ---- @param col ImU32 - -function MT.ImDrawList:AddConvexPolyFilled(points, points_count, col) - if points_count < 3 or bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then - return - end - - local uv = self._Data.TexUvWhitePixel - - if bit32.band(self.Flags, 4096 --[[ImDrawFlags.AAFill]]) ~= 0 then - local AA_SIZE = self._FringeScale - local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) - local idx_count = (points_count - 2) * 3 + points_count * 6 - local vtx_count = points_count * 2 - self:PrimReserve(idx_count, vtx_count) - - local vtx_inner_idx = self._VtxCurrentIdx - local vtx_outer_idx = self._VtxCurrentIdx + 1 - local idx_data = self.IdxBuffer.Data - local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops - for i = 2, points_count - 1 do - idx_data[idx_write_ptr + 0] = vtx_inner_idx; idx_data[idx_write_ptr + 1] = vtx_inner_idx + ((i - 1) * 2); idx_data[idx_write_ptr + 2] = vtx_inner_idx + (i * 2) - idx_write_ptr = idx_write_ptr + 3 - end - - -- Compute normals - local temp_buffer = self._Data.TempBuffer - if points_count > temp_buffer.Capacity then temp_buffer:reserve_discard(points_count) end -- [Luau] skip the method lookup when no growth is needed - local temp_normals = self._Data.TempBuffer.Data - - local i0 = points_count - for i1 = 1, points_count do - local p0 = points[i0] - local p1 = points[i1] - local dx = p1.x - p0.x - local dy = p1.y - p0.y - dx, dy = IM_NORMALIZE2F_OVER_ZERO(dx, dy) - temp_normals[i0].x = dy - temp_normals[i0].y = -dx - - i0 = i1 - end - - local vtx_data = self.VtxBuffer.Buf - local vtx_write_ptr = self._VtxWritePtr - local uv_x, uv_y = uv.x, uv.y - i0 = points_count - for i1 = 1, points_count do - local n0 = temp_normals[i0] - local n1 = temp_normals[i1] - local dm_x = (n0.x + n1.x) * 0.5 - local dm_y = (n0.y + n1.y) * 0.5 - dm_x, dm_y = IM_FIXNORMAL2F(dm_x, dm_y) - dm_x = dm_x * AA_SIZE * 0.5 - dm_y = dm_y * AA_SIZE * 0.5 - - local p1 = points[i1] - WV(vtx_data, vtx_write_ptr + 0, p1.x - dm_x, p1.y - dm_y, uv_x, uv_y, col) - WV(vtx_data, vtx_write_ptr + 1, p1.x + dm_x, p1.y + dm_y, uv_x, uv_y, col_trans) - vtx_write_ptr = vtx_write_ptr + 2 - - idx_data[idx_write_ptr + 0] = vtx_inner_idx + ((i1 - 1) * 2); idx_data[idx_write_ptr + 1] = vtx_inner_idx + ((i0 - 1) * 2); idx_data[idx_write_ptr + 2] = vtx_outer_idx + ((i0 - 1) * 2) - idx_data[idx_write_ptr + 3] = vtx_outer_idx + ((i0 - 1) * 2); idx_data[idx_write_ptr + 4] = vtx_outer_idx + ((i1 - 1) * 2); idx_data[idx_write_ptr + 5] = vtx_inner_idx + ((i1 - 1) * 2) - idx_write_ptr = idx_write_ptr + 6 - - i0 = i1 - end - self._VtxWritePtr = vtx_write_ptr - self._IdxWritePtr = idx_write_ptr - self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count - else - local idx_count = (points_count - 2) * 3 - local vtx_count = points_count - self:PrimReserve(idx_count, vtx_count) - - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - for i = 1, vtx_count do - local vtx_write_ptr = self._VtxWritePtr - WVP(vtx_data, vtx_write_ptr + 0, points[i], uv, col) - self._VtxWritePtr = vtx_write_ptr + 1 - end - for i = 3, points_count do - local idx_write_ptr = self._IdxWritePtr - idx_data[idx_write_ptr + 0] = self._VtxCurrentIdx; idx_data[idx_write_ptr + 1] = self._VtxCurrentIdx + i - 2; idx_data[idx_write_ptr + 2] = self._VtxCurrentIdx + i - 1 - self._IdxWritePtr = idx_write_ptr + 3 - end - - self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count - end -end - function MT.ImDrawList:PushTexture(tex_ref) self._TextureStack:push_back(tex_ref) self._CmdHeader.TexRef = tex_ref @@ -37793,249 +37824,641 @@ function MT.ImDrawList:PrimRectUV(a, c, uv_a, uv_c, col) self._IdxWritePtr = idx_write_ptr + 6 end ---- @param points ImVec2[] ---- @param points_count int ---- @param col ImU32 ---- @param thickness float ---- @param flags ImDrawFlags ---- @param max_inner_offset float # [Luau] unused by the pre-1.93 stroker +--- @param thickness float +--- @return float +-- [Luau] local function (also MT.ImDrawList._CalculateCenterBiasedOffset) +local function _CalculateCenterBiasedOffset(self, thickness) + -- Old jumpy one. + -- return IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + + -- Calculate outside offset for stroke position ImDrawFlags_StrokeCenterBiased + -- so that one side the of the line is always at pixel boundary. + -- On integer thickness both sides of the line are on pixel boundary. + local screen_thickness = thickness * self._InvFringeScale + local s_thickness = math.floor(screen_thickness) -- (int) of a positive value + return (math.floor(s_thickness / 2) + (s_thickness % 2) * (screen_thickness - s_thickness)) * self._FringeScale +end +MT.ImDrawList._CalculateCenterBiasedOffset = _CalculateCenterBiasedOffset + +-- Check if R is considered to be worthy rounding. Can be only used within ImDrawList due to use of _FringeScale. +-- #define IM_HAS_ROUNDING(RAD) ((RAD) >= (0.25f * _FringeScale)) -- [Luau] written inline at each use + +-- Select texture and adjust fringe size for specified line thickness. +-- [Luau] returns uv0.x, uv0.y, uv1.x, uv1.y, fringe instead of writing to out parameters +--- @param screen_thickness float +--- @param flags ImDrawFlags +local function _SelectLineTexture(self, screen_thickness, flags) + -- We assume that the screen_thickness has been clamped to 1.0 by the caller. + IM_ASSERT_PARANOID(screen_thickness >= 1.0) + + if bit32.band(flags, 8192 --[[ImDrawFlags.AALines]]) ~= 0 then + -- Handle the thickness between 1.0 - IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. + -- We use super sampled textures in this range to make texture changes less noticeable. + -- There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT-1 maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). + local texture_idx + if screen_thickness <= 4 --[[IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]] then + texture_idx = math.floor((screen_thickness - 1.0) * 4 --[[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]] + 0.5) + (IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + elseif screen_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1.5 then + texture_idx = math.floor(screen_thickness + 0.5) + else + texture_idx = IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1 -- Catches large values of screen_thickness (and +inf/NaN) that a ImMin() wouldn't. + end + + -- Scale the fringe so that the texture matches the line thickness. + local tex_uvs = self._Data.TexUvLines[texture_idx] -- 0-based table + return tex_uvs.x, tex_uvs.z, tex_uvs.y, tex_uvs.z, self._FringeScale * screen_thickness * tex_uvs.w -- tex_uvs.w = 1 / thickness + else + -- For no anti-alias we use fully opaque texture, and no fringe expansion. + local uv = self._Data.TexUvWhitePixel + return uv.x, uv.y, uv.x, uv.y, 0.0 + end +end +MT.ImDrawList._SelectLineTexture = _SelectLineTexture + +-- [Luau] scratch arrays (normals x/y and squared segment lengths) used instead of _Data->TempBuffer: no ImVec2 per point +local poly_normals_x, poly_normals_y, poly_sqr_lengths = {}, {}, {} + +--- @param points ImVec2[] # 1-based +--- @param points_count int +--- @param col ImU32 +--- @param thickness float +--- @param flags ImDrawFlags +--- @param max_inner_offset float +-- [Luau] IM_APPEND_VTX()/IM_APPEND_TRI() are expanded inline: vertex/index write pointers and _VtxCurrentIdx kept in locals, stored back at the end function MT.ImDrawList:_AddPolyline(points, points_count, col, thickness, flags, max_inner_offset) - if points_count < 2 or bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - local closed = bit32.band(flags, 512 --[[ImDrawFlags.Closed]]) ~= 0 - local opaque_uv = self._Data.TexUvWhitePixel - local count = closed and points_count or points_count - 1 -- Number of line segments - local thick_line = thickness > self._FringeScale + local screen_thickness = thickness * self._InvFringeScale + if screen_thickness < 1.0 then + if screen_thickness < 1.0 / 255.0 then + return + end + col = ImAlphaMultiply(col, screen_thickness) + screen_thickness = 1.0 + thickness = self._FringeScale + end + -- If this assert triggers on legacy code: -- - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. -- - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. -- Read more details near AddRect() + see "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. + flags = bit32.bor(flags, self.Flags) IM_ASSERT_USER_ERROR_RET(bit32.band(flags, 2147483663 --[[ImDrawFlags.InvalidMask_]]) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?") - if bit32.band(self.Flags, 8192 --[[ImDrawFlags.AALines]]) ~= 0 then - -- Anti-aliased stroke - local AA_SIZE = self._FringeScale - local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) + -- Allocate data for temp buffers + local normals_x, normals_y, sqr_lengths = poly_normals_x, poly_normals_y, poly_sqr_lengths - -- Thicknesses <1.0 should behave like thickness 1.0 - thickness = ImMax(thickness, 1.0) - local integer_thickness = ImFloor(thickness) - local fractional_thickness = thickness - integer_thickness - - -- Do we want to draw this line using a texture? - -- [Luau] pre-1.93 stroker kept until the 1.93 one is ported. ImDrawListFlags_AntiAliasedLinesUseTex no longer exists (== AALines) - -- and was never set by this port's SetupDrawListSharedData() before, so the texture path stays disabled (unchanged output). - local use_texture = false - - IM_ASSERT_PARANOID((not use_texture) or bit32.band(self._Data.Font.OwnerAtlas.Flags, 4 --[[ImFontAtlasFlags.NoBakedLines]]) == 0) - - local idx_count = use_texture and (count * 6) or (thick_line and count * 18 or count * 12) - local vtx_count = use_texture and (points_count * 2) or (thick_line and points_count * 4 or points_count * 3) - PrimReserve(self, idx_count, vtx_count) - - -- Temporary buffer - local temp_buffer_size = points_count * ((use_texture or not thick_line) and 3 or 5) - local temp_buffer = self._Data.TempBuffer - if temp_buffer_size > temp_buffer.Capacity then temp_buffer:reserve_discard(temp_buffer_size) end -- [Luau] skip the method lookup when no growth is needed - local temp_normals = self._Data.TempBuffer.Data - local temp_points = self._Data.TempBuffer.Data; local temp_points_start = points_count + 1 - - -- Calculate normals for each line segment - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local p1 = points[i1] - local p2 = points[i2] - local dx = p2.x - p1.x - local dy = p2.y - p1.y - dx, dy = IM_NORMALIZE2F_OVER_ZERO(dx, dy) - temp_normals[i1].x = dy - temp_normals[i1].y = -dx - end - if not closed then - ImVec2_Copy(temp_normals[points_count], temp_normals[points_count - 1]) - end - - -- If we are drawing a one-pixel-wide line without a texture, or a textured line of any width - if use_texture or not thick_line then - -- [PATH 1] Texture-based lines (thick or non-thick) - -- [PATH 2] Non texture-based lines (non-thick) - local half_draw_size = use_texture and (thickness * 0.5 + 1) or AA_SIZE - - -- If line is not closed, the first and last points need to be generated differently - if not closed then - ImVec2_CopyV(temp_points[temp_points_start + 0], ImVec2_AddVA(points[1], ImVec2_MulVX(temp_normals[1], half_draw_size))) - ImVec2_CopyV(temp_points[temp_points_start + 1], ImVec2_SubVA(points[1], ImVec2_MulVX(temp_normals[1], half_draw_size))) - ImVec2_CopyV(temp_points[temp_points_start + (points_count - 1) * 2 + 0], ImVec2_AddVA(points[points_count], ImVec2_MulVX(temp_normals[points_count], half_draw_size))) - ImVec2_CopyV(temp_points[temp_points_start + (points_count - 1) * 2 + 1], ImVec2_SubVA(points[points_count], ImVec2_MulVX(temp_normals[points_count], half_draw_size))) - end - - -- Generate indices and vertices - local idx1 = self._VtxCurrentIdx - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local idx2 = (i1 == points_count) and self._VtxCurrentIdx or (idx1 + (use_texture and 2 or 3)) - - -- Average normals - local dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5 - local dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5 - dm_x, dm_y = IM_FIXNORMAL2F(dm_x, dm_y) - dm_x = dm_x * half_draw_size - dm_y = dm_y * half_draw_size - - -- Add temporary vertices for the outer edges - local out_off = (i2 - 1) * 2 - temp_points[temp_points_start + out_off + 0].x = points[i2].x + dm_x - temp_points[temp_points_start + out_off + 0].y = points[i2].y + dm_y - temp_points[temp_points_start + out_off + 1].x = points[i2].x - dm_x - temp_points[temp_points_start + out_off + 1].y = points[i2].y - dm_y - - if use_texture then - -- Add indices for two triangles - idx_data[idx_write_ptr + 0] = idx2 + 0; idx_data[idx_write_ptr + 1] = idx1 + 0; idx_data[idx_write_ptr + 2] = idx1 + 1 - idx_data[idx_write_ptr + 3] = idx2 + 1; idx_data[idx_write_ptr + 4] = idx1 + 1; idx_data[idx_write_ptr + 5] = idx2 + 0 - idx_write_ptr = idx_write_ptr + 6 - else - -- Add indices for four triangles - idx_data[idx_write_ptr + 0] = idx2 + 0; idx_data[idx_write_ptr + 1] = idx1 + 0; idx_data[idx_write_ptr + 2] = idx1 + 2 - idx_data[idx_write_ptr + 3] = idx1 + 2; idx_data[idx_write_ptr + 4] = idx2 + 2; idx_data[idx_write_ptr + 5] = idx2 + 0 - idx_data[idx_write_ptr + 6] = idx2 + 1; idx_data[idx_write_ptr + 7] = idx1 + 1; idx_data[idx_write_ptr + 8] = idx1 + 0 - idx_data[idx_write_ptr + 9] = idx1 + 0; idx_data[idx_write_ptr + 10] = idx2 + 0; idx_data[idx_write_ptr + 11] = idx2 + 1 - idx_write_ptr = idx_write_ptr + 12 - end - - idx1 = idx2 - end - self._IdxWritePtr = idx_write_ptr - - -- Add vertices - local vtx_write_ptr = self._VtxWritePtr - if use_texture then - -- Texture-based: need to implement TexUvLines lookup - local tex_uvs = self._Data.TexUvLines[integer_thickness] - local tex_uv0 = ImVec2(tex_uvs.x, tex_uvs.y) - local tex_uv1 = ImVec2(tex_uvs.z, tex_uvs.w) - for i = 0, points_count - 1 do - WVP(vtx_data, vtx_write_ptr + 0, temp_points[temp_points_start + i * 2 + 0], tex_uv0, col) - WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 2 + 1], tex_uv1, col) - vtx_write_ptr = vtx_write_ptr + 2 - end - else - -- If we're not using a texture, we need the center vertex as well - for i = 0, points_count - 1 do - WVP(vtx_data, vtx_write_ptr + 0, points[i + 1], opaque_uv, col) - WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 2 + 0], opaque_uv, col_trans) - WVP(vtx_data, vtx_write_ptr + 2, temp_points[temp_points_start + i * 2 + 1], opaque_uv, col_trans) - vtx_write_ptr = vtx_write_ptr + 3 - end - end - self._VtxWritePtr = vtx_write_ptr - else - -- [PATH 3] Non texture-based lines (thick) - local half_inner_thickness = (thickness - AA_SIZE) * 0.5 - - -- If line is not closed, handle first and last points - if not closed then - local points_last = points_count - 1 - ImVec2_CopyV(temp_points[temp_points_start + 0], ImVec2_AddVA(points[1], ImVec2_MulVX(temp_normals[1], (half_inner_thickness + AA_SIZE)))) - ImVec2_CopyV(temp_points[temp_points_start + 1], ImVec2_AddVA(points[1], ImVec2_MulVX(temp_normals[1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + 2], ImVec2_SubVA(points[1], ImVec2_MulVX(temp_normals[1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + 3], ImVec2_SubVA(points[1], ImVec2_MulVX(temp_normals[1], (half_inner_thickness + AA_SIZE)))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 0], ImVec2_AddVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], (half_inner_thickness + AA_SIZE)))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 1], ImVec2_AddVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 2], ImVec2_SubVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 3], ImVec2_SubVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], (half_inner_thickness + AA_SIZE)))) - end - - -- Generate indices and vertices - local idx1 = self._VtxCurrentIdx - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local idx2 = (i1 == points_count) and self._VtxCurrentIdx or (idx1 + 4) - - -- Average normals - local dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5 - local dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5 - dm_x, dm_y = IM_FIXNORMAL2F(dm_x, dm_y) - local dm_out_x = dm_x * (half_inner_thickness + AA_SIZE) - local dm_out_y = dm_y * (half_inner_thickness + AA_SIZE) - local dm_in_x = dm_x * half_inner_thickness - local dm_in_y = dm_y * half_inner_thickness - - -- Add temporary vertices - local out_off = (i2 - 1) * 4 - temp_points[temp_points_start + out_off + 0].x = points[i2].x + dm_out_x - temp_points[temp_points_start + out_off + 0].y = points[i2].y + dm_out_y - temp_points[temp_points_start + out_off + 1].x = points[i2].x + dm_in_x - temp_points[temp_points_start + out_off + 1].y = points[i2].y + dm_in_y - temp_points[temp_points_start + out_off + 2].x = points[i2].x - dm_in_x - temp_points[temp_points_start + out_off + 2].y = points[i2].y - dm_in_y - temp_points[temp_points_start + out_off + 3].x = points[i2].x - dm_out_x - temp_points[temp_points_start + out_off + 3].y = points[i2].y - dm_out_y - - -- Add indices - idx_data[idx_write_ptr + 0] = idx2 + 1; idx_data[idx_write_ptr + 1] = idx1 + 1; idx_data[idx_write_ptr + 2] = idx1 + 2 - idx_data[idx_write_ptr + 3] = idx1 + 2; idx_data[idx_write_ptr + 4] = idx2 + 2; idx_data[idx_write_ptr + 5] = idx2 + 1 - idx_data[idx_write_ptr + 6] = idx2 + 1; idx_data[idx_write_ptr + 7] = idx1 + 1; idx_data[idx_write_ptr + 8] = idx1 + 0 - idx_data[idx_write_ptr + 9] = idx1 + 0; idx_data[idx_write_ptr + 10] = idx2 + 0; idx_data[idx_write_ptr + 11] = idx2 + 1 - idx_data[idx_write_ptr + 12] = idx2 + 2; idx_data[idx_write_ptr + 13] = idx1 + 2; idx_data[idx_write_ptr + 14] = idx1 + 3 - idx_data[idx_write_ptr + 15] = idx1 + 3; idx_data[idx_write_ptr + 16] = idx2 + 3; idx_data[idx_write_ptr + 17] = idx2 + 2 - idx_write_ptr = idx_write_ptr + 18 - - idx1 = idx2 - end - self._IdxWritePtr = idx_write_ptr - - -- Add vertices - local vtx_write_ptr = self._VtxWritePtr - for i = 0, points_count - 1 do - WVP(vtx_data, vtx_write_ptr + 0, temp_points[temp_points_start + i * 4 + 0], opaque_uv, col_trans) - WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 4 + 1], opaque_uv, col) - WVP(vtx_data, vtx_write_ptr + 2, temp_points[temp_points_start + i * 4 + 2], opaque_uv, col) - WVP(vtx_data, vtx_write_ptr + 3, temp_points[temp_points_start + i * 4 + 3], opaque_uv, col_trans) - vtx_write_ptr = vtx_write_ptr + 4 - end - self._VtxWritePtr = vtx_write_ptr - end - self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count + -- Calculate normals for each line segment + for i = 1, points_count - 1 do + local p0 = points[i] + local p1 = points[i + 1] + local dx = p1.x - p0.x + local dy = p1.y - p0.y + local d2 = dx * dx + dy * dy + local inv_len = 0.0 + if d2 > 0.0 then inv_len = 1.0 / math.sqrt(d2) end -- ImRsqrtPrecise() + normals_x[i] = -dy * inv_len + normals_y[i] = dx * inv_len + sqr_lengths[i] = d2 + end + if bit32.band(flags, 512 --[[ImDrawFlags.Closed]]) ~= 0 then + local p0 = points[points_count] + local p1 = points[1] + local dx = p1.x - p0.x + local dy = p1.y - p0.y + local d2 = dx * dx + dy * dy + local inv_len = 0.0 + if d2 > 0.0 then inv_len = 1.0 / math.sqrt(d2) end -- ImRsqrtPrecise() + normals_x[points_count] = -dy * inv_len + normals_y[points_count] = dx * inv_len + sqr_lengths[points_count] = d2 else - -- [PATH 4] Non texture-based, Non anti-aliased lines - local idx_count = count * 6 - local vtx_count = count * 4 - PrimReserve(self, idx_count, vtx_count) + normals_x[points_count] = normals_x[points_count - 1] + normals_y[points_count] = normals_y[points_count - 1] + sqr_lengths[points_count] = 0.0 + end - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local p1 = points[i1] - local p2 = points[i2] + local u0, v0, u1, v1, fringe = _SelectLineTexture(self, screen_thickness, flags) - local dx = p2.x - p1.x - local dy = p2.y - p1.y - dx, dy = IM_NORMALIZE2F_OVER_ZERO(dx, dy) - dx = dx * (thickness * 0.5) - dy = dy * (thickness * 0.5) + local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) - local vtx_write_ptr = self._VtxWritePtr - WV(vtx_data, vtx_write_ptr + 0, p1.x + dy, p1.y - dx, opaque_uv.x, opaque_uv.y, col) - WV(vtx_data, vtx_write_ptr + 1, p2.x + dy, p2.y - dx, opaque_uv.x, opaque_uv.y, col) - WV(vtx_data, vtx_write_ptr + 2, p2.x - dy, p2.y + dx, opaque_uv.x, opaque_uv.y, col) - WV(vtx_data, vtx_write_ptr + 3, p1.x - dy, p1.y + dx, opaque_uv.x, opaque_uv.y, col) - self._VtxWritePtr = vtx_write_ptr + 4 + local stroke_pos = _GetStrokePos(flags, 262144 --[[ImDrawFlags.StrokeCenter]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + flags = bit32.band(bit32.bor(flags, 1024 --[[ImDrawFlags.JoinMiter]]), bit32.bnot(16384 --[[ImDrawFlags.AALineEnds]])) + end - local idx_write_ptr = self._IdxWritePtr - idx_data[idx_write_ptr + 0] = self._VtxCurrentIdx + 0; idx_data[idx_write_ptr + 1] = self._VtxCurrentIdx + 1; idx_data[idx_write_ptr + 2] = self._VtxCurrentIdx + 2 - idx_data[idx_write_ptr + 3] = self._VtxCurrentIdx + 0; idx_data[idx_write_ptr + 4] = self._VtxCurrentIdx + 2; idx_data[idx_write_ptr + 5] = self._VtxCurrentIdx + 3 - self._IdxWritePtr = idx_write_ptr + 6 - self._VtxCurrentIdx = self._VtxCurrentIdx + 4 + local closed = bit32.band(flags, 512 --[[ImDrawFlags.Closed]]) ~= 0 + local miters_only = bit32.band(flags, 1024 --[[ImDrawFlags.JoinMiter]]) ~= 0 + local miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT + + local thickness0 = (thickness + fringe) * 0.5 -- Center (or legacy) + if stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + thickness0 = thickness + fringe * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + thickness0 = _CalculateCenterBiasedOffset(self, thickness) + fringe * 0.5 + elseif stroke_pos == 131072 --[[ImDrawFlags.StrokeInside]] then + thickness0 = fringe * 0.5 + end + local thickness1 = (thickness + fringe) - thickness0 + + local idx_count = 0 + local vtx_count = 0 + local max_verts_per_point = miters_only and 2 or 4 + local max_tris_per_point = miters_only and 2 or 5 + if closed then + vtx_count = --[[body]] points_count * max_verts_per_point + --[[closing]] 2 + idx_count = (--[[body]] points_count * max_tris_per_point + --[[closing]] 2) * 3 + else + vtx_count = --[[body]] (points_count - 2) * max_verts_per_point + --[[caps]] (4 * 2) + idx_count = (--[[body]] (points_count - 2) * max_tris_per_point + --[[last seg]] 2 + --[[caps]] (2 * 2)) * 3 + end + PrimReserve(self, idx_count, vtx_count) + + local ratio = thickness0 / (thickness0 + thickness1) -- The points that use uv2 are placed directly on the path (offset = 0), calculate the texture position from stroke offsets. + local u2, v2 = u0 + (u1 - u0) * ratio, v0 + + -- Clamp inner offset and adjust texture coordinates to match the change. + -- This is used by some of the convex shape rendering to avoid rendering artifacts when thickness is about to fill the whole shape. + if thickness1 > max_inner_offset then + thickness1 = max_inner_offset + local s = (thickness0 + thickness1) / (thickness + fringe) + local du = u1 - u0 + u1 = u0 + du * s + end + + local half_aa = self._FringeScale * 0.5 -- Used for end caps, does not use "fringe" since end cap AA is not using the texture. + local use_aa_ends = bit32.band(flags, bit32.bor(8192 --[[ImDrawFlags.AALines]], 16384 --[[ImDrawFlags.AALineEnds]])) == bit32.bor(8192 --[[ImDrawFlags.AALines]], 16384 --[[ImDrawFlags.AALineEnds]]) + + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr -- 1-based write position in VtxBuffer + local iw = self._IdxWritePtr -- 1-based write position in IdxBuffer + local vtx_idx = self._VtxCurrentIdx + local first_vtx_offset = vw + local first_vtx_idx = vtx_idx -- [Luau] VtxBuffer position of index 'n' is first_vtx_offset + (n - first_vtx_idx) + local base_idx = vtx_idx + + local p1x, p1y + local n1x, n1y + local len_sqr1 + + local point_idx = 1 -- [Luau] 1-based, iterate point_idx..point_last inclusive + local point_last = points_count + if not closed then + -- Start cap + point_idx = point_idx + 1 + point_last = point_last - 1 + local p = points[1] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[1], normals_y[1] + len_sqr1 = sqr_lengths[1] + + local dir_x = n1y + local dir_y = -n1x + if bit32.band(flags, 2048 --[[ImDrawFlags.CapSquare]]) ~= 0 then + p1x = p1x - dir_x * thickness * 0.5 + p1y = p1y - dir_y * thickness * 0.5 end + + if not use_aa_ends then + base_idx = vtx_idx + WV(vb, vw + 0, p1x - n1x * thickness0, p1y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 1, p1x + n1x * thickness1, p1y + n1y * thickness1, u1, v1, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + else + local pa_x = p1x - dir_x * half_aa + local pa_y = p1y - dir_y * half_aa + local pb_x = p1x + dir_x * half_aa + local pb_y = p1y + dir_y * half_aa + + base_idx = vtx_idx + WV(vb, vw + 0, pa_x - n1x * thickness0, pa_y - n1y * thickness0, u0, v0, col_trans) + WV(vb, vw + 1, pa_x + n1x * thickness1, pa_y + n1y * thickness1, u1, v1, col_trans) + + local next_base_idx = vtx_idx + 2 + WV(vb, vw + 2, pb_x - n1x * thickness0, pb_y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 3, pb_x + n1x * thickness1, pb_y + n1y * thickness1, u1, v1, col) + vw = vw + 4; vtx_idx = vtx_idx + 4 + + -- AA cap + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + base_idx = next_base_idx + end + else + -- Wrap around segment + local p = points[points_count] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[points_count], normals_y[points_count] + len_sqr1 = sqr_lengths[points_count] + + -- This will be filled later, allocate space. + base_idx = vtx_idx + WV(vb, vw + 0, 0, 0, u0, v0, col) + WV(vb, vw + 1, 0, 0, u1, v1, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + end + + if miters_only then + for pi = point_idx, point_last do + local n0x, n0y = n1x, n1y + local p = points[pi] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[pi], normals_y[pi] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor + local miter_offset_y = (n0y + n1y) * miter_scale_factor + + -- Miter + local next_base_idx = vtx_idx + WV(vb, vw + 0, p1x - miter_offset_x * thickness0, p1y - miter_offset_y * thickness0, u0, v0, col) -- 2 + WV(vb, vw + 1, p1x + miter_offset_x * thickness1, p1y + miter_offset_y * thickness1, u1, v1, col) -- 3 + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + + base_idx = next_base_idx + end + else + for pi = point_idx, point_last do + local n0x, n0y = n1x, n1y + local len_sqr0 = len_sqr1 + local p = points[pi] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[pi], normals_y[pi] + len_sqr1 = sqr_lengths[pi] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + local sin_theta = n0y * n1x - n0x * n1y + + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor + local miter_offset_y = (n0y + n1y) * miter_scale_factor + + local miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y + + if miter_distance_sqr > miter_distance_limit_sqr then + -- Clipped bevel + + -- Limit the inner miter offset to avoid overshooting. + local ref_thickness = (sin_theta < 0.0) and thickness1 or thickness0 + local ref_thickness_sqr = ref_thickness * ref_thickness + local limit_sqr = ImMax(ImMax(len_sqr0, len_sqr1), ref_thickness_sqr) + local ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr + if ref_miter_dist_sqr > limit_sqr then + local scale = ImSqrt(limit_sqr / ref_miter_dist_sqr) + miter_offset_x = miter_offset_x * scale + miter_offset_y = miter_offset_y * scale + end + + local bn_x, bn_y = IM_NORMALIZE2F_OVER_ZERO(n0x + n1x, n0y + n1y) + + local side_offset = ((n0x * bn_x + n0y * bn_y) - 1.0) / (n0y * bn_x - n0x * bn_y) + local sd_x = bn_y * side_offset + local sd_y = -bn_x * side_offset + + -- Bevel + local next_base_idx = vtx_idx + 2 + if sin_theta < 0.0 then + WV(vb, vw + 0, p1x - (bn_x + sd_x) * thickness0, p1y - (bn_y + sd_y) * thickness0, u0, v0, col) -- 2 + WV(vb, vw + 1, p1x, p1y, u2, v2, col) -- 3 + WV(vb, vw + 2, p1x - (bn_x - sd_x) * thickness0, p1y - (bn_y - sd_y) * thickness0, u0, v0, col) -- 4 + WV(vb, vw + 3, p1x + miter_offset_x * thickness1, p1y + miter_offset_y * thickness1, u1, v1, col) -- 5 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 5 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 5; ib[iw + 5] = base_idx + 1 + -- Bevel + ib[iw + 6] = base_idx + 3; ib[iw + 7] = base_idx + 5; ib[iw + 8] = base_idx + 2 + ib[iw + 9] = base_idx + 3; ib[iw + 10] = base_idx + 2; ib[iw + 11] = base_idx + 4 + ib[iw + 12] = base_idx + 3; ib[iw + 13] = base_idx + 4; ib[iw + 14] = base_idx + 5 + else + WV(vb, vw + 0, p1x + (bn_x + sd_x) * thickness1, p1y + (bn_y + sd_y) * thickness1, u1, v1, col) -- 2 + WV(vb, vw + 1, p1x, p1y, u2, v2, col) -- 3 + WV(vb, vw + 2, p1x - miter_offset_x * thickness0, p1y - miter_offset_y * thickness0, u0, v0, col) -- 4 + WV(vb, vw + 3, p1x + (bn_x - sd_x) * thickness1, p1y + (bn_y - sd_y) * thickness1, u1, v1, col) -- 5 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 4; ib[iw + 2] = base_idx + 2 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 2; ib[iw + 5] = base_idx + 1 + -- Bevel + ib[iw + 6] = base_idx + 3; ib[iw + 7] = base_idx + 2; ib[iw + 8] = base_idx + 4 + ib[iw + 9] = base_idx + 3; ib[iw + 10] = base_idx + 4; ib[iw + 11] = base_idx + 5 + ib[iw + 12] = base_idx + 3; ib[iw + 13] = base_idx + 5; ib[iw + 14] = base_idx + 2 + end + vw = vw + 4; vtx_idx = vtx_idx + 4 + iw = iw + 15 + base_idx = next_base_idx + else + -- Miter + local next_base_idx = vtx_idx + WV(vb, vw + 0, p1x - miter_offset_x * thickness0, p1y - miter_offset_y * thickness0, u0, v0, col) -- 2 + WV(vb, vw + 1, p1x + miter_offset_x * thickness1, p1y + miter_offset_y * thickness1, u1, v1, col) -- 3 + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + base_idx = next_base_idx + end + end + end + + -- End cap + if not closed then + local p = points[points_count] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[points_count], normals_y[points_count] + + -- End cap + local dir_x = n1y + local dir_y = -n1x + if bit32.band(flags, 2048 --[[ImDrawFlags.CapSquare]]) ~= 0 then + p1x = p1x + dir_x * thickness * 0.5 + p1y = p1y + dir_y * thickness * 0.5 + end + + if not use_aa_ends then + WV(vb, vw + 0, p1x - n1x * thickness0, p1y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 1, p1x + n1x * thickness1, p1y + n1y * thickness1, u1, v1, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Connect + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + else + local pa_x = p1x - dir_x * half_aa + local pa_y = p1y - dir_y * half_aa + local pb_x = p1x + dir_x * half_aa + local pb_y = p1y + dir_y * half_aa + + local next_base_idx = vtx_idx + WV(vb, vw + 0, pa_x - n1x * thickness0, pa_y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 1, pa_x + n1x * thickness1, pa_y + n1y * thickness1, u1, v1, col) + + WV(vb, vw + 2, pb_x - n1x * thickness0, pb_y - n1y * thickness0, u0, v0, col_trans) + WV(vb, vw + 3, pb_x + n1x * thickness1, pb_y + n1y * thickness1, u1, v1, col_trans) + vw = vw + 4; vtx_idx = vtx_idx + 4 + + -- Connect + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + base_idx = next_base_idx + + -- AA cap + ib[iw + 6] = base_idx + 0; ib[iw + 7] = base_idx + 2; ib[iw + 8] = base_idx + 3 + ib[iw + 9] = base_idx + 0; ib[iw + 10] = base_idx + 3; ib[iw + 11] = base_idx + 1 + iw = iw + 12 + end + else + -- Connect the path. + -- [Luau] VtxBuffer.Data[first_vtx_offset + 0..1] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 0..1] (2 vertices of 36 bytes) + buffer.copy(vb, (first_vtx_offset - 1) * 36, vb, (first_vtx_offset + (base_idx - first_vtx_idx) - 1) * 36, 2 * 36) + end + + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = vtx_idx + + -- Restore unused memory + local remaining_idx_count = self.IdxBuffer.Size - (iw - 1) + local remaining_vtx_count = self.VtxBuffer.Size - (vw - 1) + IM_ASSERT_PARANOID(remaining_idx_count >= 0 and remaining_vtx_count >= 0) + if remaining_idx_count > 0 or remaining_vtx_count > 0 then -- [Luau] skip the call when nothing is left + self:PrimUnreserve(remaining_idx_count, remaining_vtx_count) + end +end + +-- - We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds. +-- - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. +--- @param points ImVec2[] # 1-based +--- @param points_count int +--- @param col ImU32 +--- @param flags? ImDrawFlags + +function MT.ImDrawList:AddConvexPolyFilled(points, points_count, col, flags) + if points_count < 3 or bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + if flags == nil then flags = 0 end + + local uv = self._Data.TexUvWhitePixel + local uv_x, uv_y = uv.x, uv.y + flags = bit32.bor(flags, self.Flags) + if bit32.band(flags, 4096 --[[ImDrawFlags.AAFill]]) ~= 0 then + local half_aa = self._FringeScale * 0.5 + local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) + local miters_only = bit32.band(flags, 1024 --[[ImDrawFlags.JoinMiter]]) ~= 0 + local miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT + + local idx_count = ((points_count - 2) + points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) * 3 + local vtx_count = points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local start_vtx_ptr = self._VtxWritePtr + local start_idx_ptr = self._IdxWritePtr + local iw = start_idx_ptr + + -- Compute normals and segment lengths + local temp_normals_x, temp_normals_y, temp_sqr_lengths = poly_normals_x, poly_normals_y, poly_sqr_lengths + local i0 = points_count + for i1 = 1, points_count do + local p0 = points[i0] + local p1 = points[i1] + local dx = p1.x - p0.x + local dy = p1.y - p0.y + local d2 = dx * dx + dy * dy + if d2 > 0.0 then + local inv_len = 1.0 / math.sqrt(d2) -- ImRsqrtPrecise() + dx = dx * inv_len + dy = dy * inv_len + end + temp_normals_x[i0] = dy + temp_normals_y[i0] = -dx + temp_sqr_lengths[i0] = d2 + i0 = i1 + end + + local vtx_inner_idx = self._VtxCurrentIdx + local inner_vtx_ptr = start_vtx_ptr + local vw = start_vtx_ptr + points_count + local vtx_idx = vtx_inner_idx + points_count + + local prev_outer_idx = 0 -- This should be the index of the last vertex, but we don't know it yet. Will need to patch once we're done. + if miters_only then + i0 = points_count + for i1 = 1, points_count do + local p1 = points[i1] + local p1x, p1y = p1.x, p1.y + local n0x, n0y = temp_normals_x[i0], temp_normals_y[i0] + local n1x, n1y = temp_normals_x[i1], temp_normals_y[i1] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor * half_aa + local miter_offset_y = (n0y + n1y) * miter_scale_factor * half_aa + + -- Inner + WV(vb, inner_vtx_ptr, p1x - miter_offset_x, p1y - miter_offset_y, uv_x, uv_y, col) + inner_vtx_ptr = inner_vtx_ptr + 1 + + local prev_inner_idx = vtx_inner_idx + (i0 - 1) + local inner_idx = vtx_inner_idx + (i1 - 1) + local outer_idx = vtx_idx + + -- Outer + WV(vb, vw, p1x + miter_offset_x, p1y + miter_offset_y, uv_x, uv_y, col_trans) + vw = vw + 1; vtx_idx = vtx_idx + 1 + -- Connect with previous + ib[iw + 0] = prev_outer_idx; ib[iw + 1] = outer_idx; ib[iw + 2] = inner_idx + ib[iw + 3] = prev_outer_idx; ib[iw + 4] = inner_idx; ib[iw + 5] = prev_inner_idx + iw = iw + 6 + + prev_outer_idx = outer_idx + i0 = i1 + end + else + local bevel_count = 0 + i0 = points_count + for i1 = 1, points_count do + local p1 = points[i1] + local p1x, p1y = p1.x, p1.y + local n0x, n0y = temp_normals_x[i0], temp_normals_y[i0] + local n1x, n1y = temp_normals_x[i1], temp_normals_y[i1] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor + local miter_offset_y = (n0y + n1y) * miter_scale_factor + + local miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y + local bevel = miter_distance_sqr > miter_distance_limit_sqr + if bevel then + -- Limit inner bevel so that it is does not shoot out outside the polygon. + -- (using ImMax is intentional: using min limited too much in Mikko's tests when you have short edge next to long one. + -- The idea of that check is to keep that worst case overshoot no bigger than the polygon) + local ref_thickness_sqr = half_aa * half_aa + local limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]) + local ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr + if ref_miter_dist_sqr > limit_sqr then + local scale = ImSqrt(limit_sqr / ref_miter_dist_sqr) + miter_offset_x = miter_offset_x * scale + miter_offset_y = miter_offset_y * scale + end + end + + miter_offset_x = miter_offset_x * half_aa + miter_offset_y = miter_offset_y * half_aa + + -- Inner + WV(vb, inner_vtx_ptr, p1x - miter_offset_x, p1y - miter_offset_y, uv_x, uv_y, col) + inner_vtx_ptr = inner_vtx_ptr + 1 + + local prev_inner_idx = vtx_inner_idx + (i0 - 1) + local inner_idx = vtx_inner_idx + (i1 - 1) + local outer_idx = vtx_idx + + -- Outer + if bevel and bevel_count < IM_POLYLINE_CONVEX_POLY_MAX_BEVELS then + -- Because the polygon is convex, we know the maximum number of bevel corners we can hit (which is very small number). + -- We keep track of them just in case the calculations disagree. + bevel_count = bevel_count + 1 + + WV(vb, vw + 0, p1x + n0x * half_aa, p1y + n0y * half_aa, uv_x, uv_y, col_trans) + WV(vb, vw + 1, p1x + n1x * half_aa, p1y + n1y * half_aa, uv_x, uv_y, col_trans) + vw = vw + 2; vtx_idx = vtx_idx + 2 + -- Connect with previous + ib[iw + 0] = prev_outer_idx; ib[iw + 1] = outer_idx; ib[iw + 2] = inner_idx + ib[iw + 3] = prev_outer_idx; ib[iw + 4] = inner_idx; ib[iw + 5] = prev_inner_idx + -- Fill bevel + ib[iw + 6] = outer_idx; ib[iw + 7] = outer_idx + 1; ib[iw + 8] = inner_idx + iw = iw + 9 + outer_idx = outer_idx + 1 + else + WV(vb, vw, p1x + miter_offset_x, p1y + miter_offset_y, uv_x, uv_y, col_trans) + vw = vw + 1; vtx_idx = vtx_idx + 1 + -- Connect with previous + ib[iw + 0] = prev_outer_idx; ib[iw + 1] = outer_idx; ib[iw + 2] = inner_idx + ib[iw + 3] = prev_outer_idx; ib[iw + 4] = inner_idx; ib[iw + 5] = prev_inner_idx + iw = iw + 6 + end + + prev_outer_idx = outer_idx + i0 = i1 + end + end + + -- Patch first segment to wrap around + ib[start_idx_ptr + 0] = prev_outer_idx + ib[start_idx_ptr + 3] = prev_outer_idx + + -- Add indices for fill + for i = 2, points_count - 1 do + ib[iw + 0] = vtx_inner_idx; ib[iw + 1] = vtx_inner_idx + i - 1; ib[iw + 2] = vtx_inner_idx + i + iw = iw + 3 + end + + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = vtx_idx + + local idx_used = iw - start_idx_ptr + local vtx_used = vw - start_vtx_ptr + assert(idx_used <= idx_count and vtx_used <= vtx_count) + if idx_used < idx_count or vtx_used < vtx_count then + self:PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used) + end + else + -- Non Anti-aliased Fill + local idx_count = (points_count - 2) * 3 + local vtx_count = points_count + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + local base_idx = self._VtxCurrentIdx + for i = 1, vtx_count do + local p = points[i] + WV(vb, vw, p.x, p.y, uv_x, uv_y, col) + vw = vw + 1 + end + for i = 2, points_count - 1 do + ib[iw + 0] = base_idx; ib[iw + 1] = base_idx + i - 1; ib[iw + 2] = base_idx + i + iw = iw + 3 + end + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = base_idx + vtx_count end end @@ -38056,15 +38479,16 @@ local function PathRectXY(self, ax, ay, bx, by, rounding, flags) if not rounding then rounding = 0.0 end if not flags then flags = 0 end - if rounding >= 0.5 then - assert(bit32.band(flags, 0x0F) == 0, "Misuse of legacy hardcoded ImDrawCornerFlags values!") + local has_rounding = rounding >= 0.25 * self._FringeScale -- IM_HAS_ROUNDING(rounding) + if has_rounding then if bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 0 then flags = bit32.bor(flags, 240 --[[ImDrawFlags.RoundAll]]) end + rounding = ImMin(rounding, ImAbs(bx - ax) * (((bit32.band(flags, 48 --[[ImDrawFlags.RoundTop]]) == 48 --[[ImDrawFlags.RoundTop]]) or (bit32.band(flags, 192 --[[ImDrawFlags.RoundBottom]]) == 192 --[[ImDrawFlags.RoundBottom]])) and 0.5 or 1.0) - 1.0) rounding = ImMin(rounding, ImAbs(by - ay) * (((bit32.band(flags, 80 --[[ImDrawFlags.RoundLeft]]) == 80 --[[ImDrawFlags.RoundLeft]]) or (bit32.band(flags, 160 --[[ImDrawFlags.RoundRight]]) == 160 --[[ImDrawFlags.RoundRight]])) and 0.5 or 1.0) - 1.0) end - if rounding < 0.5 or (bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]]) then + if not has_rounding or (bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]]) then PathLineToXY(self, ax, ay) PathLineToXY(self, bx, ay) PathLineToXY(self, bx, by) @@ -38074,7 +38498,7 @@ local function PathRectXY(self, ax, ay, bx, by, rounding, flags) local rounding_tr = (bit32.band(flags, 32 --[[ImDrawFlags.RoundTopRight]]) ~= 0) and rounding or 0.0 local rounding_br = (bit32.band(flags, 128 --[[ImDrawFlags.RoundBottomRight]]) ~= 0) and rounding or 0.0 local rounding_bl = (bit32.band(flags, 64 --[[ImDrawFlags.RoundBottomLeft]]) ~= 0) and rounding or 0.0 - -- [Luau] inline PathArcToFast() (radius < 0.5 handled by _PathArcToFastExXY()) + -- [Luau] inline PathArcToFast() (small radius handled by _PathArcToFastExXY()) _PathArcToFastExXY(self, ax + rounding_tl, ay + rounding_tl, rounding_tl, 6 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 9 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) _PathArcToFastExXY(self, bx - rounding_tr, ay + rounding_tr, rounding_tr, 9 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 12 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) _PathArcToFastExXY(self, bx - rounding_br, by - rounding_br, rounding_br, 0 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 3 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) @@ -38086,19 +38510,654 @@ function MT.ImDrawList:PathRect(a, b, rounding, flags) PathRectXY(self, a.x, a.y, b.x, b.y, rounding, flags) end +-- [Luau] scalar _AddLine (p1 = p1x,p1y; p2 = p2x,p2y); _AddLine() below wraps it + +local function _AddLineXY(self, p1x, p1y, p2x, p2y, col, thickness, flags) + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + local screen_thickness = thickness * self._InvFringeScale + if screen_thickness < 1.0 then + if screen_thickness < 1.0 / 255.0 then + return + end + col = ImAlphaMultiply(col, screen_thickness) + screen_thickness = 1.0 + thickness = self._FringeScale + end + + flags = bit32.bor(flags, self.Flags) + local u0, v0, u1, v1, fringe = _SelectLineTexture(self, screen_thickness, flags) + + local dir_x = p2x - p1x + local dir_y = p2y - p1y + local d2 = dir_x * dir_x + dir_y * dir_y + local inv_len = 0.0 + if d2 > 0.0 then inv_len = 1.0 / math.sqrt(d2) end -- ImRsqrtPrecise() + dir_x = dir_x * inv_len + dir_y = dir_y * inv_len + local norm_x = -dir_y + local norm_y = dir_x + + local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) + + local stroke_pos = _GetStrokePos(flags, 262144 --[[ImDrawFlags.StrokeCenter]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + flags = bit32.band(bit32.bor(flags, 1024 --[[ImDrawFlags.JoinMiter]]), bit32.bnot(16384 --[[ImDrawFlags.AALineEnds]])) + end + + local thickness0 = (thickness + fringe) * 0.5 -- Center (or legacy) + if stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + thickness0 = thickness + fringe * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + thickness0 = _CalculateCenterBiasedOffset(self, thickness) + fringe * 0.5 + elseif stroke_pos == 131072 --[[ImDrawFlags.StrokeInside]] then + thickness0 = fringe * 0.5 + end + local thickness1 = (thickness + fringe) - thickness0 + + -- [Luau] uv2 (ratio) is unused by _AddLine(): not computed + + local half_aa = self._FringeScale * 0.5 -- Used for end caps, does not use "fringe" since end cap AA is not using the texture. + local use_aa_ends = bit32.band(flags, bit32.bor(8192 --[[ImDrawFlags.AALines]], 16384 --[[ImDrawFlags.AALineEnds]])) == bit32.bor(8192 --[[ImDrawFlags.AALines]], 16384 --[[ImDrawFlags.AALineEnds]]) + + if not use_aa_ends then + PrimReserve(self, 2 * 3, 4) + else + PrimReserve(self, 6 * 3, 8) + end + + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + local base_idx = self._VtxCurrentIdx + + if bit32.band(flags, 2048 --[[ImDrawFlags.CapSquare]]) ~= 0 then + local half_thickness = thickness * 0.5 + p1x = p1x - dir_x * half_thickness + p1y = p1y - dir_y * half_thickness + p2x = p2x + dir_x * half_thickness + p2y = p2y + dir_y * half_thickness + end + + -- Start cap + if not use_aa_ends then + WV(vb, vw + 0, p1x - norm_x * thickness0, p1y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 1, p1x + norm_x * thickness1, p1y + norm_y * thickness1, u1, v1, col) + WV(vb, vw + 2, p2x - norm_x * thickness0, p2y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 3, p2x + norm_x * thickness1, p2y + norm_y * thickness1, u1, v1, col) + + -- Connect + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + self._VtxWritePtr = vw + 4 + self._IdxWritePtr = iw + 6 + self._VtxCurrentIdx = base_idx + 4 + else + -- Start + local pa_x = p1x - dir_x * half_aa + local pa_y = p1y - dir_y * half_aa + local pb_x = p1x + dir_x * half_aa + local pb_y = p1y + dir_y * half_aa + + WV(vb, vw + 0, pa_x - norm_x * thickness0, pa_y - norm_y * thickness0, u0, v0, col_trans) + WV(vb, vw + 1, pa_x + norm_x * thickness1, pa_y + norm_y * thickness1, u1, v1, col_trans) + + WV(vb, vw + 2, pb_x - norm_x * thickness0, pb_y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 3, pb_x + norm_x * thickness1, pb_y + norm_y * thickness1, u1, v1, col) + + -- AA cap + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + base_idx = base_idx + 2 + + -- End + pa_x = p2x - dir_x * half_aa + pa_y = p2y - dir_y * half_aa + pb_x = p2x + dir_x * half_aa + pb_y = p2y + dir_y * half_aa + + WV(vb, vw + 4, pa_x - norm_x * thickness0, pa_y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 5, pa_x + norm_x * thickness1, pa_y + norm_y * thickness1, u1, v1, col) + WV(vb, vw + 6, pb_x - norm_x * thickness0, pb_y - norm_y * thickness0, u0, v0, col_trans) + WV(vb, vw + 7, pb_x + norm_x * thickness1, pb_y + norm_y * thickness1, u1, v1, col_trans) + + -- Connect + ib[iw + 6] = base_idx + 0; ib[iw + 7] = base_idx + 2; ib[iw + 8] = base_idx + 3 + ib[iw + 9] = base_idx + 0; ib[iw + 10] = base_idx + 3; ib[iw + 11] = base_idx + 1 + base_idx = base_idx + 2 + + -- AA cap + ib[iw + 12] = base_idx + 0; ib[iw + 13] = base_idx + 2; ib[iw + 14] = base_idx + 3 + ib[iw + 15] = base_idx + 0; ib[iw + 16] = base_idx + 3; ib[iw + 17] = base_idx + 1 + self._VtxWritePtr = vw + 8 + self._IdxWritePtr = iw + 18 + self._VtxCurrentIdx = base_idx + 4 + end +end + +function MT.ImDrawList:_AddLine(p1, p2, col, thickness, flags) + _AddLineXY(self, p1.x, p1.y, p2.x, p2.y, col, thickness, flags) +end + +--- @param p1 ImVec2 +--- @param p2 ImVec2 +--- @param col ImU32 +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddLine(p1, p2, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 262144 --[[ImDrawFlags.StrokeCenter]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + _AddLineXY(self, p1.x + 0.5, p1.y + 0.5, p2.x + 0.5, p2.y + 0.5, col, thickness, flags) + return + end + + _AddLineXY(self, p1.x, p1.y, p2.x, p2.y, col, thickness, flags) +end + +-- AddLineH(10, 20, 5, col, 1.0f) == AddLine( {10,5.5}, {20,5.5}, col, 1.0f) +-- == AddLine( {10,5}, {20,5}, col, 1.0f, ImDrawFlags_StrokeLegacy); // or before 1.93.0 +-- Thickness extend inside/bottom by default. Faster than equivalent AddLine() call. +--- @param min_x float +--- @param max_x float +--- @param y float +--- @param col ImU32 +--- @param thickness? float +--- @param flags? ImDrawFlags + +function MT.ImDrawList:AddLineH(min_x, max_x, y, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + _AddLineXY(self, min_x + 0.5, y + 0.5, max_x + 0.5, y + 0.5, col, thickness, flags) + return + end + + local top_y = y -- Inside + if stroke_pos == 262144 --[[ImDrawFlags.StrokeCenter]] then + top_y = top_y - thickness * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + top_y = top_y - _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + top_y = top_y - thickness + end + + local is_truncated = min_x % 1 == 0 and max_x % 1 == 0 and top_y % 1 == 0 and thickness % 1 == 0 -- IM_IS_TRUNCATED4() + if self._FringeScaleIsInteger and is_truncated and bit32.band(flags, 2048 --[[ImDrawFlags.CapSquare]]) == 0 then + if thickness < self._FringeScale then + if thickness < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, thickness * self._InvFringeScale) + thickness = self._FringeScale + end + + -- For pixel aligned case, use simple rectangle. + PrimReserve(self, 6, 4) + PrimRectXY(self, min_x, top_y, max_x, top_y + thickness, col) + else + -- Replace stroke pos with the already calculated one, as AddPolyline() default is different. + flags = bit32.bor(bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]])), stroke_pos) + + -- For generic case use line, since it needs less triangles than AA rectangle. + _AddLineXY(self, min_x, y, max_x, y, col, thickness, flags) + end +end + +-- AddLineV(5, 10, 20, col, 1.0f) == AddLine( {5.5,10}, {5.5,20}, col, 1.0f) +-- == AddLine( {5,10}, {5,20}, col, 1.0f, ImDrawFlags_StrokeLegacy); // or before 1.93.0 +-- Thickness extend inside/right by default. Faster than equivalent AddLine() call. +--- @param x float +--- @param min_y float +--- @param max_y float +--- @param col ImU32 +--- @param thickness? float +--- @param flags? ImDrawFlags + +function MT.ImDrawList:AddLineV(x, min_y, max_y, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + _AddLineXY(self, x + 0.5, min_y + 0.5, x + 0.5, max_y + 0.5, col, thickness, flags) + return + end + + local left_x = x -- Inside + if stroke_pos == 262144 --[[ImDrawFlags.StrokeCenter]] then + left_x = left_x - thickness * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + left_x = left_x - _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + left_x = left_x - thickness + end + + local is_truncated = left_x % 1 == 0 and min_y % 1 == 0 and max_y % 1 == 0 and thickness % 1 == 0 -- IM_IS_TRUNCATED4() + if self._FringeScaleIsInteger and is_truncated and bit32.band(flags, 2048 --[[ImDrawFlags.CapSquare]]) == 0 then + if thickness < self._FringeScale then + if thickness < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, thickness * self._InvFringeScale) + thickness = self._FringeScale + end + + -- For pixel aligned case, use simple rectangle. + PrimReserve(self, 6, 4) + PrimRectXY(self, left_x, min_y, left_x + thickness, max_y, col) + else + -- Replace stroke pos with the already calculated one, as AddPolyline() default is different. + flags = bit32.bor(bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]])), stroke_pos) + + -- For generic case use line, since it needs less triangles than AA rectangle. + -- Drawing for bottom to top, so that the inside stroke pos expands to right. + _AddLineXY(self, x, max_y, x, min_y, col, thickness, flags) + end +end + +-- [Port] TODO baked round corners: _AddRectBaked() (UseTexForRoundCorners) not ported yet, AddRect() falls back to the geometry path. + +local corner_offset_x = { 1, -1, -1, 1 } -- static const ImVec2 corner_offset[4] = { {1,1}, {-1,1}, {-1,-1}, {1,-1} }; +local corner_offset_y = { 1, 1, -1, -1 } +local corner_flags = { ImDrawFlags.RoundTopLeft, ImDrawFlags.RoundTopRight, ImDrawFlags.RoundBottomRight, ImDrawFlags.RoundBottomLeft } +local corner_pos_x, corner_pos_y = { 0, 0, 0, 0 }, { 0, 0, 0, 0 } -- [Luau] scratch + +-- Draws rounded rectangle where thickness > rounding. If rendered using the regular polyline, the stroke will fold and leave artifacts on the corners if rendered with transparency. +-- The stroke is positioned inside the rectangle. +--- @param p_min ImVec2 +--- @param p_max ImVec2 +--- @param col ImU32 +--- @param rounding float +--- @param thickness float +--- @param flags ImDrawFlags +function MT.ImDrawList:_AddRectTinyRounding(p_min, p_max, col, rounding, thickness, flags) + local screen_thickness = thickness * self._InvFringeScale + if screen_thickness < 1.0 then + if screen_thickness < 1.0 / 255.0 then + return + end + col = ImAlphaMultiply(col, screen_thickness) + screen_thickness = 1.0 + thickness = self._FringeScale + end + + flags = bit32.bor(flags, self.Flags) + local outer_u, outer_v, inner_u, inner_v, fringe = _SelectLineTexture(self, screen_thickness, flags) + + local auto_seg_count = self:_CalcCircleAutoSegmentCount(rounding) + local arc_step, arc_step_count = CalcCornerArcStepAndCount(auto_seg_count) + local arc_point_count = arc_step_count + 1 + + local vtx_count = (2 + arc_point_count) * 4 + 3 + local idx_count = (4 + arc_point_count - 1) * 4 * 3 + + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local start_vtx_ptr = self._VtxWritePtr + local start_idx_ptr = self._IdxWritePtr + local vw, iw = start_vtx_ptr, start_idx_ptr + local vtx_idx = self._VtxCurrentIdx + local first_vtx_idx = vtx_idx -- [Luau] VtxBuffer position of index 'n' is start_vtx_ptr + (n - first_vtx_idx) + + local half_fringe = fringe * 0.5 + corner_pos_x[1] = p_min.x - half_fringe; corner_pos_y[1] = p_min.y - half_fringe + corner_pos_x[2] = p_max.x + half_fringe; corner_pos_y[2] = p_min.y - half_fringe + corner_pos_x[3] = p_max.x + half_fringe; corner_pos_y[3] = p_max.y + half_fringe + corner_pos_x[4] = p_min.x - half_fringe; corner_pos_y[4] = p_max.y + half_fringe + thickness = thickness + fringe + rounding = rounding + half_fringe + + local stem_offset = ImMin(rounding, thickness) -- The function might get called with rounding slightly bigger than thickness, which is ok, but we should keep the stem inside the shape. + local ratio = stem_offset / thickness + local stem_u, stem_v = outer_u + (inner_u - outer_u) * ratio, outer_v + + -- Previous vertices, copied from the last vertices. + local base_idx = vtx_idx + WV(vb, vw + 0, 0, 0, inner_u, inner_v, col) + WV(vb, vw + 1, 0, 0, stem_u, stem_v, col) + WV(vb, vw + 2, 0, 0, outer_u, outer_v, col) + vw = vw + 3; vtx_idx = vtx_idx + 3 + + local arc_fast_vtx = self._Data.ArcFastVtx -- 1-based + for corner = 1, 4 do + local outer_x = corner_pos_x[corner] + local outer_y = corner_pos_y[corner] + local cox, coy = corner_offset_x[corner], corner_offset_y[corner] + local stem_x = outer_x + cox * stem_offset + local stem_y = outer_y + coy * stem_offset + local inner_x = outer_x + cox * thickness + local inner_y = outer_y + coy * thickness + local is_rounded = bit32.band(flags, corner_flags[corner]) ~= 0 + + local arc_idx = vtx_idx + if is_rounded then + local center_x = outer_x + cox * rounding + local center_y = outer_y + coy * rounding + local a = (48 --[[IM_DRAWLIST_ARCFAST_TABLE_SIZE]] / 4) * (corner - 1) + for _ = 0, arc_point_count - 1 do + local s = arc_fast_vtx[a + 1] + WV(vb, vw, center_x - s.x * rounding, center_y - s.y * rounding, outer_u, outer_v, col) + vw = vw + 1; vtx_idx = vtx_idx + 1 + a = (a + arc_step) % 48 --[[IM_DRAWLIST_ARCFAST_TABLE_SIZE]] + end + else + WV(vb, vw, outer_x, outer_y, outer_u, outer_v, col) + vw = vw + 1; vtx_idx = vtx_idx + 1 + end + + local stem_idx = vtx_idx + WV(vb, vw, stem_x, stem_y, stem_u, stem_v, col) + local inner_idx = vtx_idx + 1 + WV(vb, vw + 1, inner_x, inner_y, inner_u, inner_v, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Arc + if is_rounded then + for i = 0, arc_point_count - 2 do + ib[iw + 0] = stem_idx; ib[iw + 1] = arc_idx + i; ib[iw + 2] = arc_idx + i + 1 + iw = iw + 3 + end + end + + -- Connect with previous + ib[iw + 0] = base_idx + 0; ib[iw + 1] = arc_idx; ib[iw + 2] = stem_idx + ib[iw + 3] = base_idx + 0; ib[iw + 4] = stem_idx; ib[iw + 5] = base_idx + 1 + ib[iw + 6] = base_idx + 1; ib[iw + 7] = stem_idx; ib[iw + 8] = inner_idx + ib[iw + 9] = base_idx + 1; ib[iw + 10] = inner_idx; ib[iw + 11] = base_idx + 2 + iw = iw + 12 + + base_idx = stem_idx - 1 + end + + -- Close + -- [Luau] start_vtx_ptr[0..2] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 0..2] (3 vertices of 36 bytes) + buffer.copy(vb, (start_vtx_ptr - 1) * 36, vb, (start_vtx_ptr + (base_idx - first_vtx_idx) - 1) * 36, 3 * 36) + + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = vtx_idx + + local idx_used = iw - start_idx_ptr + local vtx_used = vw - start_vtx_ptr + IM_ASSERT_PARANOID(idx_used <= idx_count and vtx_used <= vtx_count) + if idx_used < idx_count or vtx_used < vtx_count then + self:PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used) + end +end + +local prim_points = { ImVec2(), ImVec2(), ImVec2(), ImVec2() } -- [Luau] scratch for C++ 'const ImVec2 points[N]' locals (callees only read them) + +-- p_min = upper-left, p_max = lower-right +--- @param p_min ImVec2 +--- @param p_max ImVec2 +--- @param col ImU32 +--- @param rounding? float +--- @param thickness? float +--- @param flags? ImDrawFlags + +function MT.ImDrawList:AddRect(p_min, p_max, col, rounding, thickness, flags) + if rounding == nil then rounding = 0.0 end + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + -- If this assert triggers on legacy code: + -- - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. + -- - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. + -- - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundXXX values. + -- If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags! + -- - Hard coded support for ~0 == ImDrawFlags_RoundAll. + -- - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. + -- - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundNone or use 'float rounding = 0.0f'. + -- See "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. + IM_ASSERT_USER_ERROR_RET(bit32.band(flags, 2147483663 --[[ImDrawFlags.InvalidMask_]]) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?") -- Or misuse of legacy hard-coded ImDrawCornerFlags values + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + if bit32.band(flags, 8192 --[[ImDrawFlags.AALines]]) ~= 0 then + PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.50, p_max.y - 0.50, rounding, flags) -- [Luau] scalar math, no temporary ImVec2 + else + PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.49, p_max.y - 0.49, rounding, flags) -- Better looking lower-right corner and rounded non-AA shapes. + end + self:PathStroke(col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], 262144 --[[ImDrawFlags.StrokeCenter]], bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]])))) + return + end + + local fringe_scale = self._FringeScale + local has_rounding = rounding >= 0.25 * fringe_scale -- IM_HAS_ROUNDING(rounding) + + -- Calculate the outer boundary of the rectangle based on the stroke position. + local offset = 0.0 -- Inside + if stroke_pos == 262144 --[[ImDrawFlags.StrokeCenter]] then + offset = thickness * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + offset = _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + offset = thickness + end + local outer_min_x = p_min.x - offset + local outer_min_y = p_min.y - offset + local outer_max_x = p_max.x + offset + local outer_max_y = p_max.y + offset + local outer_rounding = rounding + (has_rounding and offset or 0.0) + + local width = outer_max_x - outer_min_x + local height = outer_max_y - outer_min_y + local half_min_dim = ImMin(width, height) * 0.5 + if half_min_dim <= thickness then + -- Rectangle has collapsed into filled rectangle. + self:AddRectFilled(ImVec2(outer_min_x, outer_min_y), ImVec2(outer_max_x, outer_max_y), col, outer_rounding, flags) + return + end + + if bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 0 then + flags = bit32.bor(flags, 240 --[[ImDrawFlags.RoundAll]]) + end + if has_rounding then + -- Constrain rounding to rect dimensions + outer_rounding = ImMin(outer_rounding, ImAbs(width) * (((bit32.band(flags, 48 --[[ImDrawFlags.RoundTop]]) == 48 --[[ImDrawFlags.RoundTop]]) or (bit32.band(flags, 192 --[[ImDrawFlags.RoundBottom]]) == 192 --[[ImDrawFlags.RoundBottom]])) and 0.5 or 1.0) - 1.0) + outer_rounding = ImMin(outer_rounding, ImAbs(height) * (((bit32.band(flags, 80 --[[ImDrawFlags.RoundLeft]]) == 80 --[[ImDrawFlags.RoundLeft]]) or (bit32.band(flags, 160 --[[ImDrawFlags.RoundRight]]) == 160 --[[ImDrawFlags.RoundRight]])) and 0.5 or 1.0) - 1.0) + end + + local is_truncated = outer_min_x % 1 == 0 and outer_min_y % 1 == 0 and outer_max_x % 1 == 0 and outer_max_y % 1 == 0 and outer_rounding % 1 == 0 and thickness % 1 == 0 -- IM_IS_TRUNCATED4() x2 + if self._FringeScaleIsInteger and is_truncated then + local s_rounding = ImTrunc64(outer_rounding * self._InvFringeScale) + local s_thickness = ImTrunc64(thickness * self._InvFringeScale) + if s_thickness <= 0 then + return + end + + if s_rounding <= 0 or bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]] then + -- Pixel aligned non-rounded rect. + local opaque_uv = self._Data.TexUvWhitePixel + local u, v = opaque_uv.x, opaque_uv.y + local t = s_thickness * fringe_scale + + PrimReserve(self, 8 * 3, 8) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + local idx = self._VtxCurrentIdx + WV(vb, vw + 0, outer_min_x, outer_min_y, u, v, col) + WV(vb, vw + 1, outer_min_x + t, outer_min_y + t, u, v, col) + WV(vb, vw + 2, outer_max_x, outer_min_y, u, v, col) + WV(vb, vw + 3, outer_max_x - t, outer_min_y + t, u, v, col) + WV(vb, vw + 4, outer_max_x, outer_max_y, u, v, col) + WV(vb, vw + 5, outer_max_x - t, outer_max_y - t, u, v, col) + WV(vb, vw + 6, outer_min_x, outer_max_y, u, v, col) + WV(vb, vw + 7, outer_min_x + t, outer_max_y - t, u, v, col) + + ib[iw + 0] = idx + 0; ib[iw + 1] = idx + 3; ib[iw + 2] = idx + 1 + ib[iw + 3] = idx + 0; ib[iw + 4] = idx + 2; ib[iw + 5] = idx + 3 + + ib[iw + 6] = idx + 2; ib[iw + 7] = idx + 5; ib[iw + 8] = idx + 3 + ib[iw + 9] = idx + 2; ib[iw + 10] = idx + 4; ib[iw + 11] = idx + 5 + + ib[iw + 12] = idx + 4; ib[iw + 13] = idx + 7; ib[iw + 14] = idx + 5 + ib[iw + 15] = idx + 4; ib[iw + 16] = idx + 6; ib[iw + 17] = idx + 7 + + ib[iw + 18] = idx + 6; ib[iw + 19] = idx + 1; ib[iw + 20] = idx + 7 + ib[iw + 21] = idx + 6; ib[iw + 22] = idx + 0; ib[iw + 23] = idx + 1 + + self._VtxWritePtr = vw + 8 + self._IdxWritePtr = iw + 24 + self._VtxCurrentIdx = idx + 8 + return + end + -- Textured corners are baked with AA, do not use them if no-AA is requested. + -- [Port] TODO baked round corners: allow_tex_corners -> _AddRectBaked(), falls back to the geometry path below. + end + + if not has_rounding or bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]] then + local points = prim_points + points[1].x = outer_min_x; points[1].y = outer_min_y + points[2].x = outer_max_x; points[2].y = outer_min_y + points[3].x = outer_max_x; points[3].y = outer_max_y + points[4].x = outer_min_x; points[4].y = outer_max_y + self:_AddPolyline(points, 4, col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], 131072 --[[ImDrawFlags.StrokeInside]]), half_min_dim) + self._Path.Size = 0 + else + -- Switch to specialized rendering a bit early to avoid overdraw visible in transparent shapes. + if thickness + (0.75 * fringe_scale) > outer_rounding then + -- Special case rendering to avoid rendering artifacts at the corners. + -- If rendered using the regular polyline, the stroke will fold and leave artifacts on the corners if rendered with transparency. + self:_AddRectTinyRounding(ImVec2(outer_min_x, outer_min_y), ImVec2(outer_max_x, outer_max_y), col, outer_rounding, thickness, flags) + return + end + local rounding_tl = (bit32.band(flags, 16 --[[ImDrawFlags.RoundTopLeft]]) ~= 0) and outer_rounding or 0.0 + local rounding_tr = (bit32.band(flags, 32 --[[ImDrawFlags.RoundTopRight]]) ~= 0) and outer_rounding or 0.0 + local rounding_br = (bit32.band(flags, 128 --[[ImDrawFlags.RoundBottomRight]]) ~= 0) and outer_rounding or 0.0 + local rounding_bl = (bit32.band(flags, 64 --[[ImDrawFlags.RoundBottomLeft]]) ~= 0) and outer_rounding or 0.0 + -- [Luau] inline PathArcToFast() + _PathArcToFastExXY(self, outer_min_x + rounding_tl, outer_min_y + rounding_tl, rounding_tl, 6 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 9 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) + _PathArcToFastExXY(self, outer_max_x - rounding_tr, outer_min_y + rounding_tr, rounding_tr, 9 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 12 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) + _PathArcToFastExXY(self, outer_max_x - rounding_br, outer_max_y - rounding_br, rounding_br, 0 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 3 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) + _PathArcToFastExXY(self, outer_min_x + rounding_bl, outer_max_y - rounding_bl, rounding_bl, 3 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 6 * 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / 12, 0) + + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], 131072 --[[ImDrawFlags.StrokeInside]], bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]]))), half_min_dim) + self._Path.Size = 0 + end +end + +-- [Port] TODO baked round corners: _AddRectFilledBaked() (UseTexForRoundCorners) not ported yet, AddRectFilled() falls back to the geometry path. + +--- @param p_min ImVec2 +--- @param p_max ImVec2 +--- @param col ImU32 +--- @param rounding? float +--- @param flags? ImDrawFlags 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 - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end - if rounding < 0.5 or (bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]]) then + local ax, ay, bx, by = p_min.x, p_min.y, p_max.x, p_max.y + local width = bx - ax + local height = by - ay + local min_dim = ImMin(width, height) + flags = bit32.bor(flags, self.Flags) + + if bit32.band(flags, 917504 --[[ImDrawFlags.StrokeMask_]]) == 917504 --[[ImDrawFlags.StrokeLegacy]] and rounding < 0.5 then PrimReserve(self, 6, 4) -- [Luau] local calls - PrimRectXY(self, p_min.x, p_min.y, p_max.x, p_max.y, col) + PrimRectXY(self, ax, ay, bx, by, col) + return + end + + -- The rect is smaller than pixel in one dimension. + local fringe_scale = self._FringeScale + if min_dim < fringe_scale then + if min_dim < (1.0 / 255.0) * fringe_scale then + return + end + if width < fringe_scale then + col = ImAlphaMultiply(col, width * self._InvFringeScale) + width = fringe_scale + end + if height < fringe_scale then + col = ImAlphaMultiply(col, height * self._InvFringeScale) + height = fringe_scale + end + local points = prim_points + points[1].x = ax; points[1].y = ay + points[2].x = ax + width; points[2].y = ay + points[3].x = ax + width; points[3].y = ay + height + points[4].x = ax; points[4].y = ay + height + self:AddConvexPolyFilled(points, 4, col, 1024 --[[ImDrawFlags.JoinMiter]]) + return + end + + -- When rounding non-integer and under threshold, has_rounding may be false but still affect is_truncated. Likely not worth catering for. + local has_rounding = bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) ~= 256 --[[ImDrawFlags.RoundNone]] and rounding >= 0.25 * fringe_scale -- IM_HAS_ROUNDING(rounding) + local is_truncated = ax % 1 == 0 and ay % 1 == 0 and bx % 1 == 0 and by % 1 == 0 and rounding % 1 == 0 -- IM_IS_TRUNCATED4() && IM_IS_TRUNCATED() + + if self._FringeScaleIsInteger and is_truncated then + -- Fast path for the non-AA non-rounded rect. + if not has_rounding then + PrimReserve(self, 6, 4) + PrimRectXY(self, ax, ay, bx, by, col) + return + end + + if bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 0 then + flags = bit32.bor(flags, 240 --[[ImDrawFlags.RoundAll]]) + end + + rounding = ImMin(rounding, ImAbs(width) * (((bit32.band(flags, 48 --[[ImDrawFlags.RoundTop]]) == 48 --[[ImDrawFlags.RoundTop]]) or (bit32.band(flags, 192 --[[ImDrawFlags.RoundBottom]]) == 192 --[[ImDrawFlags.RoundBottom]])) and 0.5 or 1.0) - 1.0) + rounding = ImMin(rounding, ImAbs(height) * (((bit32.band(flags, 80 --[[ImDrawFlags.RoundLeft]]) == 80 --[[ImDrawFlags.RoundLeft]]) or (bit32.band(flags, 160 --[[ImDrawFlags.RoundRight]]) == 160 --[[ImDrawFlags.RoundRight]])) and 0.5 or 1.0) - 1.0) + + local s_rounding = ImTrunc64(rounding * self._InvFringeScale) + if s_rounding <= 0 then + PrimReserve(self, 6, 4) + PrimRectXY(self, ax, ay, bx, by, col) + return + end + -- Textured corners are baked with AA, do not use them if no-AA is requested. + -- [Port] TODO baked round corners: allow_tex_corners -> _AddRectFilledBaked(), falls back to the geometry path below. + end + + if not has_rounding then + local points = prim_points + points[1].x = ax; points[1].y = ay + points[2].x = bx; points[2].y = ay + points[3].x = bx; points[3].y = by + points[4].x = ax; points[4].y = by + self:AddConvexPolyFilled(points, 4, col, 1024 --[[ImDrawFlags.JoinMiter]]) else - PathRectXY(self, p_min.x, p_min.y, p_max.x, p_max.y, rounding, flags) - self:PathFillConvex(col) + PathRectXY(self, ax, ay, bx, by, rounding, flags) + self:PathFillConvex(col, 1024 --[[ImDrawFlags.JoinMiter]]) end end @@ -38131,189 +39190,353 @@ function MT.ImDrawList:AddRectFilledMultiColor(p_min, p_max, col_upr_left, col_u self._VtxCurrentIdx = idx + 4 end --- p_min = upper-left, p_max = lower-right ---- @param p_min ImVec2 ---- @param p_max ImVec2 ---- @param col ImU32 ---- @param rounding float --- @param thickness? float --- @param flags? ImDrawFlags - -function MT.ImDrawList:AddRect(p_min, p_max, col, rounding, thickness, flags) +function MT.ImDrawList:AddQuad(p1, p2, p3, p4, col, thickness, flags) if thickness == nil then thickness = 1.0 end if flags == nil then flags = 0 end - -- If this assert triggers on legacy code: - -- - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. - -- - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. - -- - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundXXX values. - -- If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags! - -- - Hard coded support for ~0 == ImDrawFlags_RoundAll. - -- - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. - -- - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundNone or use 'float rounding = 0.0f'. - -- See "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. - IM_ASSERT_USER_ERROR_RET(bit32.band(flags, 2147483663 --[[ImDrawFlags.InvalidMask_]]) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?") -- Or misuse of legacy hard-coded ImDrawCornerFlags values - - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then - return - end - -- [Luau] square corners + whole-pixel thickness: 4 filled rects instead of a stroked path (same pixels, far cheaper here) - if (rounding == nil or rounding < 0.5 or bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 256 --[[ImDrawFlags.RoundNone]]) - and thickness >= 1.0 and thickness % 1 == 0 and p_max.x - p_min.x > 2 * thickness and p_max.y - p_min.y > 2 * thickness then - local x0, y0, x1, y1, t = p_min.x, p_min.y, p_max.x, p_max.y, thickness - PrimReserve(self, 24, 16) - PrimRectXY(self, x0, y0, x1, y0 + t, col) - PrimRectXY(self, x0, y1 - t, x1, y1, col) - PrimRectXY(self, x0, y0 + t, x0 + t, y1 - t, col) - PrimRectXY(self, x1 - t, y0 + t, x1, y1 - t, col) - return - end - if bit32.band(self.Flags, 8192 --[[ImDrawFlags.AALines]]) ~= 0 then - PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.50, p_max.y - 0.50, rounding, flags) -- [Luau] scalar math, no temporary ImVec2 - else - PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.49, p_max.y - 0.49, rounding, flags) -- Better looking lower-right corner and rounded non-AA shapes. - end - - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) -end - -function MT.ImDrawList:AddLine(p1, p2, col, thickness) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - - PathLineToXY(self, p1.x + 0.5, p1.y + 0.5) -- [Luau] scalar math, no temporary ImVec2 - PathLineToXY(self, p2.x + 0.5, p2.y + 0.5) - self:PathStroke(col, thickness) -end - ---- @param min_x float ---- @param max_x float ---- @param y float ---- @param col ImU32 ---- @param thickness? float -function MT.ImDrawList:AddLineH(min_x, max_x, y, col, thickness) - if thickness == nil then thickness = 1.0 end - - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then - return - end - PathLineToXY(self, min_x + 0.5, y + 0.5) - PathLineToXY(self, max_x + 0.5, y + 0.5) - self:PathStroke(col, thickness) -end - ---- @param x float ---- @param min_y float ---- @param max_y float ---- @param col ImU32 ---- @param thickness? float -function MT.ImDrawList:AddLineV(x, min_y, max_y, col, thickness) - if thickness == nil then thickness = 1.0 end - - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then - return - end - PathLineToXY(self, x + 0.5, min_y + 0.5) - PathLineToXY(self, x + 0.5, max_y + 0.5) - self:PathStroke(col, thickness) -end - -function MT.ImDrawList:AddTriangle(p1, p2, p3, col, thickness) if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) -end + --flags |= Flags; // Not needed. + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + flags = bit32.bor(bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]])), stroke_pos) -- As AddPolyline() default is different -function MT.ImDrawList:AddQuad(p1, p2, p3, p4, col, thickness) - if thickness == nil then thickness = 1.0 end - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathLineTo(p4) - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3); ImVec2_Copy(points[4], p4) + self:_AddPolyline(points, 4, col, thickness, bit32.bor(flags, 512 --[[ImDrawFlags.Closed]]), 3.4028234663853e+38 --[[FLT_MAX]]) end function MT.ImDrawList:AddQuadFilled(p1, p2, p3, p4, col) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathLineTo(p4) - self:PathFillConvex(col) + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3); ImVec2_Copy(points[4], p4) + self:AddConvexPolyFilled(points, 4, col) +end + +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddTriangle(p1, p2, p3, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + --flags |= Flags; // Not needed. + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + flags = bit32.bor(bit32.band(flags, bit32.bnot(917504 --[[ImDrawFlags.StrokeMask_]])), stroke_pos) -- As AddPolyline() default is different + + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3) + self:_AddPolyline(points, 3, col, thickness, bit32.bor(flags, 512 --[[ImDrawFlags.Closed]]), 3.4028234663853e+38 --[[FLT_MAX]]) end function MT.ImDrawList:AddTriangleFilled(p1, p2, p3, col) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathFillConvex(col) -end - ---- @param center ImVec2 ---- @param radius float ---- @param col ImU32 ---- @param num_segments int ---- @param thickness float -function MT.ImDrawList:AddCircle(center, radius, col, num_segments, thickness) - if num_segments == nil then num_segments = 0 end - if thickness == nil then thickness = 1.0 end - - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or radius < 0.5 then + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - if num_segments <= 0 then + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3) + self:AddConvexPolyFilled(points, 3, col) +end + +--- @param center ImVec2 +--- @param radius float +--- @param col ImU32 +--- @param num_segments? int +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddCircle(center, radius, col, num_segments, thickness, flags) + if num_segments == nil then num_segments = 0 end + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + radius = radius - 0.5 + stroke_pos = 262144 --[[ImDrawFlags.StrokeCenter]] + end + radius = ImMax(0.01, radius) + + local outer_radius = radius + if stroke_pos == 262144 --[[ImDrawFlags.StrokeCenter]] then + outer_radius = radius + thickness * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + outer_radius = radius + _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == 131072 --[[ImDrawFlags.StrokeInside]] then + outer_radius = radius + elseif stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + outer_radius = radius + thickness + end + + if outer_radius <= thickness then + -- The circle has collapsed into a filled circle. + self:AddCircleFilled(center, outer_radius, col, num_segments) + return + end + + if num_segments <= 0 and outer_radius <= self._Data.ArcFastRadiusCutoff then -- Use arc with automatic segment count - _PathArcToFastExXY(self, center.x, center.y, radius - 0.5, 0, 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]], 0) + local a_step = math.floor(48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / self:_CalcCircleAutoSegmentCount(outer_radius)) -- Use outer_radius for segment count to be consistent with rounded rect. + _PathArcToFastExXY(self, center.x, center.y, radius, 0, 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]], a_step) self._Path.Size = self._Path.Size - 1 else - -- Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) + -- Explicit segment count or above fast arc cutoff (still clamp to avoid drawing insanely tessellated shapes) + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(outer_radius) + end num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1) / num_segments - self:PathArcTo(center, radius - 0.5, 0.0, a_max, num_segments - 1) + local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments + self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) end - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) + if self._Path.Size >= 3 then + -- Calculate distance to the edge of the polygon describing the circle. + -- This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. + local unit_apothem = ApproxApothem(self._Path.Size) + local edge_dist = radius * unit_apothem + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], stroke_pos), edge_dist) + end + self._Path.Size = 0 end ---- @param center ImVec2 ---- @param radius float ---- @param col ImU32 ---- @param num_segments int +--- @param center ImVec2 +--- @param radius float +--- @param col ImU32 +--- @param num_segments? int function MT.ImDrawList:AddCircleFilled(center, radius, col, num_segments) if num_segments == nil then num_segments = 0 end - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or radius < 0.5 then + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - if num_segments <= 0 then + local diameter = radius * 2.0 + if diameter < self._FringeScale then + if diameter < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, diameter * self._InvFringeScale) + radius = self._FringeScale * 0.5 + end + + if num_segments <= 0 and radius <= self._Data.ArcFastRadiusCutoff then -- Use arc with automatic segment count _PathArcToFastExXY(self, center.x, center.y, radius, 0, 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]], 0) self._Path.Size = self._Path.Size - 1 else - -- Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) + -- Explicit segment count or above cutoff (still clamp to avoid drawing insanely tessellated shapes) + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(radius) + end num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1) / num_segments + local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) end + self:PathFillConvex(col, 1024 --[[ImDrawFlags.JoinMiter]]) +end + +-- Guaranteed to honor 'num_segments' +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddNgon(center, radius, col, num_segments, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or num_segments <= 2 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + radius = radius - 0.5 + stroke_pos = 262144 --[[ImDrawFlags.StrokeCenter]] + end + radius = ImMax(0.01, radius) + + -- Calculate distance to the edge of the polygon describing the circle. + -- This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. + local unit_apothem = ApproxApothem(num_segments) + local edge_dist = radius * unit_apothem + + -- Check overestimate first before testing details. + if radius < thickness * 2.0 then + local outer_radius = radius + if stroke_pos == 262144 --[[ImDrawFlags.StrokeCenter]] then + outer_radius = radius + thickness * 0.5 + elseif stroke_pos == 393216 --[[ImDrawFlags.StrokeCenterBiased]] then + outer_radius = radius + _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == 131072 --[[ImDrawFlags.StrokeInside]] then + outer_radius = radius + elseif stroke_pos == 524288 --[[ImDrawFlags.StrokeOutside]] then + outer_radius = radius + thickness + end + if outer_radius * unit_apothem <= thickness then + -- The polygon has collapsed into a filled polygon. + self:AddNgonFilled(center, outer_radius, col, num_segments) + return + end + end + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments + self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], stroke_pos), edge_dist) + self._Path.Size = 0 +end + +-- Guaranteed to honor 'num_segments' +function MT.ImDrawList:AddNgonFilled(center, radius, col, num_segments) + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or num_segments <= 2 then + return + end + + local diameter = radius * 2.0 + if diameter < self._FringeScale then + if diameter < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, diameter * self._InvFringeScale) + radius = self._FringeScale * 0.5 + end + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments + self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) + self:PathFillConvex(col, 1024 --[[ImDrawFlags.JoinMiter]]) +end + +-- Ellipse +--- @param rot? float +--- @param num_segments? int +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddEllipse(center, radius, col, rot, num_segments, thickness, flags) + if rot == nil then rot = 0.0 end + if num_segments == nil then num_segments = 0 end + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + local stroke_pos = _GetStrokePos(flags, 131072 --[[ImDrawFlags.StrokeInside]]) + if stroke_pos == 917504 --[[ImDrawFlags.StrokeLegacy]] then + stroke_pos = 262144 --[[ImDrawFlags.StrokeCenter]] + end + + local rad = ImVec2(radius.x, radius.y) + rad.x = ImMax(0.01, rad.x) + rad.y = ImMax(0.01, rad.y) + + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(ImMax(rad.x, rad.y)) -- A bit pessimistic, maybe there's a better computation to do here. + end + + self._Path:reserve(self._Path.Size + (num_segments + 1)) + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = 3.141592653589793 --[[IM_PI]] * 2.0 * (num_segments - 1.0) / num_segments + self:PathEllipticalArcTo(center, rad, rot, 0.0, a_max, num_segments - 1) + + -- Prevent the inside part of the stroke to be expanded too much to prevent some rendering artifacts. + local max_inner_offset = ImMin(rad.x, rad.y) + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(512 --[[ImDrawFlags.Closed]], 1024 --[[ImDrawFlags.JoinMiter]], stroke_pos), max_inner_offset) + self._Path.Size = 0 +end + +--- @param rot? float +--- @param num_segments? int +function MT.ImDrawList:AddEllipseFilled(center, radius, col, rot, num_segments) + if rot == nil then rot = 0.0 end + if num_segments == nil then num_segments = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + local rad = ImVec2(radius.x, radius.y) + local diameter_x = rad.x * 2.0 + local diameter_y = rad.y * 2.0 + local min_dim = ImMin(diameter_x, diameter_y) + + -- The ellipse is smaller than pixel in one dimension. + if min_dim < self._FringeScale then + if min_dim < (1.0 / 255.0) * self._FringeScale then + return + end + if diameter_x < self._FringeScale then + col = ImAlphaMultiply(col, diameter_x * self._InvFringeScale) + rad.x = self._FringeScale * 0.5 + end + if diameter_y < self._FringeScale then + col = ImAlphaMultiply(col, diameter_y * self._InvFringeScale) + rad.y = self._FringeScale * 0.5 + end + end + + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)) -- A bit pessimistic, maybe there's a better computation to do here. + end + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = 3.141592653589793 --[[IM_PI]] * 2.0 * (num_segments - 1.0) / num_segments + self:PathEllipticalArcTo(center, radius, rot, 0.0, a_max, num_segments - 1) -- [Port] upstream passes 'radius' (not 'rad') here, kept as-is self:PathFillConvex(col) end +-- Cubic Bezier takes 4 controls points +--- @param num_segments? int +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments, flags) + if num_segments == nil then num_segments = 0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + self:PathLineTo(p1) + self:PathBezierCubicCurveTo(p2, p3, p4, num_segments) + self:PathStroke(col, thickness, flags) +end + +-- Quadratic Bezier takes 3 controls points +--- @param num_segments? int +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddBezierQuadratic(p1, p2, p3, col, thickness, num_segments, flags) + if num_segments == nil then num_segments = 0 end + if flags == nil then flags = 0 end + + if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + self:PathLineTo(p1) + self:PathBezierQuadraticCurveTo(p2, p3, num_segments) + self:PathStroke(col, thickness, flags) +end + --- @param font ImFont --- @param font_size float --- @param pos ImVec2 @@ -38403,6 +39626,7 @@ function MT.ImDrawList:AddImageRounded(tex_ref, p_min, p_max, uv_min, uv_max, co if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end + if flags == nil then flags = 0 end assert(bit32.band(flags, 0x0F) == 0, "Misuse of legacy hardcoded ImDrawCornerFlags values!") -- If this assert triggers on legacy code: see comments in ImDrawList:PathRect(). if bit32.band(flags, 496 --[[ImDrawFlags.RoundMask_]]) == 0 then @@ -38421,7 +39645,7 @@ function MT.ImDrawList:AddImageRounded(tex_ref, p_min, p_max, uv_min, uv_max, co local vert_start_idx = self.VtxBuffer.Size + 1 self:PathRect(p_min, p_max, rounding, flags) - self:PathFillConvex(col) + self:PathFillConvex(col, 1024 --[[ImDrawFlags.JoinMiter]]) local vert_end_idx = self.VtxBuffer.Size + 1 ImGui.ShadeVertsLinearUV(self, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true) @@ -38491,19 +39715,21 @@ end -- [Luau] scalar center (cx, cy); assigned to the forward-declared local, _PathArcToFastEx() wraps it function _PathArcToFastExXY(self, cx, cy, radius, a_min_sample, a_max_sample, a_step) - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) PathLineToXY(self, cx, cy) return end -- Calculate arc auto segment step size if a_step <= 0 then - a_step = math.floor(48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] / self:_CalcCircleAutoSegmentCount(radius)) + -- Calculate step size using the common function. No need to clamp, the step is already in range. + local auto_seg_count = self:_CalcCircleAutoSegmentCount(radius) + a_step = CalcCornerArcStepAndCount(auto_seg_count) + else + -- Make sure we never do steps larger than one quarter of the circle + a_step = ImClamp(a_step, 1, math.floor(48 --[[IM_DRAWLIST_ARCFAST_TABLE_SIZE]] / 4)) end - -- Make sure we never do steps larger than one quarter of the circle - a_step = ImClamp(a_step, 1, math.floor(48 --[[IM_DRAWLIST_ARCFAST_TABLE_SIZE]] / 4)) - local sample_range = ImAbs(a_max_sample - a_min_sample) local a_next_step = a_step @@ -38595,7 +39821,7 @@ end -- 0: East, 3: South, 6: West, 9: North, 12: East function MT.ImDrawList:PathArcToFast(center, radius, a_min_of_12, a_max_of_12) - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) self._Path:push_back(center) return end @@ -38609,7 +39835,7 @@ end --- @param a_max float --- @param num_segments int function MT.ImDrawList:_PathArcToN(center, radius, a_min, a_max, num_segments) - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) self._Path:push_back(center) return end @@ -38637,7 +39863,7 @@ end function MT.ImDrawList:PathArcTo(center, radius, a_min, a_max, num_segments) if num_segments == nil then num_segments = 0 end - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) self._Path:push_back(center) return end @@ -38651,13 +39877,21 @@ function MT.ImDrawList:PathArcTo(center, radius, a_min, a_max, num_segments) if radius <= self._Data.ArcFastRadiusCutoff then local a_is_reverse = a_max < a_min + -- Calculate number of segments per circle. + -- First we calculate the step count as usual, then match to the precomputed directions. + -- This will give us the spacing between the samples (step), and how many segments to use in total for a full circle. + local auto_seg_count = self:_CalcCircleAutoSegmentCount(radius) + local arc_step, arc_step_count = CalcCornerArcStepAndCount(auto_seg_count) + local arc_seg_count = arc_step_count * 4 -- Seg count for full circle. + -- We are going to use precomputed values for mid samples. -- Determine first and last sample in lookup table that belong to the arc. - local a_min_sample_f = 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] * a_min / (3.141592653589793 --[[IM_PI]] * 2.0) - local a_max_sample_f = 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] * a_max / (3.141592653589793 --[[IM_PI]] * 2.0) + -- In first step we use the coarser segment count, and after rounding, convert back to the precomputed array index. + local a_min_sample_f = arc_seg_count * a_min / (3.141592653589793 --[[IM_PI]] * 2.0) + local a_max_sample_f = arc_seg_count * a_max / (3.141592653589793 --[[IM_PI]] * 2.0) - local a_min_sample = a_is_reverse and ImFloor(a_min_sample_f) or ImCeil(a_min_sample_f) - local a_max_sample = a_is_reverse and ImCeil(a_max_sample_f) or ImFloor(a_max_sample_f) + local a_min_sample = (a_is_reverse and ImFloor(a_min_sample_f) or ImCeil(a_min_sample_f)) * arc_step + local a_max_sample = (a_is_reverse and ImCeil(a_max_sample_f) or ImFloor(a_max_sample_f)) * arc_step local a_mid_samples = a_is_reverse and ImMax(a_min_sample - a_max_sample, 0) or ImMax(a_max_sample - a_min_sample, 0) local a_min_segment_angle = a_min_sample * 3.141592653589793 --[[IM_PI]] * 2.0 / 48 --[[IM_DRAWLIST_ARCFAST_SAMPLE_MAX]] @@ -38668,7 +39902,7 @@ function MT.ImDrawList:PathArcTo(center, radius, a_min, a_max, num_segments) self._Path:reserve(self._Path.Size + (a_mid_samples + 1 + (a_emit_start and 1 or 0) + (a_emit_end and 1 or 0))) if a_emit_start then PathLineToXY(self, center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius) end -- [Luau] no temporary ImVec2 - if a_mid_samples > 0 then _PathArcToFastExXY(self, center.x, center.y, radius, a_min_sample, a_max_sample, 0) end + if a_mid_samples > 0 then _PathArcToFastExXY(self, center.x, center.y, radius, a_min_sample, a_max_sample, arc_step) end if a_emit_end then PathLineToXY(self, center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius) end else local arc_length = ImAbs(a_max - a_min) @@ -38702,7 +39936,8 @@ function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_b if y > clip_rect.w then return end - if bit32.band(draw_list.Flags, 1048576 --[[ImDrawFlags.TextNoPixelSnap]]) == 0 then + flags = bit32.bor(flags, draw_list.Flags) + if bit32.band(flags, 1048576 --[[ImDrawFlags.TextNoPixelSnap]]) == 0 then x = IM_TRUNC(x) y = IM_TRUNC(y) end @@ -39219,9 +40454,8 @@ function ImGui.RenderRectFilledWithHole(draw_list, outer, inner, col, rounding) end ------------------------------------------------------------------------------- --- Extra primitives (Ngon, Ellipse, Bezier, concave fill) +-- Extra path functions (elliptical arc, bezier curves) and concave fill ------------------------------------------------------------------------------- --- [port] These follow the 1.92 stroker this port uses (no ImDrawFlags_StrokeXXX placement flags). function MT.ImDrawList:PathEllipticalArcTo(center, radius, rot, a_min, a_max, num_segments) if num_segments == nil or num_segments <= 0 then @@ -39304,63 +40538,6 @@ function MT.ImDrawList:PathBezierQuadraticCurveTo(p2, p3, num_segments) end end --- Guaranteed to honor 'num_segments' -function MT.ImDrawList:AddNgon(center, radius, col, num_segments, thickness) - if thickness == nil then thickness = 1.0 end - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or num_segments <= 2 then return end - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments - self:PathArcTo(center, radius - 0.5, 0.0, a_max, num_segments - 1) - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) -end - --- Guaranteed to honor 'num_segments' -function MT.ImDrawList:AddNgonFilled(center, radius, col, num_segments) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 or num_segments <= 2 then return end - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (3.141592653589793 --[[IM_PI]] * 2.0) * (num_segments - 1.0) / num_segments - self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) - self:PathFillConvex(col) -end - --- Ellipse -function MT.ImDrawList:AddEllipse(center, radius, col, rot, num_segments, thickness) - if rot == nil then rot = 0.0 end - if thickness == nil then thickness = 1.0 end - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - if num_segments == nil or num_segments <= 0 then num_segments = self:_CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)) end -- A bit pessimistic, maybe there's a better computation to do here. - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = 3.141592653589793 --[[IM_PI]] * 2.0 * (num_segments - 1.0) / num_segments - self:PathEllipticalArcTo(center, radius, rot, 0.0, a_max, num_segments - 1) - self:PathStroke(col, thickness, 512 --[[ImDrawFlags.Closed]]) -end - -function MT.ImDrawList:AddEllipseFilled(center, radius, col, rot, num_segments) - if rot == nil then rot = 0.0 end - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - if num_segments == nil or num_segments <= 0 then num_segments = self:_CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)) end -- A bit pessimistic, maybe there's a better computation to do here. - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = 3.141592653589793 --[[IM_PI]] * 2.0 * (num_segments - 1.0) / num_segments - self:PathEllipticalArcTo(center, radius, rot, 0.0, a_max, num_segments - 1) - self:PathFillConvex(col) -end - --- Cubic Bezier takes 4 controls points -function MT.ImDrawList:AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - self:PathLineTo(p1) - self:PathBezierCubicCurveTo(p2, p3, p4, num_segments or 0) - self:PathStroke(col, thickness or 1.0) -end - --- Quadratic Bezier takes 3 controls points -function MT.ImDrawList:AddBezierQuadratic(p1, p2, p3, col, thickness, num_segments) - if bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - self:PathLineTo(p1) - self:PathBezierQuadraticCurveTo(p2, p3, num_segments or 0) - self:PathStroke(col, thickness or 1.0) -end - -- ponytail: ear-clipping, no anti-aliased fringe (upstream's AA triangulator not ported). O(n^2) is fine for UI polygons. --- @param points ImVec2[] # 1-based function MT.ImDrawList:AddConcavePolyFilled(points, points_count, col) diff --git a/docs/PORTING.md b/docs/PORTING.md index 5251214..37f2380 100644 --- a/docs/PORTING.md +++ b/docs/PORTING.md @@ -65,5 +65,5 @@ there, but keep this port's existing data structures and conventions. Read the s - **ImGuiAxis is 1-based in this port** (`None=0, X=1, Y=2`; upstream `None=-1, X=0, Y=1`). Use `v[axis]` to index an ImVec2 and `3 - axis` for the other axis. `ImGuiDir` keeps upstream values (`None=-1, Left=0, Right=1, Up=2, Down=3`). - Never write `cond and nil or x` in Lua: it always evaluates `x`. Use an `if`. -- Port signature quirk: `AddRect(min, max, col, rounding, thickness, flags)` (thickness BEFORE flags; upstream has flags first). Check the port's signature before porting any draw call that takes both.\n +- Draw calls taking both thickness and flags use upstream's 1.92.8+ order: thickness BEFORE flags, e.g. `AddRect(min, max, col, rounding, thickness, flags)`, `PathStroke(col, thickness, flags)`, `AddPolyline(points, count, col, thickness, flags)`. - C++ default arguments (e.g. `bool x = true`) must be reproduced with `if x == nil then x = true end`; a missing Lua argument is nil/falsy. diff --git a/docs/PORT_STATUS.txt b/docs/PORT_STATUS.txt index bfa1e82..ad3a562 100644 --- a/docs/PORT_STATUS.txt +++ b/docs/PORT_STATUS.txt @@ -12,20 +12,20 @@ Known low scores that are not missing code: data-driven port code (e.g. DemoWind polyline stroker (AddPolyline), Lua-native replacements (ImGuiStorage, ImStr*, ImFormatString -> string.format). upstream file funcs ported by size similarity stubs -imgui.cpp 786 87.4% 93.5% 75.0% 2 +imgui.cpp 786 87.4% 93.5% 75.1% 2 imgui_widgets.cpp 308 93.8% 98.7% 78.1% 1 -imgui_draw.cpp 258 75.2% 75.5% 63.1% 2 +imgui_draw.cpp 258 79.5% 86.2% 62.8% 2 imgui_tables.cpp 113 96.5% 99.0% 74.0% 0 imgui_demo.cpp 56 100.0% 100.0% 55.8% 0 -imgui.h 110 53.6% 37.4% 56.7% 0 -imgui_internal.h 269 61.3% 64.9% 68.0% 1 -TOTAL 1900 82.1% 93.1% 70.7% 6 +imgui.h 110 53.6% 37.4% 60.0% 0 +imgui_internal.h 269 62.5% 65.7% 67.6% 1 +TOTAL 1900 82.8% 94.5% 70.6% 6 -upstream code reproduced (similarity x ported size / all upstream size): 65.8% +upstream code reproduced (similarity x ported size / all upstream size): 66.7% Public API (imgui.h, namespace ImGui) 368 / 376 97.9% Internal API (imgui_internal.h, namespace ImGui) 417 / 443 94.1% -Enum values (imgui.h + imgui_internal.h) 1005 / 1078 93.2% -Struct fields (imgui.h + imgui_internal.h) 1822 / 1926 94.6% +Enum values (imgui.h + imgui_internal.h) 1034 / 1078 95.9% +Struct fields (imgui.h + imgui_internal.h) 1832 / 1926 95.1% Legend: [x] ported (sim %) [~] stub [ ] not ported [-] excluded (c++-only / obsolete / os-specific / #if 0 / test-engine) @@ -41,9 +41,9 @@ imgui.cpp [ ] FreeWrapper -- USER FACING STRUCTURES (ImGuiStyle, ImGuiIO, ImGuiPlatformIO) - [x] 38% ImGuiStyle::ImGuiStyle imgui_internal.lua:1479 + [x] 33% ImGuiStyle::ImGuiStyle imgui_internal.lua:1518 [x] 83% ImGuiStyle::ScaleAllSizes imgui.lua:4779 - [x] 12% ImGuiIO::ImGuiIO imgui_h.lua:1181 + [x] 12% ImGuiIO::ImGuiIO imgui_h.lua:1187 [x] 83% ImGuiIO::AddInputCharacter imgui.lua:7983 [x] 83% ImGuiIO::AddInputCharacterUTF16 imgui.lua:8004 [ ] ImGuiIO::AddInputCharactersUTF8 @@ -61,7 +61,7 @@ imgui.cpp [x] 96% ImGuiIO::AddMouseSourceEvent imgui.lua:8173 [x] 87% ImGuiIO::AddMouseViewportEvent imgui.lua:8180 [x] 84% ImGuiIO::AddFocusEvent imgui.lua:8209 - [x] 15% ImGuiPlatformIO::ImGuiPlatformIO imgui_h.lua:1517 + [x] 15% ImGuiPlatformIO::ImGuiPlatformIO imgui_h.lua:1525 -- MISC HELPERS/UTILITIES (Geometry functions) [ ] ImBezierCubicClosestPoint @@ -116,11 +116,11 @@ imgui.cpp [x] 41% ImTextCountLines imgui_tables.lua:3782 -- MISC HELPERS/UTILITIES (Color functions) - [x] 73% ImAlphaBlendColors imgui.lua:14383 - [x] 41% ImGui::ColorConvertU32ToFloat4 imgui.lua:14394 - [x] 16% ImGui::ColorConvertFloat4ToU32 imgui.lua:14413 - [x] 87% ImGui::ColorConvertRGBtoHSV imgui.lua:14424 - [x] 61% ImGui::ColorConvertHSVtoRGB imgui.lua:14449 + [x] 73% ImAlphaBlendColors imgui.lua:14400 + [x] 41% ImGui::ColorConvertU32ToFloat4 imgui.lua:14411 + [x] 16% ImGui::ColorConvertFloat4ToU32 imgui.lua:14430 + [x] 87% ImGui::ColorConvertRGBtoHSV imgui.lua:14441 + [x] 61% ImGui::ColorConvertHSVtoRGB imgui.lua:14466 -- ImGuiStorage [x] 69% ImLowerBound imgui_widgets.lua:5186 @@ -141,44 +141,44 @@ imgui.cpp [ ] ImGuiStorage::SetAllInt -- ImGuiTextFilter - [x] 32% ImGuiTextFilter::ImGuiTextFilter imgui.lua:19337 - [x] 36% ImGuiTextFilter::Draw imgui.lua:19347 - [x] 62% ImGuiTextFilter::DrawWithHint imgui.lua:19354 - [x] 49% ImGuiTextFilter::Build imgui.lua:19369 - [x] 56% ImGuiTextFilter::PassFilter imgui.lua:19430 + [x] 32% ImGuiTextFilter::ImGuiTextFilter imgui.lua:19361 + [x] 36% ImGuiTextFilter::Draw imgui.lua:19371 + [x] 62% ImGuiTextFilter::DrawWithHint imgui.lua:19378 + [x] 49% ImGuiTextFilter::Build imgui.lua:19393 + [x] 56% ImGuiTextFilter::PassFilter imgui.lua:19454 -- ImGuiTextBuffer, ImGuiTextIndex - [x] 5% ImGuiTextBuffer::append imgui_h.lua:2685 - [x] 17% ImGuiTextBuffer::appendf imgui_h.lua:2686 + [x] 5% ImGuiTextBuffer::append imgui_h.lua:2748 + [x] 17% ImGuiTextBuffer::appendf imgui_h.lua:2749 [ ] ImGuiTextBuffer::appendfv [ ] ImGuiTextIndex::append -- ImGuiListClipper - [x] 86% GetSkipItemForListClipping imgui.lua:19468 - [x] 71% ImGuiListClipper_SortAndFuseRanges imgui.lua:19478 - [x] 89% ImGuiListClipper_SeekCursorAndSetupPrevLine imgui.lua:19502 - [x] 8% ImGuiListClipper::ImGuiListClipper imgui.lua:19535 + [x] 86% GetSkipItemForListClipping imgui.lua:19492 + [x] 71% ImGuiListClipper_SortAndFuseRanges imgui.lua:19502 + [x] 89% ImGuiListClipper_SeekCursorAndSetupPrevLine imgui.lua:19526 + [x] 8% ImGuiListClipper::ImGuiListClipper imgui.lua:19559 [-] ImGuiListClipper::~ImGuiListClipper c++-only - [x] 64% ImGuiListClipper::Begin imgui.lua:19542 - [x] 79% ImGuiListClipper::End imgui.lua:19571 - [x] 61% ImGuiListClipper::IncludeItemsByIndex imgui.lua:19597 - [x] 80% ImGuiListClipper::SeekCursorForItem imgui.lua:19608 - [x] 79% ImGuiListClipper_StepInternal imgui.lua:19615 - [x] 40% ImGuiListClipper::Step imgui.lua:19764 - [x] 92% ImGui::CalcClipRectVisibleItemsY imgui.lua:14481 + [x] 64% ImGuiListClipper::Begin imgui.lua:19566 + [x] 79% ImGuiListClipper::End imgui.lua:19595 + [x] 61% ImGuiListClipper::IncludeItemsByIndex imgui.lua:19621 + [x] 80% ImGuiListClipper::SeekCursorForItem imgui.lua:19632 + [x] 79% ImGuiListClipper_StepInternal imgui.lua:19639 + [x] 40% ImGuiListClipper::Step imgui.lua:19788 + [x] 92% ImGui::CalcClipRectVisibleItemsY imgui.lua:14498 -- STYLING - [x] 90% ImGui::GetStyle imgui.lua:14492 - [x] 71% ImGui::GetStyleColorVec4 imgui.lua:18887 - [x] 26% ImGui::GetColorU32 imgui.lua:14506 - [x] 50% ImGui::PushStyleColor imgui.lua:14544 - [x] 68% ImGui::PopStyleColor imgui.lua:14561 - [x] 74% ImGui::GetStyleVarInfo imgui.lua:14628 - [x] 56% ImGui::PushStyleVar imgui.lua:14636 - [x] 91% ImGui::PushStyleVarX imgui.lua:14655 - [x] 91% ImGui::PushStyleVarY imgui.lua:14666 - [x] 70% ImGui::PopStyleVar imgui.lua:14676 - [x] 1% ImGui::GetStyleColorName imgui.lua:19197 + [x] 90% ImGui::GetStyle imgui.lua:14509 + [x] 71% ImGui::GetStyleColorVec4 imgui.lua:18911 + [x] 26% ImGui::GetColorU32 imgui.lua:14523 + [x] 50% ImGui::PushStyleColor imgui.lua:14561 + [x] 68% ImGui::PopStyleColor imgui.lua:14578 + [x] 74% ImGui::GetStyleVarInfo imgui.lua:14645 + [x] 56% ImGui::PushStyleVar imgui.lua:14653 + [x] 91% ImGui::PushStyleVarX imgui.lua:14672 + [x] 91% ImGui::PushStyleVarY imgui.lua:14683 + [x] 70% ImGui::PopStyleVar imgui.lua:14693 + [x] 1% ImGui::GetStyleColorName imgui.lua:19221 -- RENDER HELPERS [x] 46% ImGui::FindRenderedTextEnd imgui.lua:10069 @@ -200,17 +200,17 @@ imgui.cpp [ ] ImGui::GetAllocatorFunctions [x] 87% ImGui::CreateContext imgui.lua:5580 [x] 83% ImGui::DestroyContext imgui.lua:5592 - [x] 6% ImGuiContext::ImGuiContext imgui_internal.lua:2197 + [x] 6% ImGuiContext::ImGuiContext imgui_internal.lua:2239 [-] ImGuiContext::~ImGuiContext c++-only [x] 78% ImGui::Initialize imgui.lua:5538 - [x] 44% ImGui::Shutdown imgui.lua:14298 + [x] 44% ImGui::Shutdown imgui.lua:14315 [ ] ImGui::SetContextName [ ] ImGui::AddContextHook [ ] ImGui::RemoveContextHook [ ] ImGui::CallContextHooks -- MAIN CODE (most of the code! lots of stuff, needs tidying up!) - [x] 11% ImGuiWindow::ImGuiWindow imgui_internal.lua:2800 + [x] 11% ImGuiWindow::ImGuiWindow imgui_internal.lua:2842 [-] ImGuiWindow::~ImGuiWindow c++-only [x] 74% SetCurrentWindow imgui.lua:10805 [x] 85% ImGui::GcCompactTransientMiscBuffers imgui.lua:10826 @@ -235,17 +235,17 @@ imgui.cpp [x] 79% ImGui::MemFree imgui.lua:470 [ ] ImGui::DemoMarker [x] 79% ImGui::DebugAllocHook imgui.lua:485 - [x] 91% ImGui::GetClipboardText imgui.lua:14336 - [x] 100% ImGui::SetClipboardText imgui.lua:14342 - [x] 100% ImGui::GetVersion imgui.lua:19194 - [x] 57% ImGui::GetIO imgui.lua:14349 - [x] 90% ImGui::GetPlatformIO imgui.lua:14352 - [x] 86% ImGui::GetDrawData imgui.lua:14287 - [x] 100% ImGui::GetTime imgui.lua:14293 - [x] 100% ImGui::GetFrameCount imgui.lua:19186 - [x] 82% GetViewportBgFgDrawList imgui.lua:13630 - [x] 80% ImGui::GetBackgroundDrawList imgui.lua:13653 - [x] 80% ImGui::GetForegroundDrawList imgui.lua:13665 + [x] 91% ImGui::GetClipboardText imgui.lua:14353 + [x] 100% ImGui::SetClipboardText imgui.lua:14359 + [x] 100% ImGui::GetVersion imgui.lua:19218 + [x] 57% ImGui::GetIO imgui.lua:14366 + [x] 90% ImGui::GetPlatformIO imgui.lua:14369 + [x] 86% ImGui::GetDrawData imgui.lua:14304 + [x] 100% ImGui::GetTime imgui.lua:14310 + [x] 100% ImGui::GetFrameCount imgui.lua:19210 + [x] 87% GetViewportBgFgDrawList imgui.lua:13639 + [x] 80% ImGui::GetBackgroundDrawList imgui.lua:13663 + [x] 80% ImGui::GetForegroundDrawList imgui.lua:13675 [x] 100% ImGui::GetDrawListSharedData imgui.lua:11533 [x] 90% ImGui::StartMouseMovingWindow imgui.lua:11462 [x] 94% ImGui::StartMouseMovingWindowOrNode imgui.lua:4527 @@ -253,27 +253,27 @@ imgui.cpp [x] 95% ImGui::UpdateMouseMovingWindowNewFrame imgui.lua:11494 [x] 88% ImGui::UpdateMouseMovingWindowEndFrame imgui.lua:11591 [x] 65% TranslateWindow imgui.lua:11662 - [x] 88% ScaleWindow imgui.lua:14904 + [x] 88% ScaleWindow imgui.lua:14921 [x] 100% IsWindowActiveAndVisible imgui.lua:9570 - [x] 93% ImGui::UpdateHoveredWindowAndCaptureFlags imgui.lua:13036 - [x] 55% SetupDrawListSharedData imgui.lua:13513 - [x] 88% ImGui::NewFrame imgui.lua:13759 - [x] 63% ChildWindowComparer imgui.lua:13679 - [x] 89% AddWindowToSortBuffer imgui.lua:13694 - [x] 86% AddWindowToDrawData imgui.lua:13708 - [x] 64% GetWindowDisplayLayer imgui.lua:13724 - [x] 100% AddRootWindowToDrawData imgui.lua:13730 - [x] 63% FlattenDrawDataIntoSingleLayer imgui.lua:13735 - [x] 83% InitViewportDrawData imgui.lua:13539 - [x] 52% ImGui::PushClipRect imgui.lua:13608 - [x] 42% ImGui::PopClipRect imgui.lua:13617 - [x] 60% ImGui::RenderDimmedBackgroundBehindWindow imgui.lua:18968 - [x] 83% ImGui::FindBottomMostVisibleWindowWithinBeginStack imgui.lua:18941 - [x] 80% ImGui::RenderDimmedBackgrounds imgui.lua:18989 - [x] 84% ImGui::EndFrame imgui.lua:14063 - [x] 74% ImGui::Render imgui.lua:14174 - [x] 17% ImGui::CalcTextSize imgui.lua:14247 - [x] 79% ImGui::FindHoveredWindowEx imgui.lua:12950 + [x] 93% ImGui::UpdateHoveredWindowAndCaptureFlags imgui.lua:13037 + [x] 80% SetupDrawListSharedData imgui.lua:13514 + [x] 87% ImGui::NewFrame imgui.lua:13769 + [x] 63% ChildWindowComparer imgui.lua:13689 + [x] 89% AddWindowToSortBuffer imgui.lua:13704 + [x] 86% AddWindowToDrawData imgui.lua:13718 + [x] 64% GetWindowDisplayLayer imgui.lua:13734 + [x] 100% AddRootWindowToDrawData imgui.lua:13740 + [x] 63% FlattenDrawDataIntoSingleLayer imgui.lua:13745 + [x] 83% InitViewportDrawData imgui.lua:13548 + [x] 52% ImGui::PushClipRect imgui.lua:13617 + [x] 42% ImGui::PopClipRect imgui.lua:13626 + [x] 60% ImGui::RenderDimmedBackgroundBehindWindow imgui.lua:18992 + [x] 83% ImGui::FindBottomMostVisibleWindowWithinBeginStack imgui.lua:18965 + [x] 80% ImGui::RenderDimmedBackgrounds imgui.lua:19013 + [x] 84% ImGui::EndFrame imgui.lua:14080 + [x] 74% ImGui::Render imgui.lua:14191 + [x] 17% ImGui::CalcTextSize imgui.lua:14264 + [x] 79% ImGui::FindHoveredWindowEx imgui.lua:12951 [x] 95% ImGui::IsItemActive imgui.lua:6979 [x] 97% ImGui::IsItemActivated imgui.lua:7020 [x] 95% ImGui::IsItemDeactivated imgui.lua:7006 @@ -326,21 +326,21 @@ imgui.cpp [x] 33% ImGui::UpdateWindowSkipRefresh imgui.lua:10741 [x] 92% SetWindowActiveForSkipRefresh imgui.lua:10793 [x] 82% ImGui::Begin imgui.lua:11702 - [x] 86% ImGui::End imgui.lua:12770 - [x] 84% ImGui::PushItemFlag imgui.lua:12859 - [x] 96% ImGui::PopItemFlag imgui.lua:12874 - [x] 79% ImGui::BeginDisabled imgui.lua:12889 - [x] 87% ImGui::EndDisabled imgui.lua:12905 - [x] 78% ImGui::BeginDisabledOverrideReenable imgui.lua:12921 - [x] 88% ImGui::EndDisabledOverrideReenable imgui.lua:12932 - [x] 54% ImGui::PushTextWrapPos imgui.lua:14701 - [x] 69% ImGui::PopTextWrapPos imgui.lua:14712 - [x] 100% GetCombinedRootWindow imgui.lua:14724 - [x] 100% ImGui::IsWindowChildOf imgui.lua:14743 - [x] 84% ImGui::IsWindowInBeginStack imgui.lua:14763 - [x] 100% ImGui::IsWindowWithinBeginStackOf imgui.lua:14775 - [x] 91% ImGui::IsWindowAbove imgui.lua:14793 - [x] 90% ImGui::IsWindowHovered imgui.lua:14819 + [x] 86% ImGui::End imgui.lua:12771 + [x] 84% ImGui::PushItemFlag imgui.lua:12860 + [x] 96% ImGui::PopItemFlag imgui.lua:12875 + [x] 79% ImGui::BeginDisabled imgui.lua:12890 + [x] 87% ImGui::EndDisabled imgui.lua:12906 + [x] 78% ImGui::BeginDisabledOverrideReenable imgui.lua:12922 + [x] 88% ImGui::EndDisabledOverrideReenable imgui.lua:12933 + [x] 54% ImGui::PushTextWrapPos imgui.lua:14718 + [x] 69% ImGui::PopTextWrapPos imgui.lua:14729 + [x] 100% GetCombinedRootWindow imgui.lua:14741 + [x] 100% ImGui::IsWindowChildOf imgui.lua:14760 + [x] 84% ImGui::IsWindowInBeginStack imgui.lua:14780 + [x] 100% ImGui::IsWindowWithinBeginStackOf imgui.lua:14792 + [x] 91% ImGui::IsWindowAbove imgui.lua:14810 + [x] 90% ImGui::IsWindowHovered imgui.lua:14836 [x] 67% ImGui::GetWindowDockID imgui.lua:4511 [x] 67% ImGui::IsWindowDocked imgui.lua:4515 [x] 100% ImGui::GetWindowWidth imgui.lua:10867 @@ -375,16 +375,16 @@ imgui.cpp [x] 100% ImGui::GetFontSize imgui.lua:11104 [x] 100% ImGui::GetFontTexUvWhitePixel imgui.lua:11109 [-] ImGui::SetWindowFontScale obsolete - [x] 93% ImGui::PushFocusScope imgui.lua:15909 - [x] 56% ImGui::PopFocusScope imgui.lua:15918 - [x] 91% ImGui::IsInNavFocusRoute imgui.lua:15930 - [x] 71% ImGui::SetNavFocusScope imgui.lua:15944 - [x] 76% ImGui::FocusItem imgui.lua:19157 - [x] 100% ImGui::ActivateItemByID imgui.lua:19170 - [x] 81% ImGui::SetKeyboardFocusHere imgui.lua:19129 - [x] 98% ImGui::SetItemDefaultFocus imgui.lua:15984 - [x] 91% ImGui::SetStateStorage imgui.lua:19188 - [x] 74% ImGui::GetStateStorage imgui.lua:19187 + [x] 93% ImGui::PushFocusScope imgui.lua:15926 + [x] 56% ImGui::PopFocusScope imgui.lua:15935 + [x] 91% ImGui::IsInNavFocusRoute imgui.lua:15947 + [x] 71% ImGui::SetNavFocusScope imgui.lua:15961 + [x] 76% ImGui::FocusItem imgui.lua:19181 + [x] 100% ImGui::ActivateItemByID imgui.lua:19194 + [x] 81% ImGui::SetKeyboardFocusHere imgui.lua:19153 + [x] 98% ImGui::SetItemDefaultFocus imgui.lua:16001 + [x] 91% ImGui::SetStateStorage imgui.lua:19212 + [x] 74% ImGui::GetStateStorage imgui.lua:19211 [x] 72% ImGui::IsRectVisible imgui.lua:6795 -- FONTS, TEXTURES @@ -451,17 +451,17 @@ imgui.cpp [x] 75% ImGui::IsAnyMouseDown imgui.lua:9031 [x] 87% ImGui::GetMouseDragDelta imgui.lua:9221 [x] 71% ImGui::ResetMouseDragDelta imgui.lua:9240 - [x] 100% ImGui::GetMouseCursor imgui.lua:14361 - [x] 100% ImGui::SetMouseCursor imgui.lua:14370 - [x] 92% UpdateAliasKey imgui.lua:13130 - [x] 42% GetMergedModsFromKeys imgui.lua:13138 - [x] 85% ImGui::UpdateKeyboardInputs imgui.lua:13151 - [x] 88% ImGui::UpdateMouseInputs imgui.lua:13229 - [x] 91% LockWheelingWindow imgui.lua:13337 - [x] 76% FindBestWheelingWindow imgui.lua:13358 - [x] 92% ImGui::UpdateMouseWheel imgui.lua:13408 - [x] 60% ImGui::SetNextFrameWantCaptureKeyboard imgui.lua:19192 - [x] 60% ImGui::SetNextFrameWantCaptureMouse imgui.lua:19193 + [x] 100% ImGui::GetMouseCursor imgui.lua:14378 + [x] 100% ImGui::SetMouseCursor imgui.lua:14387 + [x] 92% UpdateAliasKey imgui.lua:13131 + [x] 42% GetMergedModsFromKeys imgui.lua:13139 + [x] 85% ImGui::UpdateKeyboardInputs imgui.lua:13152 + [x] 88% ImGui::UpdateMouseInputs imgui.lua:13230 + [x] 91% LockWheelingWindow imgui.lua:13338 + [x] 76% FindBestWheelingWindow imgui.lua:13359 + [x] 92% ImGui::UpdateMouseWheel imgui.lua:13409 + [x] 60% ImGui::SetNextFrameWantCaptureKeyboard imgui.lua:19216 + [x] 60% ImGui::SetNextFrameWantCaptureMouse imgui.lua:19217 [ ] GetInputSourceName [ ] GetMouseSourceName [ ] DebugPrintInputEvent @@ -478,16 +478,16 @@ imgui.cpp -- ERROR CHECKING, STATE RECOVERY [ ] ImGui::DebugCheckVersionAndDataLayout - [x] 86% ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries imgui.lua:18904 + [x] 86% ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries imgui.lua:18928 [ ] ImGui::ErrorCheckNewFrameSanityChecks [ ] ImGui::ErrorCheckEndFrameSanityChecks - [x] 100% ImGui::ErrorRecoveryStoreState imgui.lua:19791 - [x] 91% ImGui::ErrorRecoveryTryToRecoverState imgui.lua:19809 - [x] 91% ImGui::ErrorRecoveryTryToRecoverWindowState imgui.lua:19838 - [x] 86% ImGui::ErrorLog imgui.lua:19927 - [x] 80% ImGui::ErrorCheckEndFrameFinalizeErrorTooltip imgui.lua:19971 - [x] 77% ImGui::BeginErrorTooltip imgui.lua:20009 - [x] 100% ImGui::EndErrorTooltip imgui.lua:20031 + [x] 100% ImGui::ErrorRecoveryStoreState imgui.lua:19815 + [x] 91% ImGui::ErrorRecoveryTryToRecoverState imgui.lua:19833 + [x] 91% ImGui::ErrorRecoveryTryToRecoverWindowState imgui.lua:19862 + [x] 86% ImGui::ErrorLog imgui.lua:19951 + [x] 80% ImGui::ErrorCheckEndFrameFinalizeErrorTooltip imgui.lua:19995 + [x] 77% ImGui::BeginErrorTooltip imgui.lua:20033 + [x] 100% ImGui::EndErrorTooltip imgui.lua:20055 -- ITEM SUBMISSION [x] 100% ImGui::KeepAliveID imgui.lua:6247 @@ -525,126 +525,126 @@ imgui.cpp [x] 91% ImGui::EndGroup imgui.lua:6839 -- SCROLLING - [x] 100% CalcScrollEdgeSnap imgui.lua:14943 - [x] 89% CalcNextScrollFromScrollTargetAndClamp imgui.lua:14954 - [x] 78% ImGui::ScrollToItem imgui.lua:15080 - [x] 86% ImGui::ScrollToRect imgui.lua:15088 - [x] 88% ImGui::ScrollToRectEx imgui.lua:14986 - [x] 74% ImGui::GetScrollX imgui.lua:15071 - [x] 74% ImGui::GetScrollY imgui.lua:15075 - [x] 74% ImGui::GetScrollMaxX imgui.lua:15067 - [x] 100% ImGui::GetScrollMaxY imgui.lua:15062 - [x] 75% ImGui::SetScrollX imgui.lua:15123 - [x] 75% ImGui::SetScrollY imgui.lua:15132 - [x] 78% ImGui::SetScrollFromPosX imgui.lua:15152 - [x] 78% ImGui::SetScrollFromPosY imgui.lua:15164 - [x] 95% ImGui::SetScrollHereX imgui.lua:15094 - [x] 95% ImGui::SetScrollHereY imgui.lua:15108 + [x] 100% CalcScrollEdgeSnap imgui.lua:14960 + [x] 89% CalcNextScrollFromScrollTargetAndClamp imgui.lua:14971 + [x] 78% ImGui::ScrollToItem imgui.lua:15097 + [x] 86% ImGui::ScrollToRect imgui.lua:15105 + [x] 88% ImGui::ScrollToRectEx imgui.lua:15003 + [x] 74% ImGui::GetScrollX imgui.lua:15088 + [x] 74% ImGui::GetScrollY imgui.lua:15092 + [x] 74% ImGui::GetScrollMaxX imgui.lua:15084 + [x] 100% ImGui::GetScrollMaxY imgui.lua:15079 + [x] 75% ImGui::SetScrollX imgui.lua:15140 + [x] 75% ImGui::SetScrollY imgui.lua:15149 + [x] 78% ImGui::SetScrollFromPosX imgui.lua:15169 + [x] 78% ImGui::SetScrollFromPosY imgui.lua:15181 + [x] 95% ImGui::SetScrollHereX imgui.lua:15111 + [x] 95% ImGui::SetScrollHereY imgui.lua:15125 -- TOOLTIPS - [x] 100% ImGui::BeginTooltip imgui.lua:15232 - [x] 100% ImGui::BeginItemTooltip imgui.lua:15236 - [x] 83% ImGui::BeginTooltipEx imgui.lua:15179 - [x] 73% ImGui::EndTooltip imgui.lua:15243 - [x] 21% ImGui::SetTooltip imgui.lua:15250 + [x] 100% ImGui::BeginTooltip imgui.lua:15249 + [x] 100% ImGui::BeginItemTooltip imgui.lua:15253 + [x] 83% ImGui::BeginTooltipEx imgui.lua:15196 + [x] 73% ImGui::EndTooltip imgui.lua:15260 + [x] 21% ImGui::SetTooltip imgui.lua:15267 [ ] ImGui::SetTooltipV - [x] 52% ImGui::SetItemTooltip imgui.lua:15262 + [x] 52% ImGui::SetItemTooltip imgui.lua:15279 [ ] ImGui::SetItemTooltipV -- POPUPS [x] 73% ImGui::IsPopupOpen imgui.lua:11540 [x] 82% ImGui::GetTopMostPopupModal imgui.lua:11575 - [x] 81% ImGui::GetTopMostAndVisiblePopupModal imgui.lua:18957 - [x] 79% ImGui::FindBlockingModal imgui.lua:15286 - [x] 26% ImGui::OpenPopup imgui.lua:15319 - [x] 86% ImGui::OpenPopupEx imgui.lua:15340 - [x] 73% ImGui::ClosePopupsOverWindow imgui.lua:15403 - [x] 80% ImGui::ClosePopupsExceptModals imgui.lua:19023 - [x] 62% ImGui::ClosePopupToLevel imgui.lua:15461 - [x] 81% ImGui::CloseCurrentPopup imgui.lua:15497 - [x] 81% ImGui::BeginPopupEx imgui.lua:15537 - [x] 80% ImGui::BeginPopupMenuEx imgui.lua:15560 - [x] 83% ImGui::BeginPopup imgui.lua:15586 - [x] 83% ImGui::BeginPopupModal imgui.lua:19041 - [x] 95% ImGui::EndPopup imgui.lua:15599 - [x] 38% ImGui::GetMouseButtonFromPopupFlags imgui.lua:15820 - [x] 94% ImGui::IsPopupOpenRequestForItem imgui.lua:15832 - [x] 85% ImGui::IsPopupOpenRequestForWindow imgui.lua:19071 - [x] 83% ImGui::OpenPopupOnItemClick imgui.lua:15849 - [x] 83% ImGui::BeginPopupContextItem imgui.lua:15883 - [x] 85% ImGui::BeginPopupContextWindow imgui.lua:19085 - [x] 88% ImGui::BeginPopupContextVoid imgui.lua:19095 - [x] 87% ImGui::FindBestWindowPosForPopupEx imgui.lua:15636 - [x] 87% ImGui::GetPopupAllowedExtentRect imgui.lua:15729 - [x] 92% ImGui::FindBestWindowPosForPopup imgui.lua:15752 + [x] 81% ImGui::GetTopMostAndVisiblePopupModal imgui.lua:18981 + [x] 79% ImGui::FindBlockingModal imgui.lua:15303 + [x] 26% ImGui::OpenPopup imgui.lua:15336 + [x] 86% ImGui::OpenPopupEx imgui.lua:15357 + [x] 73% ImGui::ClosePopupsOverWindow imgui.lua:15420 + [x] 80% ImGui::ClosePopupsExceptModals imgui.lua:19047 + [x] 62% ImGui::ClosePopupToLevel imgui.lua:15478 + [x] 81% ImGui::CloseCurrentPopup imgui.lua:15514 + [x] 81% ImGui::BeginPopupEx imgui.lua:15554 + [x] 80% ImGui::BeginPopupMenuEx imgui.lua:15577 + [x] 83% ImGui::BeginPopup imgui.lua:15603 + [x] 83% ImGui::BeginPopupModal imgui.lua:19065 + [x] 95% ImGui::EndPopup imgui.lua:15616 + [x] 38% ImGui::GetMouseButtonFromPopupFlags imgui.lua:15837 + [x] 94% ImGui::IsPopupOpenRequestForItem imgui.lua:15849 + [x] 85% ImGui::IsPopupOpenRequestForWindow imgui.lua:19095 + [x] 83% ImGui::OpenPopupOnItemClick imgui.lua:15866 + [x] 83% ImGui::BeginPopupContextItem imgui.lua:15900 + [x] 85% ImGui::BeginPopupContextWindow imgui.lua:19109 + [x] 88% ImGui::BeginPopupContextVoid imgui.lua:19119 + [x] 87% ImGui::FindBestWindowPosForPopupEx imgui.lua:15653 + [x] 87% ImGui::GetPopupAllowedExtentRect imgui.lua:15746 + [x] 92% ImGui::FindBestWindowPosForPopup imgui.lua:15769 -- WINDOW FOCUS [x] 51% ImGui::SetWindowFocus imgui.lua:11016 [x] 71% ImGui::SetNextWindowFocus imgui.lua:11400 - [x] 87% ImGui::IsWindowFocused imgui.lua:14875 + [x] 87% ImGui::IsWindowFocused imgui.lua:14892 [x] 94% ImGui::FindWindowFocusIndex imgui.lua:7203 [x] 81% ImGui::UpdateWindowInFocusOrderList imgui.lua:7238 [x] 82% ImGui::BringWindowToFocusFront imgui.lua:7263 [x] 56% ImGui::BringWindowToDisplayFront imgui.lua:7289 - [x] 54% ImGui::BringWindowToDisplayBack imgui.lua:18931 + [x] 54% ImGui::BringWindowToDisplayBack imgui.lua:18955 [x] 35% ImGui::BringWindowToDisplayBehind imgui.lua:7310 - [x] 41% ImGui::FindWindowDisplayIndex imgui.lua:18925 + [x] 41% ImGui::FindWindowDisplayIndex imgui.lua:18949 [x] 87% ImGui::FocusWindow imgui.lua:7124 [x] 84% ImGui::FocusTopMostWindowUnderOne imgui.lua:7322 -- KEYBOARD/GAMEPAD NAVIGATION - [x] 93% ImGui::SetNavCursorVisible imgui.lua:19176 - [x] 94% ImGui::SetNavCursorVisibleAfterMove imgui.lua:16005 - [x] 80% ImGui::SetNavWindow imgui.lua:16021 - [x] 100% ImGui::NavHighlightActivated imgui.lua:16101 - [x] 100% ImGui::NavClearPreferredPosForAxis imgui.lua:16035 - [x] 95% ImGui::SetNavID imgui.lua:16044 + [x] 93% ImGui::SetNavCursorVisible imgui.lua:19200 + [x] 94% ImGui::SetNavCursorVisibleAfterMove imgui.lua:16022 + [x] 80% ImGui::SetNavWindow imgui.lua:16038 + [x] 100% ImGui::NavHighlightActivated imgui.lua:16118 + [x] 100% ImGui::NavClearPreferredPosForAxis imgui.lua:16052 + [x] 95% ImGui::SetNavID imgui.lua:16061 [x] 96% ImGui::SetFocusID imgui.lua:7362 - [x] 90% ImGetDirQuadrantFromDelta imgui.lua:17071 - [x] 100% NavScoreItemDistInterval imgui.lua:17078 - [x] 77% ImGui::NavScoreItem imgui.lua:17092 - [x] 93% ImGui::NavApplyItemToResult imgui.lua:17198 - [x] 100% ImGui::NavUpdateCurrentWindowIsScrollPushableX imgui.lua:17525 - [x] 94% ImGui::NavProcessItem imgui.lua:17290 - [x] 90% ImGui::NavProcessItemForTabbingRequest imgui.lua:17230 - [x] 100% ImGui::NavMoveRequestButNoResultYet imgui.lua:17066 - [x] 89% ImGui::NavMoveRequestSubmit imgui.lua:16544 - [x] 92% ImGui::NavMoveRequestResolveWithLastItem imgui.lua:17213 - [x] 92% ImGui::NavMoveRequestResolveWithPastTreeNode imgui.lua:17534 - [x] 93% ImGui::NavMoveRequestCancel imgui.lua:17545 - [x] 95% ImGui::NavMoveRequestForward imgui.lua:17557 - [x] 78% ImGui::NavMoveRequestTryWrapping imgui.lua:17572 - [x] 93% ImGui::NavSaveLastChildNavWindowIntoParent imgui.lua:16062 - [x] 57% ImGui::NavRestoreLastChildNavWindow imgui.lua:16075 - [x] 86% ImGui::NavRestoreLayer imgui.lua:16083 - [x] 100% ImGui::NavUpdateAnyRequestFlag imgui.lua:16107 - [x] 88% ImGui::NavInitWindow imgui.lua:16118 - [x] 97% ImGui::NavCalcPreferredRefPosSource imgui.lua:16150 - [x] 94% ImGui::NavCalcPreferredRefPos imgui.lua:16169 - [x] 91% ImGui::GetNavTweakPressedAmount imgui.lua:16211 - [x] 85% ImGui::NavUpdate imgui.lua:16813 - [x] 88% ImGui::NavInitRequestApplyResult imgui.lua:17364 - [x] 81% NavBiasScoringRect imgui.lua:16573 - [x] 83% ImGui::NavUpdateCreateMoveRequest imgui.lua:16674 - [x] 87% ImGui::NavUpdateCreateTabbingRequest imgui.lua:16784 - [x] 85% ImGui::NavMoveRequestApplyResult imgui.lua:17395 - [x] 91% ImGui::NavUpdateCancelRequest imgui.lua:17021 - [x] 83% ImGui::NavUpdateContextMenuRequest imgui.lua:16509 - [x] 90% ImGui::NavUpdatePageUpPageDown imgui.lua:16605 - [x] 81% ImGui::NavEndFrame imgui.lua:19311 - [x] 83% ImGui::NavUpdateCreateWrappingRequest imgui.lua:19255 + [x] 90% ImGetDirQuadrantFromDelta imgui.lua:17088 + [x] 100% NavScoreItemDistInterval imgui.lua:17095 + [x] 77% ImGui::NavScoreItem imgui.lua:17109 + [x] 93% ImGui::NavApplyItemToResult imgui.lua:17215 + [x] 100% ImGui::NavUpdateCurrentWindowIsScrollPushableX imgui.lua:17542 + [x] 94% ImGui::NavProcessItem imgui.lua:17307 + [x] 90% ImGui::NavProcessItemForTabbingRequest imgui.lua:17247 + [x] 100% ImGui::NavMoveRequestButNoResultYet imgui.lua:17083 + [x] 89% ImGui::NavMoveRequestSubmit imgui.lua:16561 + [x] 92% ImGui::NavMoveRequestResolveWithLastItem imgui.lua:17230 + [x] 92% ImGui::NavMoveRequestResolveWithPastTreeNode imgui.lua:17551 + [x] 93% ImGui::NavMoveRequestCancel imgui.lua:17562 + [x] 95% ImGui::NavMoveRequestForward imgui.lua:17574 + [x] 78% ImGui::NavMoveRequestTryWrapping imgui.lua:17589 + [x] 93% ImGui::NavSaveLastChildNavWindowIntoParent imgui.lua:16079 + [x] 57% ImGui::NavRestoreLastChildNavWindow imgui.lua:16092 + [x] 86% ImGui::NavRestoreLayer imgui.lua:16100 + [x] 100% ImGui::NavUpdateAnyRequestFlag imgui.lua:16124 + [x] 88% ImGui::NavInitWindow imgui.lua:16135 + [x] 97% ImGui::NavCalcPreferredRefPosSource imgui.lua:16167 + [x] 94% ImGui::NavCalcPreferredRefPos imgui.lua:16186 + [x] 91% ImGui::GetNavTweakPressedAmount imgui.lua:16228 + [x] 85% ImGui::NavUpdate imgui.lua:16830 + [x] 88% ImGui::NavInitRequestApplyResult imgui.lua:17381 + [x] 81% NavBiasScoringRect imgui.lua:16590 + [x] 83% ImGui::NavUpdateCreateMoveRequest imgui.lua:16691 + [x] 87% ImGui::NavUpdateCreateTabbingRequest imgui.lua:16801 + [x] 85% ImGui::NavMoveRequestApplyResult imgui.lua:17412 + [x] 91% ImGui::NavUpdateCancelRequest imgui.lua:17038 + [x] 83% ImGui::NavUpdateContextMenuRequest imgui.lua:16526 + [x] 90% ImGui::NavUpdatePageUpPageDown imgui.lua:16622 + [x] 81% ImGui::NavEndFrame imgui.lua:19335 + [x] 83% ImGui::NavUpdateCreateWrappingRequest imgui.lua:19279 [x] 85% ImGui::IsWindowNavFocusable imgui.lua:7217 [x] 67% FindWindowNavFocusable imgui.lua:7225 - [x] 86% NavUpdateWindowingTarget imgui.lua:16239 - [x] 92% ImGui::NavUpdateWindowingApplyFocus imgui.lua:16262 - [x] 88% ImGui::NavUpdateWindowing imgui.lua:16300 - [x] 67% GetFallbackWindowNameForWindowingList imgui.lua:19212 - [x] 84% ImGui::NavUpdateWindowingOverlay imgui.lua:19226 + [x] 86% NavUpdateWindowingTarget imgui.lua:16256 + [x] 92% ImGui::NavUpdateWindowingApplyFocus imgui.lua:16279 + [x] 88% ImGui::NavUpdateWindowing imgui.lua:16317 + [x] 67% GetFallbackWindowNameForWindowingList imgui.lua:19236 + [x] 84% ImGui::NavUpdateWindowingOverlay imgui.lua:19250 -- DRAG AND DROP [x] 55% ImGui::IsDragDropActive imgui.lua:525 [x] 73% ImGui::ClearDragDrop imgui.lua:529 - [x] 82% ImGui::BeginTooltipHidden imgui.lua:19108 + [x] 82% ImGui::BeginTooltipHidden imgui.lua:19132 [x] 88% ImGui::BeginDragDropSource imgui.lua:547 [x] 86% ImGui::EndDragDropSource imgui.lua:648 [x] 62% ImGui::SetDragDropPayload imgui.lua:665 @@ -697,38 +697,38 @@ imgui.cpp [x] 78% WindowSettingsHandler_WriteAll imgui.lua:6001 -- LOCALIZATION - [x] 84% ImGui::LocalizeRegisterEntries imgui.lua:17598 + [x] 84% ImGui::LocalizeRegisterEntries imgui.lua:17615 -- VIEWPORTS, PLATFORM WINDOWS - [x] 83% ImGuiViewport::GetDebugName imgui.lua:17632 - [x] 76% ImGuiPlatformIO::ClearPlatformHandlers imgui.lua:17637 - [x] 69% ImGuiPlatformIO::ClearRendererHandlers imgui.lua:17651 - [x] 78% ImGui::GetMainViewport imgui.lua:17660 - [x] 88% ImGui::FindViewportByID imgui.lua:17669 + [x] 83% ImGuiViewport::GetDebugName imgui.lua:17649 + [x] 76% ImGuiPlatformIO::ClearPlatformHandlers imgui.lua:17654 + [x] 69% ImGuiPlatformIO::ClearRendererHandlers imgui.lua:17668 + [x] 78% ImGui::GetMainViewport imgui.lua:17677 + [x] 88% ImGui::FindViewportByID imgui.lua:17686 [ ] ImGui::FindViewportByPlatformHandle - [x] 98% ImGui::SetCurrentViewport imgui.lua:17681 - [x] 99% ImGui::SetWindowViewport imgui.lua:17709 - [x] 86% ImGui::GetWindowAlwaysWantOwnViewport imgui.lua:17722 - [x] 86% IsViewportAbove imgui.lua:17744 - [x] 89% ImGui::UpdateTryMergeWindowIntoHostViewport imgui.lua:17769 - [x] 83% ImGui::UpdateTryMergeWindowIntoHostViewports imgui.lua:17829 - [x] 88% ImGui::TranslateWindowsInViewport imgui.lua:17841 - [x] 94% ImGui::ScaleWindowsInViewport imgui.lua:14915 - [x] 85% ImGui::FindHoveredViewportFromPlatformWindowStack imgui.lua:17867 - [x] 87% ImGui::UpdateViewportsNewFrame imgui.lua:17884 - [x] 78% ImGui::UpdateViewportsEndFrame imgui.lua:18171 - [x] 81% ImGui::AddUpdateViewport imgui.lua:18205 - [x] 80% ImGui::DestroyViewport imgui.lua:18285 - [x] 93% ImGui::WindowSelectViewport imgui.lua:18311 - [x] 73% ImGui::WindowSyncOwnedViewport imgui.lua:18437 - [x] 88% ImGui::UpdatePlatformWindows imgui.lua:18541 - [x] 85% ImGui::RenderPlatformWindowsDefault imgui.lua:18659 - [x] 87% ImGui::FindPlatformMonitorForPos imgui.lua:18691 - [x] 78% ImGui::FindPlatformMonitorForRect imgui.lua:18707 - [x] 93% ImGui::UpdateViewportPlatformMonitor imgui.lua:18748 - [x] 89% ImGui::GetViewportPlatformMonitor imgui.lua:18755 - [x] 97% ImGui::DestroyPlatformWindow imgui.lua:18767 - [x] 77% ImGui::DestroyPlatformWindows imgui.lua:18793 + [x] 98% ImGui::SetCurrentViewport imgui.lua:17698 + [x] 99% ImGui::SetWindowViewport imgui.lua:17726 + [x] 86% ImGui::GetWindowAlwaysWantOwnViewport imgui.lua:17739 + [x] 86% IsViewportAbove imgui.lua:17761 + [x] 89% ImGui::UpdateTryMergeWindowIntoHostViewport imgui.lua:17786 + [x] 83% ImGui::UpdateTryMergeWindowIntoHostViewports imgui.lua:17846 + [x] 88% ImGui::TranslateWindowsInViewport imgui.lua:17858 + [x] 94% ImGui::ScaleWindowsInViewport imgui.lua:14932 + [x] 85% ImGui::FindHoveredViewportFromPlatformWindowStack imgui.lua:17884 + [x] 87% ImGui::UpdateViewportsNewFrame imgui.lua:17901 + [x] 78% ImGui::UpdateViewportsEndFrame imgui.lua:18188 + [x] 84% ImGui::AddUpdateViewport imgui.lua:18222 + [x] 80% ImGui::DestroyViewport imgui.lua:18309 + [x] 93% ImGui::WindowSelectViewport imgui.lua:18335 + [x] 73% ImGui::WindowSyncOwnedViewport imgui.lua:18461 + [x] 88% ImGui::UpdatePlatformWindows imgui.lua:18565 + [x] 85% ImGui::RenderPlatformWindowsDefault imgui.lua:18683 + [x] 87% ImGui::FindPlatformMonitorForPos imgui.lua:18715 + [x] 78% ImGui::FindPlatformMonitorForRect imgui.lua:18731 + [x] 93% ImGui::UpdateViewportPlatformMonitor imgui.lua:18772 + [x] 89% ImGui::GetViewportPlatformMonitor imgui.lua:18779 + [x] 97% ImGui::DestroyPlatformWindow imgui.lua:18791 + [x] 77% ImGui::DestroyPlatformWindows imgui.lua:18817 -- DOCKING [x] 61% ImGuiDockRequest::ImGuiDockRequest imgui.lua:1019 @@ -845,47 +845,47 @@ imgui.cpp [ ] InitializeDefaultPlatformHandlers -- METRICS/DEBUGGER WINDOW - [x] 97% MetricsHelpMarker imgui.lua:20067 = MetricsHelpMarker - [x] 93% ImGui::DebugRenderViewportThumbnail imgui.lua:18816 - [x] 92% RenderViewportsThumbnails imgui.lua:18855 + [x] 97% MetricsHelpMarker imgui.lua:20091 = MetricsHelpMarker + [x] 93% ImGui::DebugRenderViewportThumbnail imgui.lua:18840 + [x] 92% RenderViewportsThumbnails imgui.lua:18879 [ ] ViewportComparerByLastFocusedStampCount [ ] ImGui::DebugRenderKeyboardPreview - [x] 52% ImGui::DebugTextEncoding imgui.lua:20969 + [x] 52% ImGui::DebugTextEncoding imgui.lua:20993 [ ] DebugFlashStyleColorStop [ ] ImGui::DebugFlashStyleColor [ ] ImGui::UpdateDebugToolFlashStyleColor [ ] ImGui::DebugTextureIDToU64 [ ] FormatTextureRefForDebugDisplay - [x] 52% ImGui::ShowFontAtlas imgui.lua:20268 - [x] 84% ImGui::DebugNodeTexture imgui.lua:20235 - [x] 44% ImGui::ShowMetricsWindow imgui.lua:21034 + [x] 52% ImGui::ShowFontAtlas imgui.lua:20292 + [x] 84% ImGui::DebugNodeTexture imgui.lua:20259 + [x] 44% ImGui::ShowMetricsWindow imgui.lua:21058 [ ] ImGui::DebugBreakClearData [ ] ImGui::DebugBreakButtonTooltip [ ] ImGui::DebugBreakButton - [x] 87% ImGui::DebugNodeColumns imgui.lua:20613 - [x] 48% DebugNodeDockNodeFlags imgui.lua:20801 - [x] 79% ImGui::DebugNodeDockNode imgui.lua:20818 - [x] 28% ImGui::DebugNodeDrawList imgui.lua:20635 + [x] 87% ImGui::DebugNodeColumns imgui.lua:20637 + [x] 48% DebugNodeDockNodeFlags imgui.lua:20825 + [x] 79% ImGui::DebugNodeDockNode imgui.lua:20842 + [x] 28% ImGui::DebugNodeDrawList imgui.lua:20659 [ ] ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox [ ] CalcFontGlyphSrcOverlapMask - [x] 60% ImGui::DebugNodeFont imgui.lua:20158 - [x] 59% ImGui::DebugNodeFontGlyphsForSrcMask imgui.lua:20103 - [x] 89% ImGui::DebugNodeFontGlyph imgui.lua:20089 - [x] 37% ImGui::DebugNodeStorage imgui.lua:20623 - [x] 68% ImGui::DebugNodeTabBar imgui.lua:20696 - [x] 62% ImGui::DebugNodeViewport imgui.lua:20951 + [x] 60% ImGui::DebugNodeFont imgui.lua:20182 + [x] 59% ImGui::DebugNodeFontGlyphsForSrcMask imgui.lua:20127 + [x] 89% ImGui::DebugNodeFontGlyph imgui.lua:20113 + [x] 37% ImGui::DebugNodeStorage imgui.lua:20647 + [x] 68% ImGui::DebugNodeTabBar imgui.lua:20720 + [x] 62% ImGui::DebugNodeViewport imgui.lua:20975 [ ] ImGui::DebugNodePlatformMonitor - [x] 70% ImGui::DebugNodeWindow imgui.lua:20862 - [x] 87% ImGui::DebugNodeWindowSettings imgui.lua:20920 - [x] 73% ImGui::DebugNodeWindowsList imgui.lua:20926 - [x] 67% ImGui::DebugNodeWindowsListByBeginStackParent imgui.lua:20937 + [x] 70% ImGui::DebugNodeWindow imgui.lua:20886 + [x] 87% ImGui::DebugNodeWindowSettings imgui.lua:20944 + [x] 73% ImGui::DebugNodeWindowsList imgui.lua:20950 + [x] 67% ImGui::DebugNodeWindowsListByBeginStackParent imgui.lua:20961 -- DEBUG LOG WINDOW - [x] 5% ImGui::DebugLog imgui.lua:19908 + [x] 5% ImGui::DebugLog imgui.lua:19932 [ ] ImGui::DebugLogV - [x] 93% SameLineOrWrap imgui.lua:20340 - [x] 61% ShowDebugLogFlag imgui.lua:20347 - [x] 65% ImGui::ShowDebugLogWindow imgui.lua:20360 + [x] 93% SameLineOrWrap imgui.lua:20364 + [x] 61% ShowDebugLogFlag imgui.lua:20371 + [x] 65% ImGui::ShowDebugLogWindow imgui.lua:20384 [ ] ImGui::DebugTextUnformattedWithLocateItem -- OTHER DEBUG TOOLS (ITEM PICKER, ID STACK TOOL) @@ -897,13 +897,13 @@ imgui.cpp [ ] ImGui::DebugLocateItemResolveWithLastItem [ ] ImGui::DebugStartItemPicker [ ] ImGui::UpdateDebugToolItemPicker - [x] 75% DebugItemPathQuery_UpdateAndGetHookId imgui.lua:20431 - [x] 49% ImGui::UpdateDebugToolItemPathQuery imgui.lua:20460 - [x] 47% ImGui::DebugHookIdInfo imgui.lua:20472 + [x] 75% DebugItemPathQuery_UpdateAndGetHookId imgui.lua:20455 + [x] 49% ImGui::UpdateDebugToolItemPathQuery imgui.lua:20484 + [x] 47% ImGui::DebugHookIdInfo imgui.lua:20496 [ ] DebugItemPathQuery_FormatLevelInfo [ ] DebugItemPathQuery_GetResultAsPath - [x] 51% ImGui::ShowIDStackToolWindow imgui.lua:20531 - [x] 78% ImGui::ShowFontSelector imgui.lua:20073 + [x] 51% ImGui::ShowIDStackToolWindow imgui.lua:20555 + [x] 78% ImGui::ShowFontSelector imgui.lua:20097 ==================================================================================================== imgui_widgets.cpp @@ -970,7 +970,7 @@ imgui_widgets.cpp -- Widgets: ComboBox [x] 100% CalcMaxPopupHeightFromItemCount imgui_widgets.lua:1945 - [x] 80% ImGui::BeginCombo imgui_widgets.lua:1956 + [x] 81% ImGui::BeginCombo imgui_widgets.lua:1956 [x] 85% ImGui::BeginComboPopup imgui_widgets.lua:2072 [x] 65% ImGui::EndCombo imgui_widgets.lua:2145 [x] 72% ImGui::BeginComboPreview imgui_widgets.lua:2163 @@ -1076,7 +1076,7 @@ imgui_widgets.cpp [x] 79% STB_TEXTEDIT_DELETECHARS imgui_widgets.lua:4738 [x] 87% STB_TEXTEDIT_INSERTCHARS imgui_widgets.lua:4752 [x] 81% stb_textedit_replace imgui_widgets.lua:4795 - [x] 12% ImGuiInputTextState::ImGuiInputTextState imgui_internal.lua:1663 + [x] 12% ImGuiInputTextState::ImGuiInputTextState imgui_internal.lua:1705 [-] ImGuiInputTextState::~ImGuiInputTextState c++-only [x] 93% ImGuiInputTextState::OnKeyPressed imgui_widgets.lua:4823 [x] 63% ImGuiInputTextState::OnCharPressed imgui_widgets.lua:4838 @@ -1093,9 +1093,9 @@ imgui_widgets.cpp [ ] ImGuiInputTextState::ReloadUserBufAndSelectAll [ ] ImGuiInputTextState::ReloadUserBufAndKeepSelection [ ] ImGuiInputTextState::ReloadUserBufAndMoveToEnd - [x] 18% ImGuiInputTextCallbackData::ImGuiInputTextCallbackData imgui_h.lua:2405 - [x] 61% ImGuiInputTextCallbackData::DeleteChars imgui_h.lua:2409 - [x] 33% ImGuiInputTextCallbackData::InsertChars imgui_h.lua:2426 + [x] 18% ImGuiInputTextCallbackData::ImGuiInputTextCallbackData imgui_h.lua:2468 + [x] 61% ImGuiInputTextCallbackData::DeleteChars imgui_h.lua:2472 + [x] 33% ImGuiInputTextCallbackData::InsertChars imgui_h.lua:2489 [x] 92% ImGui::PushPasswordFont imgui_widgets.lua:4869 [x] 98% ImGui::PopPasswordFont imgui_widgets.lua:4884 [x] 80% InputTextFilterCharacter imgui_widgets.lua:4904 @@ -1115,7 +1115,7 @@ imgui_widgets.cpp [x] 70% ImGui::ColorPicker3 imgui_widgets.lua:6691 [x] 98% RenderArrowsForVerticalBar imgui_widgets.lua:6719 [x] 80% ImGui::ColorPicker4 imgui_widgets.lua:6738 - [x] 68% ImGui::ColorButton imgui_widgets.lua:7165 + [x] 69% ImGui::ColorButton imgui_widgets.lua:7165 [-] ImGui::SetColorEditOptions obsolete [x] 73% ImGui::ColorTooltip imgui_widgets.lua:7260 [x] 72% ImGui::ColorEditOptionsPopup imgui_widgets.lua:7304 @@ -1219,7 +1219,7 @@ imgui_widgets.cpp -- Widgets: BeginTabBar, EndTabBar, etc. [x] 19% ImGuiTabBarSection::ImGuiTabBarSection imgui_widgets.lua:10691 - [x] 16% ImGuiTabBar::ImGuiTabBar imgui_internal.lua:2168 + [x] 16% ImGuiTabBar::ImGuiTabBar imgui_internal.lua:2210 [x] 65% TabItemGetSectionIdx imgui_widgets.lua:10658 [ ] TabItemComparerBySection [ ] TabItemComparerByBeginOrder @@ -1260,7 +1260,7 @@ imgui_widgets.cpp [x] 79% ImGui::TabItemEx imgui_widgets.lua:11570 [x] 88% ImGui::SetTabItemClosed imgui_widgets.lua:11856 [x] 79% ImGui::TabItemCalcSize imgui_widgets.lua:11878 - [x] 91% ImGui::TabItemBackground imgui_widgets.lua:11894 + [x] 92% ImGui::TabItemBackground imgui_widgets.lua:11894 [x] 86% ImGui::TabItemLabelAndCloseButton imgui_widgets.lua:11915 ==================================================================================================== @@ -1268,87 +1268,87 @@ imgui_draw.cpp ==================================================================================================== -- Style functions - [x] 81% ImGui::StyleColorsDark imgui_draw.lua:130 - [x] 81% ImGui::StyleColorsClassic imgui_draw.lua:200 - [x] 82% ImGui::StyleColorsLight imgui_draw.lua:271 + [x] 81% ImGui::StyleColorsDark imgui_draw.lua:129 + [x] 81% ImGui::StyleColorsClassic imgui_draw.lua:199 + [x] 82% ImGui::StyleColorsLight imgui_draw.lua:270 -- ImDrawList - [x] 30% ImDrawListSharedData::ImDrawListSharedData imgui_internal.lua:1092 + [x] 30% ImDrawListSharedData::ImDrawListSharedData imgui_internal.lua:1120 [-] ImDrawListSharedData::~ImDrawListSharedData c++-only - [x] 57% ImDrawListSharedData::SetCircleTessellationMaxError imgui_draw.lua:3632 - [x] 7% ImDrawList::ImDrawList imgui_h.lua:653 + [x] 57% ImDrawListSharedData::SetCircleTessellationMaxError imgui_draw.lua:3744 + [x] 6% ImDrawList::ImDrawList imgui_h.lua:657 [-] ImDrawList::~ImDrawList c++-only - [x] 88% ImDrawList::_SetDrawListSharedData imgui_draw.lua:3649 - [x] 39% ImDrawList::_ResetForNewFrame imgui_draw.lua:3662 - [x] 61% ImDrawList::_ClearFreeMemory imgui_draw.lua:3689 + [x] 88% ImDrawList::_SetDrawListSharedData imgui_draw.lua:3761 + [x] 43% ImDrawList::_ResetForNewFrame imgui_draw.lua:3774 + [x] 64% ImDrawList::_ClearFreeMemory imgui_draw.lua:3802 [ ] ImDrawList::CloneOutput - [x] 77% ImDrawList::AddDrawCmd imgui_draw.lua:3707 - [x] 88% ImDrawList::_PopUnusedDrawCmd imgui_draw.lua:3724 - [x] 57% ImDrawList::AddCallback imgui_draw.lua:3738 - [x] 42% ImDrawList::_TryMergeDrawCmds imgui_draw.lua:3813 - [x] 33% ImDrawList::_OnChangedClipRect imgui_draw.lua:3825 - [x] 55% ImDrawList::_OnChangedTexture imgui_draw.lua:3852 - [x] 91% ImDrawList::_OnChangedVtxOffset imgui_draw.lua:3878 - [x] 57% ImDrawList::_CalcCircleAutoSegmentCount imgui_draw.lua:4966 - [x] 86% ImDrawList::PushClipRect imgui_draw.lua:4978 - [x] 60% ImDrawList::PushClipRectFullScreen imgui_draw.lua:5001 - [x] 39% ImDrawList::PopClipRect imgui_draw.lua:5006 - [x] 67% ImDrawList::PushTexture imgui_draw.lua:4189 - [x] 81% ImDrawList::PopTexture imgui_draw.lua:4198 - [ ] ImDrawList::PushDrawFlag - [ ] ImDrawList::PopDrawFlag - [x] 83% ImDrawList::_SetTexture imgui_draw.lua:4205 - [ ] ImDrawList::_SetPixelDensity - [x] 55% ImDrawList::PrimReserve imgui_draw.lua:4247 = PrimReserve - [x] 68% ImDrawList::PrimUnreserve imgui_draw.lua:4250 - [x] 8% ImDrawList::PrimRect imgui_draw.lua:4297 - [x] 13% ImDrawList::PrimRectUV imgui_draw.lua:4306 + [x] 77% ImDrawList::AddDrawCmd imgui_draw.lua:3821 + [x] 88% ImDrawList::_PopUnusedDrawCmd imgui_draw.lua:3838 + [x] 57% ImDrawList::AddCallback imgui_draw.lua:3852 + [x] 42% ImDrawList::_TryMergeDrawCmds imgui_draw.lua:3927 + [x] 33% ImDrawList::_OnChangedClipRect imgui_draw.lua:3939 + [x] 55% ImDrawList::_OnChangedTexture imgui_draw.lua:3966 + [x] 91% ImDrawList::_OnChangedVtxOffset imgui_draw.lua:3992 + [x] 57% ImDrawList::_CalcCircleAutoSegmentCount imgui_draw.lua:6209 + [x] 86% ImDrawList::PushClipRect imgui_draw.lua:6221 + [x] 60% ImDrawList::PushClipRectFullScreen imgui_draw.lua:6244 + [x] 39% ImDrawList::PopClipRect imgui_draw.lua:6249 + [x] 67% ImDrawList::PushTexture imgui_draw.lua:4208 + [x] 81% ImDrawList::PopTexture imgui_draw.lua:4217 + [x] 62% ImDrawList::PushDrawFlag imgui_draw.lua:4226 + [x] 85% ImDrawList::PopDrawFlag imgui_draw.lua:4238 + [x] 83% ImDrawList::_SetTexture imgui_draw.lua:4245 + [x] 89% ImDrawList::_SetPixelDensity imgui_draw.lua:4255 + [x] 57% ImDrawList::PrimReserve imgui_draw.lua:4295 = PrimReserve + [x] 68% ImDrawList::PrimUnreserve imgui_draw.lua:4298 + [x] 8% ImDrawList::PrimRect imgui_draw.lua:4345 + [x] 13% ImDrawList::PrimRectUV imgui_draw.lua:4354 [ ] ImDrawList::PrimQuadUV - [ ] ImDrawList::_CalculateCenterBiasedOffset - [ ] ImAlphaMultiply - [ ] ImDrawList::_SelectLineTexture - [ ] ImDrawList::_AddPolyline - [x] 30% ImDrawList::AddConvexPolyFilled imgui_draw.lua:4100 - [ ] CalcCornerArcStepAndCount - [x] 63% ImDrawList::_PathArcToFastEx imgui_draw.lua:5123 - [x] 63% ImDrawList::_PathArcToN imgui_draw.lua:5143 - [x] 10% ImDrawList::PathArcToFast imgui_draw.lua:5129 - [x] 43% ImDrawList::PathArcTo imgui_draw.lua:5169 - [x] 61% ImDrawList::PathEllipticalArcTo imgui_draw.lua:5758 - [x] 88% ImBezierCubicCalc imgui_draw.lua:5770 - [x] 89% ImBezierQuadraticCalc imgui_draw.lua:5776 - [x] 85% PathBezierCubicCurveToCasteljau imgui_draw.lua:5783 - [x] 90% PathBezierQuadraticCurveToCasteljau imgui_draw.lua:5801 - [x] 81% ImDrawList::PathBezierCubicCurveTo imgui_draw.lua:5815 - [x] 78% ImDrawList::PathBezierQuadraticCurveTo imgui_draw.lua:5825 - [x] 7% ImDrawList::PathRect imgui_draw.lua:4617 - [ ] ImDrawList::_AddLine - [x] 38% ImDrawList::AddLine imgui_draw.lua:4712 - [x] 19% ImDrawList::AddLineH imgui_draw.lua:4725 - [x] 19% ImDrawList::AddLineV imgui_draw.lua:4741 + [x] 83% ImDrawList::_CalculateCenterBiasedOffset imgui_draw.lua:4392 = _CalculateCenterBiasedOffset + [x] 77% ImAlphaMultiply imgui_draw.lua:3656 + [x] 72% ImDrawList::_SelectLineTexture imgui_draw.lua:4427 = _SelectLineTexture + [x] 37% ImDrawList::_AddPolyline imgui_draw.lua:4440 + [x] 43% ImDrawList::AddConvexPolyFilled imgui_draw.lua:4810 + [x] 59% CalcCornerArcStepAndCount imgui_draw.lua:3723 + [x] 64% ImDrawList::_PathArcToFastEx imgui_draw.lua:6368 + [x] 63% ImDrawList::_PathArcToN imgui_draw.lua:6388 + [x] 10% ImDrawList::PathArcToFast imgui_draw.lua:6374 + [x] 46% ImDrawList::PathArcTo imgui_draw.lua:6414 + [x] 61% ImDrawList::PathEllipticalArcTo imgui_draw.lua:7011 + [x] 88% ImBezierCubicCalc imgui_draw.lua:7023 + [x] 89% ImBezierQuadraticCalc imgui_draw.lua:7029 + [x] 85% PathBezierCubicCurveToCasteljau imgui_draw.lua:7036 + [x] 90% PathBezierQuadraticCurveToCasteljau imgui_draw.lua:7054 + [x] 91% ImDrawList::PathBezierCubicCurveTo imgui_draw.lua:7068 + [x] 90% ImDrawList::PathBezierQuadraticCurveTo imgui_draw.lua:7080 + [x] 8% ImDrawList::PathRect imgui_draw.lua:5060 + [x] 46% ImDrawList::_AddLine imgui_draw.lua:5192 + [x] 4% ImDrawList::AddLine imgui_draw.lua:5201 + [x] 9% ImDrawList::AddLineH imgui_draw.lua:5229 + [x] 9% ImDrawList::AddLineV imgui_draw.lua:5285 [ ] ImDrawList::_AddRectBaked - [ ] ImDrawList::_AddRectTinyRounding - [x] 3% ImDrawList::AddRect imgui_draw.lua:4674 + [x] 42% ImDrawList::_AddRectTinyRounding imgui_draw.lua:5347 + [x] 15% ImDrawList::AddRect imgui_draw.lua:5473 [ ] ImDrawList::_AddRectFilledBaked - [x] 11% ImDrawList::AddRectFilled imgui_draw.lua:4622 - [x] 26% ImDrawList::AddRectFilledMultiColor imgui_draw.lua:4645 - [x] 43% ImDrawList::AddQuad imgui_draw.lua:4763 - [x] 55% ImDrawList::AddQuadFilled imgui_draw.lua:4774 - [x] 47% ImDrawList::AddTriangle imgui_draw.lua:4752 - [x] 56% ImDrawList::AddTriangleFilled imgui_draw.lua:4784 - [ ] ApproxApothem - [x] 19% ImDrawList::AddCircle imgui_draw.lua:4798 - [x] 20% ImDrawList::AddCircleFilled imgui_draw.lua:4826 - [x] 34% ImDrawList::AddNgon imgui_draw.lua:5836 - [x] 65% ImDrawList::AddNgonFilled imgui_draw.lua:5846 - [x] 47% ImDrawList::AddEllipse imgui_draw.lua:5855 - [x] 53% ImDrawList::AddEllipseFilled imgui_draw.lua:5866 - [x] 83% ImDrawList::AddBezierCubic imgui_draw.lua:5877 - [x] 82% ImDrawList::AddBezierQuadratic imgui_draw.lua:5885 - [x] 65% ImDrawList::AddText imgui_draw.lua:4861 - [x] 69% ImDrawList::AddImage imgui_draw.lua:4904 + [x] 13% ImDrawList::AddRectFilled imgui_draw.lua:5630 + [x] 26% ImDrawList::AddRectFilledMultiColor imgui_draw.lua:5723 + [x] 59% ImDrawList::AddQuad imgui_draw.lua:5746 + [x] 71% ImDrawList::AddQuadFilled imgui_draw.lua:5763 + [x] 59% ImDrawList::AddTriangle imgui_draw.lua:5775 + [x] 73% ImDrawList::AddTriangleFilled imgui_draw.lua:5792 + [x] 100% ApproxApothem imgui_draw.lua:3733 + [x] 47% ImDrawList::AddCircle imgui_draw.lua:5808 + [x] 32% ImDrawList::AddCircleFilled imgui_draw.lua:5873 + [x] 86% ImDrawList::AddNgon imgui_draw.lua:5911 + [x] 95% ImDrawList::AddNgonFilled imgui_draw.lua:5959 + [x] 80% ImDrawList::AddEllipse imgui_draw.lua:5984 + [x] 88% ImDrawList::AddEllipseFilled imgui_draw.lua:6021 + [x] 73% ImDrawList::AddBezierCubic imgui_draw.lua:6062 + [x] 71% ImDrawList::AddBezierQuadratic imgui_draw.lua:6078 + [x] 65% ImDrawList::AddText imgui_draw.lua:6103 + [x] 69% ImDrawList::AddImage imgui_draw.lua:6146 [ ] ImDrawList::AddImageQuad - [x] 83% ImDrawList::AddImageRounded imgui_draw.lua:4934 + [x] 84% ImDrawList::AddImageRounded imgui_draw.lua:6176 -- ImTriangulator, ImDrawList concave polygon fill [ ] ImTriangulatorNode::Unlink @@ -1364,147 +1364,147 @@ imgui_draw.cpp [ ] ImTriangulator::FlipNodeList [ ] ImTriangulator::IsEar [ ] ImTriangulator::ReclassifyNode - [x] 17% ImDrawList::AddConcavePolyFilled imgui_draw.lua:5894 + [x] 17% ImDrawList::AddConcavePolyFilled imgui_draw.lua:7094 -- ImDrawListSplitter - [x] 58% ImDrawListSplitter::ClearFreeMemory imgui_draw.lua:3913 - [x] 63% ImDrawListSplitter::Split imgui_draw.lua:3931 - [x] 54% ImDrawListSplitter::Merge imgui_draw.lua:3957 - [x] 29% ImDrawListSplitter::SetCurrentChannel imgui_draw.lua:4048 + [x] 58% ImDrawListSplitter::ClearFreeMemory imgui_draw.lua:4027 + [x] 63% ImDrawListSplitter::Split imgui_draw.lua:4045 + [x] 54% ImDrawListSplitter::Merge imgui_draw.lua:4071 + [x] 29% ImDrawListSplitter::SetCurrentChannel imgui_draw.lua:4162 -- ImDrawData - [x] 60% ImDrawData::Clear imgui_draw.lua:3581 - [x] 66% ImGui::AddDrawListToDrawDataEx imgui_draw.lua:3594 - [x] 94% ImDrawData::AddDrawList imgui_draw.lua:3616 + [x] 60% ImDrawData::Clear imgui_draw.lua:3674 + [x] 68% ImGui::AddDrawListToDrawDataEx imgui_draw.lua:3687 + [x] 94% ImDrawData::AddDrawList imgui_draw.lua:3709 [ ] ImDrawData::DeIndexAllBuffers [ ] ImDrawData::ScaleClipRects -- Helpers ShadeVertsXXX functions - [x] 36% ImGui::ShadeVertsLinearColorGradientKeepAlpha imgui_draw.lua:352 - [x] 54% ImGui::ShadeVertsLinearUV imgui_draw.lua:399 - [x] 16% ImGui::ShadeVertsTransformPos imgui_draw.lua:379 + [x] 36% ImGui::ShadeVertsLinearColorGradientKeepAlpha imgui_draw.lua:351 + [x] 54% ImGui::ShadeVertsLinearUV imgui_draw.lua:398 + [x] 16% ImGui::ShadeVertsTransformPos imgui_draw.lua:378 -- ImFontConfig - [x] 20% ImFontConfig::ImFontConfig imgui_h.lua:951 + [x] 20% ImFontConfig::ImFontConfig imgui_h.lua:957 -- ImTextureData - [x] 76% ImTextureDataGetFormatBytesPerPixel imgui_draw.lua:2365 + [x] 76% ImTextureDataGetFormatBytesPerPixel imgui_draw.lua:2427 [ ] ImTextureDataGetStatusName [ ] ImTextureDataGetFormatName - [x] 80% ImTextureData::Create imgui_draw.lua:2375 - [x] 71% ImTextureData::DestroyPixels imgui_draw.lua:2360 + [x] 80% ImTextureData::Create imgui_draw.lua:2437 + [x] 71% ImTextureData::DestroyPixels imgui_draw.lua:2422 -- ImFontAtlas, ImFontAtlasBuilder - [x] 39% ImFontAtlas::ImFontAtlas imgui_h.lua:1032 + [x] 39% ImFontAtlas::ImFontAtlas imgui_h.lua:1038 [-] ImFontAtlas::~ImFontAtlas c++-only - [x] 93% ImFontAtlas::Clear imgui_draw.lua:569 - [x] 76% ImFontAtlas::ClearFonts imgui_draw.lua:614 - [x] 69% ImFontAtlas::ClearInputData imgui_draw.lua:582 - [x] 70% ImFontAtlas::ClearTexData imgui_draw.lua:600 - [x] 100% ImFontAtlas::CompactCache imgui_draw.lua:610 - [x] 100% ImFontAtlas::SetFontLoader imgui_draw.lua:578 - [x] 81% ImFontAtlasBuildUpdateRendererHasTexturesFromContext imgui_draw.lua:1885 - [x] 78% ImFontAtlasUpdateNewFrame imgui_draw.lua:688 - [x] 93% ImTextureDataUpdateNewFrame imgui_draw.lua:638 - [x] 44% ImFontAtlasTextureBlockConvert imgui_draw.lua:779 - [x] 100% ImFontAtlasTextureBlockPostProcess imgui_draw.lua:824 - [x] 48% ImFontAtlasTextureBlockPostProcessMultiply imgui_draw.lua:833 + [x] 93% ImFontAtlas::Clear imgui_draw.lua:568 + [x] 76% ImFontAtlas::ClearFonts imgui_draw.lua:613 + [x] 69% ImFontAtlas::ClearInputData imgui_draw.lua:581 + [x] 70% ImFontAtlas::ClearTexData imgui_draw.lua:599 + [x] 100% ImFontAtlas::CompactCache imgui_draw.lua:609 + [x] 100% ImFontAtlas::SetFontLoader imgui_draw.lua:577 + [x] 81% ImFontAtlasBuildUpdateRendererHasTexturesFromContext imgui_draw.lua:1884 + [x] 78% ImFontAtlasUpdateNewFrame imgui_draw.lua:687 + [x] 93% ImTextureDataUpdateNewFrame imgui_draw.lua:637 + [x] 44% ImFontAtlasTextureBlockConvert imgui_draw.lua:778 + [x] 100% ImFontAtlasTextureBlockPostProcess imgui_draw.lua:823 + [x] 48% ImFontAtlasTextureBlockPostProcessMultiply imgui_draw.lua:832 [ ] ImFontAtlasTextureBlockFill - [x] 87% ImFontAtlasTextureBlockCopy imgui_draw.lua:875 - [x] 100% ImFontAtlasTextureBlockQueueUpload imgui_draw.lua:924 - [x] 99% ImTextureDataQueueUpload imgui_draw.lua:895 + [x] 87% ImFontAtlasTextureBlockCopy imgui_draw.lua:874 + [x] 100% ImFontAtlasTextureBlockQueueUpload imgui_draw.lua:923 + [x] 99% ImTextureDataQueueUpload imgui_draw.lua:894 [-] GetTexDataAsFormat obsolete [-] ImFontAtlas::GetTexDataAsAlpha8 obsolete [-] ImFontAtlas::GetTexDataAsRGBA32 obsolete [-] ImFontAtlas::Build obsolete - [x] 83% ImFontAtlas::AddFont imgui_draw.lua:3276 + [x] 83% ImFontAtlas::AddFont imgui_draw.lua:3338 [ ] Decode85Byte [ ] Decode85 - [x] 35% GetExpectedContextFontSize imgui_draw.lua:3364 - [x] 97% ImFontAtlas::AddFontDefault imgui_draw.lua:3372 - [x] 68% ImFontAtlas::AddFontDefaultBitmap imgui_draw.lua:3439 - [x] 66% ImFontAtlas::AddFontDefaultVector imgui_draw.lua:3461 - [x] 56% ImFontAtlas::AddFontFromFileTTF imgui_draw.lua:3399 - [x] 90% ImFontAtlas::AddFontFromMemoryTTF imgui_draw.lua:3380 - [~] 0% ImFontAtlas::AddFontFromMemoryCompressedTTF imgui_draw.lua:3395 stub + [x] 35% GetExpectedContextFontSize imgui_draw.lua:3426 + [x] 97% ImFontAtlas::AddFontDefault imgui_draw.lua:3434 + [x] 68% ImFontAtlas::AddFontDefaultBitmap imgui_draw.lua:3501 + [x] 66% ImFontAtlas::AddFontDefaultVector imgui_draw.lua:3523 + [x] 56% ImFontAtlas::AddFontFromFileTTF imgui_draw.lua:3461 + [x] 90% ImFontAtlas::AddFontFromMemoryTTF imgui_draw.lua:3442 + [~] 0% ImFontAtlas::AddFontFromMemoryCompressedTTF imgui_draw.lua:3457 stub [ ] ImFontAtlas::AddFontFromMemoryCompressedBase85TTF - [x] 92% ImFontAtlasBuildNotifySetFont imgui_draw.lua:2287 + [x] 92% ImFontAtlasBuildNotifySetFont imgui_draw.lua:2349 [ ] ImFontAtlas::RemoveFont - [x] 93% ImFontAtlas::AddCustomRect imgui_draw.lua:3528 + [x] 93% ImFontAtlas::AddCustomRect imgui_draw.lua:3590 [ ] ImFontAtlas::RemoveCustomRect [-] ImFontAtlas::AddCustomRectFontGlyph obsolete [-] ImFontAtlas::AddCustomRectFontGlyphForSize obsolete - [x] 93% ImFontAtlas::GetCustomRect imgui_draw.lua:3480 - [x] 78% ImFontAtlasGetMouseCursorTexData imgui_draw.lua:3505 - [x] 87% ImFontAtlasBuildMain imgui_draw.lua:2329 - [x] 80% ImFontAtlasBuildGetOversampleFactors imgui_draw.lua:949 - [x] 91% ImFontAtlasBuildSetupFontLoader imgui_draw.lua:1855 + [x] 93% ImFontAtlas::GetCustomRect imgui_draw.lua:3542 + [x] 78% ImFontAtlasGetMouseCursorTexData imgui_draw.lua:3567 + [x] 87% ImFontAtlasBuildMain imgui_draw.lua:2391 + [x] 80% ImFontAtlasBuildGetOversampleFactors imgui_draw.lua:948 + [x] 91% ImFontAtlasBuildSetupFontLoader imgui_draw.lua:1854 [ ] ImFontAtlasBuildLegacyPreloadAllGlyphRanges - [~] 0% ImFontAtlasBuildUpdatePointers imgui_draw.lua:1901 stub - [x] 52% ImFontAtlasBuildRenderBitmapFromString imgui_draw.lua:2157 - [x] 91% ImFontAtlasBuildUpdateTexDataBasic imgui_draw.lua:2188 + [~] 0% ImFontAtlasBuildUpdatePointers imgui_draw.lua:1900 stub + [x] 52% ImFontAtlasBuildRenderBitmapFromString imgui_draw.lua:2219 + [x] 91% ImFontAtlasBuildUpdateTexDataBasic imgui_draw.lua:2250 [ ] InitCornerGeometry [ ] SampleConvex [ ] SampleCornerFillOrStroke [ ] ImFontAtlasBuildUpdateTexDataCorners - [x] 50% ImFontAtlasBuildUpdateTexDataLines imgui_draw.lua:2079 - [x] 89% ImFontAtlasFontInitOutput imgui_draw.lua:1679 - [x] 86% ImFontAtlasFontDestroyOutput imgui_draw.lua:1691 + [x] 64% ImFontAtlasBuildUpdateTexDataLines imgui_draw.lua:2079 + [x] 89% ImFontAtlasFontInitOutput imgui_draw.lua:1678 + [x] 86% ImFontAtlasFontDestroyOutput imgui_draw.lua:1690 [ ] ImFontAtlasFontRebuildOutput - [x] 94% ImFontAtlasFontSourceInit imgui_draw.lua:1704 - [x] 95% ImFontAtlasFontSourceAddToFont imgui_draw.lua:1715 - [x] 54% ImFontAtlasFontDestroySourceData imgui_draw.lua:1728 - [x] 95% ImFontAtlasBuildSetupFontBakedEllipsis imgui_draw.lua:1790 - [x] 91% ImFontAtlasBuildSetupFontBakedFallback imgui_draw.lua:1834 - [x] 88% ImFontAtlasBuildSetupFontBakedBlanks imgui_draw.lua:1526 - [x] 90% ImFontAtlasBuildSetupFontSpecialGlyphs imgui_draw.lua:1746 + [x] 94% ImFontAtlasFontSourceInit imgui_draw.lua:1703 + [x] 95% ImFontAtlasFontSourceAddToFont imgui_draw.lua:1714 + [x] 54% ImFontAtlasFontDestroySourceData imgui_draw.lua:1727 + [x] 95% ImFontAtlasBuildSetupFontBakedEllipsis imgui_draw.lua:1789 + [x] 91% ImFontAtlasBuildSetupFontBakedFallback imgui_draw.lua:1833 + [x] 88% ImFontAtlasBuildSetupFontBakedBlanks imgui_draw.lua:1525 + [x] 90% ImFontAtlasBuildSetupFontSpecialGlyphs imgui_draw.lua:1745 [ ] ImFontAtlasBakedDiscardFontGlyph - [x] 81% ImFontAtlasBakedAdd imgui_draw.lua:1549 - [x] 89% ImFontAtlasBakedGetClosestMatch imgui_draw.lua:1586 - [x] 74% ImFontAtlasBakedDiscard imgui_draw.lua:1628 - [x] 83% ImFontAtlasFontDiscardBakes imgui_draw.lua:1657 - [x] 83% ImFontAtlasBuildDiscardBakes imgui_draw.lua:966 - [x] 92% ImFontAtlasAddDrawListSharedData imgui_draw.lua:989 - [x] 92% ImFontAtlasRemoveDrawListSharedData imgui_draw.lua:996 - [x] 79% ImFontAtlasUpdateDrawListsTextures imgui_draw.lua:1914 - [x] 76% ImFontAtlasUpdateDrawListsSharedData imgui_draw.lua:2219 - [x] 98% ImFontAtlasBuildSetTexture imgui_draw.lua:1943 - [x] 40% ImFontAtlasBuildUpdateTexData imgui_draw.lua:1953 + [x] 81% ImFontAtlasBakedAdd imgui_draw.lua:1548 + [x] 89% ImFontAtlasBakedGetClosestMatch imgui_draw.lua:1585 + [x] 74% ImFontAtlasBakedDiscard imgui_draw.lua:1627 + [x] 83% ImFontAtlasFontDiscardBakes imgui_draw.lua:1656 + [x] 83% ImFontAtlasBuildDiscardBakes imgui_draw.lua:965 + [x] 92% ImFontAtlasAddDrawListSharedData imgui_draw.lua:988 + [x] 92% ImFontAtlasRemoveDrawListSharedData imgui_draw.lua:995 + [x] 79% ImFontAtlasUpdateDrawListsTextures imgui_draw.lua:1913 + [x] 79% ImFontAtlasUpdateDrawListsSharedData imgui_draw.lua:2281 + [x] 98% ImFontAtlasBuildSetTexture imgui_draw.lua:1942 + [x] 80% ImFontAtlasBuildUpdateTexData imgui_draw.lua:1952 [x] 91% ImFontAtlasTextureAdd imgui_draw.lua:1959 [-] ImFontAtlasDebugWriteTexToDisk if0 - [x] 91% ImFontAtlasTextureRepack imgui_draw.lua:1004 - [x] 94% ImFontAtlasTextureGrow imgui_draw.lua:1086 - [x] 99% ImFontAtlasTextureMakeSpace imgui_draw.lua:1125 + [x] 91% ImFontAtlasTextureRepack imgui_draw.lua:1003 + [x] 94% ImFontAtlasTextureGrow imgui_draw.lua:1085 + [x] 99% ImFontAtlasTextureMakeSpace imgui_draw.lua:1124 [x] 96% ImFontAtlasTextureGetSizeEstimate imgui_draw.lua:1994 [x] 85% ImFontAtlasBuildClear imgui_draw.lua:2029 [x] 95% ImFontAtlasTextureCompact imgui_draw.lua:2047 - [x] 93% ImFontAtlasBuildInit imgui_draw.lua:2229 - [x] 91% ImFontAtlasBuildDestroy imgui_draw.lua:2271 + [x] 93% ImFontAtlasBuildInit imgui_draw.lua:2291 + [x] 91% ImFontAtlasBuildDestroy imgui_draw.lua:2333 [x] 75% ImFontAtlasPackInit imgui_draw.lua:2062 - [x] 85% ImFontAtlasPackAllocRectEntry imgui_draw.lua:1142 - [x] 93% ImFontAtlasPackReuseRectEntry imgui_draw.lua:1166 - [x] 82% ImFontAtlasPackDiscardRect imgui_draw.lua:1176 - [x] 91% ImFontAtlasPackAddRect imgui_draw.lua:1204 - [x] 75% ImFontAtlasPackGetRect imgui_draw.lua:1255 - [x] 82% ImFontAtlasPackGetRectSafe imgui_draw.lua:1270 - [x] 66% ImFontAtlasBuildAcceptCodepointForSource imgui_draw.lua:1295 - [x] 97% ImFontBaked_BuildGrowIndex imgui_draw.lua:1313 + [x] 85% ImFontAtlasPackAllocRectEntry imgui_draw.lua:1141 + [x] 93% ImFontAtlasPackReuseRectEntry imgui_draw.lua:1165 + [x] 82% ImFontAtlasPackDiscardRect imgui_draw.lua:1175 + [x] 91% ImFontAtlasPackAddRect imgui_draw.lua:1203 + [x] 75% ImFontAtlasPackGetRect imgui_draw.lua:1254 + [x] 82% ImFontAtlasPackGetRectSafe imgui_draw.lua:1269 + [x] 66% ImFontAtlasBuildAcceptCodepointForSource imgui_draw.lua:1294 + [x] 97% ImFontBaked_BuildGrowIndex imgui_draw.lua:1312 [ ] ImFont::AddRemapCodepoint [ ] ImFont::AddRemapCodepointToGlyphIndex - [x] 29% ImFontAtlas_FontHookRemapCodepoint imgui_draw.lua:2637 - [x] 70% ImFontBaked_BuildLoadGlyph imgui_draw.lua:2652 - [x] 77% ImFontBaked_BuildLoadGlyphAdvanceX imgui_draw.lua:2732 - [x] 67% BuildLoadGlyphGetAdvanceOrFallback imgui_draw.lua:2777 + [x] 29% ImFontAtlas_FontHookRemapCodepoint imgui_draw.lua:2699 + [x] 70% ImFontBaked_BuildLoadGlyph imgui_draw.lua:2714 + [x] 77% ImFontBaked_BuildLoadGlyphAdvanceX imgui_draw.lua:2794 + [x] 67% BuildLoadGlyphGetAdvanceOrFallback imgui_draw.lua:2839 [ ] ImFontAtlasDebugLogTextureRequests -- ImFontAtlas: backend for stb_truetype - [x] 84% ImGui_ImplStbTrueType_FontSrcInit imgui_draw.lua:1349 - [x] 53% ImGui_ImplStbTrueType_FontSrcDestroy imgui_draw.lua:1384 + [x] 84% ImGui_ImplStbTrueType_FontSrcInit imgui_draw.lua:1348 + [x] 53% ImGui_ImplStbTrueType_FontSrcDestroy imgui_draw.lua:1383 [ ] ImGui_ImplStbTrueType_FontSrcGetGlyphIndexFromCodepoint - [x] 43% ImGui_ImplStbTrueType_FontSrcContainsGlyph imgui_draw.lua:1389 - [x] 88% ImGui_ImplStbTrueType_FontBakedInit imgui_draw.lua:1398 - [x] 85% ImGui_ImplStbTrueType_FontBakedLoadGlyph imgui_draw.lua:1425 - [x] 74% ImFontAtlasGetFontLoaderForStbTruetype imgui_draw.lua:1509 + [x] 43% ImGui_ImplStbTrueType_FontSrcContainsGlyph imgui_draw.lua:1388 + [x] 88% ImGui_ImplStbTrueType_FontBakedInit imgui_draw.lua:1397 + [x] 85% ImGui_ImplStbTrueType_FontBakedLoadGlyph imgui_draw.lua:1424 + [x] 74% ImFontAtlasGetFontLoaderForStbTruetype imgui_draw.lua:1508 -- ImFontAtlas: glyph ranges helpers [ ] ImFontAtlas::GetGlyphRangesDefault @@ -1524,46 +1524,46 @@ imgui_draw.cpp [ ] ImFontGlyphRangesBuilder::BuildRanges -- ImFontBaked, ImFont - [x] 12% ImFontBaked::ImFontBaked imgui_h.lua:838 - [x] 79% ImFontBaked::ClearOutputData imgui_draw.lua:2400 - [x] 10% ImFont::ImFont imgui_h.lua:895 + [x] 12% ImFontBaked::ImFontBaked imgui_h.lua:844 + [x] 79% ImFontBaked::ClearOutputData imgui_draw.lua:2462 + [x] 10% ImFont::ImFont imgui_h.lua:901 [-] ImFont::~ImFont c++-only - [x] 49% ImFont::ClearOutputData imgui_draw.lua:2454 + [x] 49% ImFont::ClearOutputData imgui_draw.lua:2516 [ ] ImFont::IsGlyphRangeUnused - [x] 87% ImFontAtlasBakedAddFontGlyph imgui_draw.lua:2520 - [x] 87% ImFontAtlasBakedAddFontGlyphAdvancedX imgui_draw.lua:2578 - [x] 84% ImFontAtlasBakedSetFontGlyphBitmap imgui_draw.lua:938 - [x] 85% ImFontBaked::FindGlyph imgui_draw.lua:2414 - [x] 88% ImFontBaked::FindGlyphNoFallback imgui_draw.lua:2437 - [x] 49% ImFontBaked::IsGlyphLoaded imgui_draw.lua:2429 - [x] 50% ImFont::IsGlyphInFont imgui_draw.lua:2749 - [x] 86% ImFontBaked::GetCharAdvance imgui_draw.lua:2765 - [x] 46% ImFontAtlasBakedGetId imgui_draw.lua:2469 - [x] 90% ImFont::GetFontBaked imgui_draw.lua:2603 - [x] 91% ImFontAtlasBakedGetOrAdd imgui_draw.lua:2479 - [x] 55% ImTextCalcWordWrapNextLineStart imgui_draw.lua:2787 - [x] 81% ImTextClassifierClear imgui_draw.lua:2807 - [x] 53% ImTextClassifierSetCharClass imgui_draw.lua:2818 - [x] 60% ImTextClassifierSetCharClassFromStr imgui_draw.lua:2826 - [x] 88% ImTextInitClassifiers imgui_draw.lua:2844 - [x] 35% ImFontCalcWordWrapPositionEx imgui_draw.lua:2875 + [x] 87% ImFontAtlasBakedAddFontGlyph imgui_draw.lua:2582 + [x] 87% ImFontAtlasBakedAddFontGlyphAdvancedX imgui_draw.lua:2640 + [x] 84% ImFontAtlasBakedSetFontGlyphBitmap imgui_draw.lua:937 + [x] 85% ImFontBaked::FindGlyph imgui_draw.lua:2476 + [x] 88% ImFontBaked::FindGlyphNoFallback imgui_draw.lua:2499 + [x] 49% ImFontBaked::IsGlyphLoaded imgui_draw.lua:2491 + [x] 50% ImFont::IsGlyphInFont imgui_draw.lua:2811 + [x] 86% ImFontBaked::GetCharAdvance imgui_draw.lua:2827 + [x] 46% ImFontAtlasBakedGetId imgui_draw.lua:2531 + [x] 90% ImFont::GetFontBaked imgui_draw.lua:2665 + [x] 91% ImFontAtlasBakedGetOrAdd imgui_draw.lua:2541 + [x] 55% ImTextCalcWordWrapNextLineStart imgui_draw.lua:2849 + [x] 81% ImTextClassifierClear imgui_draw.lua:2869 + [x] 53% ImTextClassifierSetCharClass imgui_draw.lua:2880 + [x] 60% ImTextClassifierSetCharClassFromStr imgui_draw.lua:2888 + [x] 88% ImTextInitClassifiers imgui_draw.lua:2906 + [x] 35% ImFontCalcWordWrapPositionEx imgui_draw.lua:2937 [ ] ImFont::CalcWordWrapPosition - [x] 49% ImFontCalcTextSizeEx imgui_draw.lua:3049 - [x] 91% ImFont::CalcTextSizeA imgui_draw.lua:3210 - [x] 85% ImFont::RenderChar imgui_draw.lua:3223 - [x] 66% ImFont::RenderText imgui_draw.lua:5227 + [x] 49% ImFontCalcTextSizeEx imgui_draw.lua:3111 + [x] 91% ImFont::CalcTextSizeA imgui_draw.lua:3272 + [x] 85% ImFont::RenderChar imgui_draw.lua:3285 + [x] 66% ImFont::RenderText imgui_draw.lua:6480 -- ImGui Internal Render Helpers - [x] 58% ImGui::RenderArrow imgui_draw.lua:5473 - [x] 88% ImGui::RenderBullet imgui_draw.lua:5505 - [x] 63% ImGui::RenderCheckMark imgui_draw.lua:5517 - [x] 90% ImGui::RenderArrowPointingAt imgui_draw.lua:5542 + [x] 58% ImGui::RenderArrow imgui_draw.lua:6727 + [x] 88% ImGui::RenderBullet imgui_draw.lua:6759 + [x] 63% ImGui::RenderCheckMark imgui_draw.lua:6771 + [x] 90% ImGui::RenderArrowPointingAt imgui_draw.lua:6796 [x] 93% ImGui::RenderArrowDockMenu imgui.lua:4551 - [x] 100% ImAcos01 imgui_draw.lua:5555 - [x] 95% ImGui::RenderRectFilledInRangeH imgui_draw.lua:5571 - [x] 75% ImGui::RenderRectFilledWithHole imgui_draw.lua:5735 - [x] 76% ImGui::CalcRoundingFlagsForRectInRect imgui_draw.lua:5626 - [x] 68% ImGui::RenderColorRectWithAlphaCheckerboard imgui_draw.lua:5662 + [x] 100% ImAcos01 imgui_draw.lua:6809 + [x] 95% ImGui::RenderRectFilledInRangeH imgui_draw.lua:6825 + [x] 80% ImGui::RenderRectFilledWithHole imgui_draw.lua:6989 + [x] 81% ImGui::CalcRoundingFlagsForRectInRect imgui_draw.lua:6880 + [x] 70% ImGui::RenderColorRectWithAlphaCheckerboard imgui_draw.lua:6916 -- Decompression code [ ] stb_decompress_length @@ -1697,8 +1697,8 @@ imgui_tables.cpp -- Tables: Debugging [ ] DebugNodeTableGetSizingPolicyDesc - [x] 16% ImGui::DebugNodeTable imgui.lua:20754 - [x] 69% ImGui::DebugNodeTableSettings imgui.lua:20736 + [x] 16% ImGui::DebugNodeTable imgui.lua:20778 + [x] 69% ImGui::DebugNodeTableSettings imgui.lua:20760 -- Columns, BeginColumns, EndColumns, etc. [x] 24% ImGui::SetWindowClipRectBeforeSetChannel imgui_tables.lua:2070 @@ -1732,7 +1732,7 @@ imgui_demo.cpp -- Demo Window / ShowDemoWindow() [-] ImGuiDemoWindowData::~ImGuiDemoWindowData c++-only - [x] 23% ImGui::ShowDemoWindow imgui_demo.lua:287 + [x] 22% ImGui::ShowDemoWindow imgui_demo.lua:287 -- DemoWindowMenuBar() [x] 60% DemoWindowMenuBar imgui_demo.lua:652 @@ -1860,11 +1860,11 @@ imgui.h ==================================================================================================== -- Forward declarations and basic types - [x] 0% IM_MSVC_RUNTIME_CHECKS_OFF::ImVec2 imgui_h.lua:290 - [x] 0% ImVec4::ImVec4 imgui_h.lua:356 + [x] 0% IM_MSVC_RUNTIME_CHECKS_OFF::ImVec2 imgui_h.lua:291 + [x] 0% ImVec4::ImVec4 imgui_h.lua:357 -- Texture identifiers (ImTextureID, ImTextureRef) - [x] 60% IM_MSVC_RUNTIME_CHECKS_OFF::ImTextureRef imgui_h.lua:801 + [x] 60% IM_MSVC_RUNTIME_CHECKS_OFF::ImTextureRef imgui_h.lua:807 -- Tables API flags and structures (ImGuiTableFlags, ImGuiTableColumnFlags, ImGuiTableRowFlags, ImGuiTableBgTarget, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) [x] 35% ImGuiTableSortSpecs::ImGuiTableSortSpecs imgui_tables.lua:232 @@ -1874,7 +1874,7 @@ imgui.h [-] operatornew c++-only [-] operatordelete c++-only [x] 26% IM_DELETE imgui_h.lua:130 - [x] 37% IM_MSVC_RUNTIME_CHECKS_OFF::ImVector imgui_h.lua:412 + [x] 37% IM_MSVC_RUNTIME_CHECKS_OFF::ImVector imgui_h.lua:413 [-] IM_MSVC_RUNTIME_CHECKS_OFF::operator= c++-only [-] IM_MSVC_RUNTIME_CHECKS_OFF::~ImVector c++-only [ ] IM_MSVC_RUNTIME_CHECKS_OFF::clear @@ -1890,7 +1890,7 @@ imgui.h [ ] IM_MSVC_RUNTIME_CHECKS_OFF::front [ ] IM_MSVC_RUNTIME_CHECKS_OFF::back [ ] IM_MSVC_RUNTIME_CHECKS_OFF::swap - [x] 65% IM_MSVC_RUNTIME_CHECKS_OFF::_grow_capacity imgui_h.lua:406 + [x] 65% IM_MSVC_RUNTIME_CHECKS_OFF::_grow_capacity imgui_h.lua:407 [ ] IM_MSVC_RUNTIME_CHECKS_OFF::resize [ ] IM_MSVC_RUNTIME_CHECKS_OFF::shrink [ ] IM_MSVC_RUNTIME_CHECKS_OFF::reserve @@ -1909,35 +1909,35 @@ imgui.h [ ] IM_MSVC_RUNTIME_CHECKS_OFF::index_from_ptr -- Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload) - [x] 80% ImGuiInputTextCallbackData::SelectAll imgui_h.lua:2455 + [x] 80% ImGuiInputTextCallbackData::SelectAll imgui_h.lua:2518 [ ] ImGuiInputTextCallbackData::SetSelection - [x] 83% ImGuiInputTextCallbackData::ClearSelection imgui_h.lua:2456 - [x] 89% ImGuiInputTextCallbackData::HasSelection imgui_h.lua:2457 - [x] 29% ImGuiWindowClass::ImGuiWindowClass imgui_h.lua:2321 - [x] 0% ImGuiPayload::ImGuiPayload imgui_h.lua:2665 - [x] 61% ImGuiPayload::Clear imgui_h.lua:2661 - [x] 55% ImGuiPayload::IsDataType imgui_h.lua:2662 - [x] 100% ImGuiPayload::IsPreview imgui_h.lua:2663 - [x] 100% ImGuiPayload::IsDelivery imgui_h.lua:2664 + [x] 83% ImGuiInputTextCallbackData::ClearSelection imgui_h.lua:2519 + [x] 89% ImGuiInputTextCallbackData::HasSelection imgui_h.lua:2520 + [x] 29% ImGuiWindowClass::ImGuiWindowClass imgui_h.lua:2384 + [x] 0% ImGuiPayload::ImGuiPayload imgui_h.lua:2728 + [x] 61% ImGuiPayload::Clear imgui_h.lua:2724 + [x] 55% ImGuiPayload::IsDataType imgui_h.lua:2725 + [x] 100% ImGuiPayload::IsPreview imgui_h.lua:2726 + [x] 100% ImGuiPayload::IsDelivery imgui_h.lua:2727 -- Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) [ ] ImGuiOnceUponAFrame::ImGuiOnceUponAFrame [-] ImGuiOnceUponAFrame::operatorbool c++-only - [x] 80% ImGuiTextFilter::Clear imgui.lua:19364 - [x] 100% ImGuiTextFilter::IsActive imgui.lua:19365 + [x] 80% ImGuiTextFilter::Clear imgui.lua:19388 + [x] 100% ImGuiTextFilter::IsActive imgui.lua:19389 [-] ImGuiTextFilter::Draw obsolete - [x] 0% ImGuiTextBuffer::ImGuiTextBuffer imgui_h.lua:2682 + [x] 0% ImGuiTextBuffer::ImGuiTextBuffer imgui_h.lua:2745 [ ] ImGuiTextBuffer::begin [ ] ImGuiTextBuffer::end - [x] 14% ImGuiTextBuffer::size imgui_h.lua:2688 - [x] 33% ImGuiTextBuffer::empty imgui_h.lua:2684 - [x] 40% ImGuiTextBuffer::clear imgui_h.lua:2683 + [x] 14% ImGuiTextBuffer::size imgui_h.lua:2751 + [x] 33% ImGuiTextBuffer::empty imgui_h.lua:2747 + [x] 40% ImGuiTextBuffer::clear imgui_h.lua:2746 [ ] ImGuiTextBuffer::resize [ ] ImGuiTextBuffer::reserve - [x] 31% ImGuiTextBuffer::c_str imgui_h.lua:2687 + [x] 31% ImGuiTextBuffer::c_str imgui_h.lua:2750 [ ] ImGuiStoragePair::ImGuiStoragePair [ ] ImGuiStorage::Clear - [x] 100% ImGuiListClipper::IncludeItemByIndex imgui.lua:19595 + [x] 100% ImGuiListClipper::IncludeItemByIndex imgui.lua:19619 [-] operator+ c++-only [-] operator- c++-only [-] operator* c++-only @@ -1948,7 +1948,7 @@ imgui.h [-] operator/= c++-only [-] operator== c++-only [-] operator!= c++-only - [x] n/a IM_MSVC_RUNTIME_CHECKS_RESTORE::ImColor imgui_h.lua:2698 = setmetatable ( { } , { + [x] n/a IM_MSVC_RUNTIME_CHECKS_RESTORE::ImColor imgui_h.lua:2761 = setmetatable ( { } , { [-] IM_MSVC_RUNTIME_CHECKS_RESTORE::operatorImU32 c++-only [-] IM_MSVC_RUNTIME_CHECKS_RESTORE::operatorImVec4 c++-only [ ] IM_MSVC_RUNTIME_CHECKS_RESTORE::SetHSV @@ -1958,70 +1958,70 @@ imgui.h [x] 100% ImGuiSelectionBasicStorage::GetStorageIdFromIndex imgui_widgets.lua:9516 -- Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawList, ImDrawData) - [x] 14% ImDrawCmd::ImDrawCmd imgui_h.lua:506 - [x] 18% ImDrawListSplitter::ImDrawListSplitter imgui_h.lua:593 + [x] 14% ImDrawCmd::ImDrawCmd imgui_h.lua:507 + [x] 18% ImDrawListSplitter::ImDrawListSplitter imgui_h.lua:594 [-] ImDrawListSplitter::~ImDrawListSplitter c++-only - [x] 92% ImDrawListSplitter::Clear imgui_draw.lua:3908 + [x] 92% ImDrawListSplitter::Clear imgui_draw.lua:4022 [ ] ImDrawList::GetClipRectMin [ ] ImDrawList::GetClipRectMax - [x] 1% ImDrawList::AddPolyline imgui_draw.lua:4336 - [x] 29% ImDrawList::PathClear imgui_internal.lua:1016 - [x] 16% ImDrawList::PathLineTo imgui_internal.lua:1020 - [x] 48% ImDrawList::PathLineToMergeDuplicate imgui_internal.lua:1030 - [x] 88% ImDrawList::PathFillConvex imgui_internal.lua:1038 - [x] 94% ImDrawList::PathFillConcave imgui_draw.lua:5935 - [x] 67% ImDrawList::PathStroke imgui_internal.lua:1046 - [x] 89% ImDrawList::ChannelsSplit imgui_draw.lua:4089 - [x] 80% ImDrawList::ChannelsMerge imgui_draw.lua:4090 - [x] 89% ImDrawList::ChannelsSetCurrent imgui_draw.lua:4092 - [x] 16% ImDrawList::PrimWriteVtx imgui_h.lua:627 - [x] 44% ImDrawList::PrimWriteIdx imgui_h.lua:637 - [x] 89% ImDrawList::PrimVtx imgui_h.lua:645 + [x] 80% ImDrawList::AddPolyline imgui_internal.lua:1077 + [x] 100% ImDrawList::PathClear imgui_internal.lua:1029 + [x] 16% ImDrawList::PathLineTo imgui_internal.lua:1033 + [x] 48% ImDrawList::PathLineToMergeDuplicate imgui_internal.lua:1043 + [x] 94% ImDrawList::PathFillConvex imgui_internal.lua:1053 + [x] 94% ImDrawList::PathFillConcave imgui_draw.lua:7135 + [x] 75% ImDrawList::PathStroke imgui_internal.lua:1061 + [x] 89% ImDrawList::ChannelsSplit imgui_draw.lua:4203 + [x] 80% ImDrawList::ChannelsMerge imgui_draw.lua:4204 + [x] 89% ImDrawList::ChannelsSetCurrent imgui_draw.lua:4206 + [x] 16% ImDrawList::PrimWriteVtx imgui_h.lua:631 + [x] 44% ImDrawList::PrimWriteIdx imgui_h.lua:641 + [x] 89% ImDrawList::PrimVtx imgui_h.lua:649 [-] ImDrawList::AddRect obsolete [-] ImDrawList::PushTextureID obsolete [-] ImDrawList::PopTextureID obsolete - [x] 12% ImDrawData::ImDrawData imgui_h.lua:701 + [x] 12% ImDrawData::ImDrawData imgui_h.lua:707 -- Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) - [x] 14% ImTextureData::ImTextureData imgui_h.lua:745 + [x] 14% ImTextureData::ImTextureData imgui_h.lua:751 [-] ImTextureData::~ImTextureData c++-only [ ] ImTextureData::GetPixels - [x] 58% ImTextureData::GetPixelsAt imgui_h.lua:772 + [x] 58% ImTextureData::GetPixelsAt imgui_h.lua:778 [ ] ImTextureData::GetSizeInBytes - [x] 86% ImTextureData::GetPitch imgui_h.lua:776 + [x] 86% ImTextureData::GetPitch imgui_h.lua:782 [ ] ImTextureData::GetTexRef - [x] 100% ImTextureData::GetTexID imgui_h.lua:777 - [x] 100% ImTextureData::SetTexID imgui_h.lua:780 - [x] 95% ImTextureData::SetStatus imgui_h.lua:783 + [x] 100% ImTextureData::GetTexID imgui_h.lua:783 + [x] 100% ImTextureData::SetTexID imgui_h.lua:786 + [x] 95% ImTextureData::SetStatus imgui_h.lua:789 -- Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont) - [x] 21% ImFontGlyph::ImFontGlyph imgui_h.lua:1120 + [x] 21% ImFontGlyph::ImFontGlyph imgui_h.lua:1126 [ ] ImFontGlyphRangesBuilder::ImFontGlyphRangesBuilder [ ] ImFontGlyphRangesBuilder::Clear [ ] ImFontGlyphRangesBuilder::GetBit [ ] ImFontGlyphRangesBuilder::SetBit [ ] ImFontGlyphRangesBuilder::AddChar - [x] 14% ImFontAtlasRect::ImFontAtlasRect imgui_h.lua:1092 + [x] 14% ImFontAtlasRect::ImFontAtlasRect imgui_h.lua:1098 [-] ImFontAtlas::SetTexID obsolete [-] ImFontAtlas::IsBuilt obsolete [-] ImFontAtlas::AddCustomRectRegular obsolete [-] ImFontAtlas::GetCustomRectByIndex obsolete [-] ImFontAtlas::CalcCustomRectUV obsolete - [x] 100% ImFont::IsLoaded imgui_h.lua:890 - [x] 58% ImFont::GetDebugName imgui_h.lua:891 + [x] 100% ImFont::IsLoaded imgui_h.lua:896 + [x] 58% ImFont::GetDebugName imgui_h.lua:897 [-] ImFont::CalcWordWrapPositionA obsolete [ ] ImTextureRef::GetTexID [ ] ImDrawCmd::GetTexID -- Viewports - [x] 7% ImGuiViewport::ImGuiViewport imgui_h.lua:1483 + [x] 7% ImGuiViewport::ImGuiViewport imgui_h.lua:1491 [-] ImGuiViewport::~ImGuiViewport c++-only - [x] 90% ImGuiViewport::GetCenter imgui_h.lua:1473 - [x] 90% ImGuiViewport::GetWorkCenter imgui_h.lua:1477 + [x] 90% ImGuiViewport::GetCenter imgui_h.lua:1481 + [x] 90% ImGuiViewport::GetWorkCenter imgui_h.lua:1485 -- ImGuiPlatformIO + other Platform Dependent Interfaces (ImGuiPlatformMonitor, ImGuiPlatformImeData) - [x] 42% ImGuiPlatformMonitor::ImGuiPlatformMonitor imgui_h.lua:1577 - [x] 24% ImGuiPlatformImeData::ImGuiPlatformImeData imgui_h.lua:1383 + [x] 42% ImGuiPlatformMonitor::ImGuiPlatformMonitor imgui_h.lua:1585 + [x] 24% ImGuiPlatformImeData::ImGuiPlatformImeData imgui_h.lua:1391 -- Obsolete functions and types [-] PushFont obsolete @@ -2036,12 +2036,12 @@ imgui_internal.h -- Generic helpers [x] 50% ImQsort imgui_internal.lua:212 - [x] 92% ImIsPowerOfTwo imgui_internal.lua:270 - [x] 35% ImUpperPowerOfTwo imgui_internal.lua:274 - [x] 82% ImCountSetBits imgui_internal.lua:289 - [x] 62% ImToUpper imgui_internal.lua:397 - [x] 75% ImCharIsBlankA imgui_internal.lua:679 - [x] 83% ImCharIsBlankW imgui_internal.lua:682 + [x] 92% ImIsPowerOfTwo imgui_internal.lua:276 + [x] 35% ImUpperPowerOfTwo imgui_internal.lua:280 + [x] 82% ImCountSetBits imgui_internal.lua:295 + [x] 62% ImToUpper imgui_internal.lua:403 + [x] 75% ImCharIsBlankA imgui_internal.lua:692 + [x] 83% ImCharIsBlankW imgui_internal.lua:695 [ ] ImCharIsXdigitA [x] 0% ImFileOpen imgui.lua:4702 [x] 0% ImFileClose imgui.lua:4719 @@ -2051,9 +2051,9 @@ imgui_internal.h [x] n/a ImPow imgui_internal.lua:150 = math . pow [x] n/a ImLog imgui_internal.lua:151 = math . log [x] n/a ImAbs imgui_internal.lua:152 = math . abs - [x] 69% ImSign imgui_internal.lua:391 + [x] 69% ImSign imgui_internal.lua:397 [x] 40% ImRsqrt imgui_internal.lua:260 - [ ] ImRsqrtPrecise + [x] 11% ImRsqrtPrecise imgui_internal.lua:262 [ ] ImSwap [x] 100% ImAddClampOverflow imgui_internal.lua:171 [x] 100% ImSubClampOverflow imgui_internal.lua:183 @@ -2061,60 +2061,60 @@ imgui_internal.h [x] 47% ImMax imgui_internal.lua:203 [x] 25% ImClamp imgui_internal.lua:218 [x] 45% ImLerp imgui_internal.lua:229 - [x] 77% ImSaturate imgui_internal.lua:387 - [x] 67% ImLengthSqr imgui_internal.lua:434 + [x] 77% ImSaturate imgui_internal.lua:393 + [x] 67% ImLengthSqr imgui_internal.lua:440 [ ] ImInvLength [x] 24% ImTrunc imgui_internal.lua:242 - [~] 25% ImFloor imgui_internal.lua:266 stub + [~] 25% ImFloor imgui_internal.lua:272 stub [x] 15% ImTrunc64 imgui_internal.lua:248 = _Trunc [ ] ImRoundPositive64 [ ] ImRoundSigned64 [x] 42% ImCeilFast imgui_internal.lua:252 [ ] ImModPositive - [x] 100% ImDot imgui_internal.lua:447 - [x] 98% ImRotate imgui_internal.lua:441 + [x] 100% ImDot imgui_internal.lua:453 + [x] 98% ImRotate imgui_internal.lua:447 [x] 100% ImLinearSweep imgui_widgets.lua:10652 - [x] 100% ImLinearRemapClamp imgui_internal.lua:393 - [x] 97% ImMul imgui_internal.lua:454 + [x] 100% ImLinearRemapClamp imgui_internal.lua:399 + [x] 97% ImMul imgui_internal.lua:460 [ ] ImIsFloatAboveGuaranteedIntegerPrecision - [x] 71% ImExponentialMovingAverage imgui_internal.lua:302 - [ ] ImIsTruncated - [ ] ImIsTruncated4 + [x] 71% ImExponentialMovingAverage imgui_internal.lua:308 + [x] 60% ImIsTruncated imgui_internal.lua:264 + [x] 60% ImIsTruncated4 imgui_internal.lua:266 [ ] ImTriangleArea [ ] ImTriangleIsClockwise - [x] 0% IM_MSVC_RUNTIME_CHECKS_OFF::ImVec1 imgui_internal.lua:784 + [x] 0% IM_MSVC_RUNTIME_CHECKS_OFF::ImVec1 imgui_internal.lua:797 [ ] ImVec2i::ImVec2i [ ] ImVec2ih::ImVec2ih - [x] 0% ImRect::ImRect imgui_internal.lua:814 - [x] 90% ImRect::GetCenter imgui_internal.lua:844 - [x] 89% ImRect::GetSize imgui_internal.lua:849 - [x] 94% ImRect::GetWidth imgui_internal.lua:845 - [x] 94% ImRect::GetHeight imgui_internal.lua:846 - [x] 91% ImRect::GetArea imgui_internal.lua:938 - [x] 33% ImRect::GetTL imgui_internal.lua:852 - [x] 89% ImRect::GetTR imgui_internal.lua:854 - [x] 89% ImRect::GetBL imgui_internal.lua:856 - [x] 33% ImRect::GetBR imgui_internal.lua:858 - [x] 43% ImRect::Contains imgui_internal.lua:821 - [x] 96% ImRect::ContainsWithPad imgui_internal.lua:833 - [x] 14% ImRect::Overlaps imgui_internal.lua:839 - [x] 59% ImRect::Add imgui_internal.lua:883 - [x] 96% ImRect::AddX imgui_internal.lua:899 - [x] 96% ImRect::AddY imgui_internal.lua:900 - [x] 38% ImRect::Expand imgui_internal.lua:904 - [x] 64% ImRect::Translate imgui_internal.lua:921 - [x] 55% ImRect::TranslateX imgui_internal.lua:927 - [x] 55% ImRect::TranslateY imgui_internal.lua:932 - [x] 22% ImRect::ClipWith imgui_internal.lua:863 - [x] 20% ImRect::ClipWithFull imgui_internal.lua:873 - [x] 91% ImRect::IsInverted imgui_internal.lua:936 - [x] 94% ImRect::ToVec4 imgui_internal.lua:915 - [x] 36% ImRect::AsVec4 imgui_internal.lua:941 + [x] 0% ImRect::ImRect imgui_internal.lua:827 + [x] 90% ImRect::GetCenter imgui_internal.lua:857 + [x] 89% ImRect::GetSize imgui_internal.lua:862 + [x] 94% ImRect::GetWidth imgui_internal.lua:858 + [x] 94% ImRect::GetHeight imgui_internal.lua:859 + [x] 91% ImRect::GetArea imgui_internal.lua:951 + [x] 33% ImRect::GetTL imgui_internal.lua:865 + [x] 89% ImRect::GetTR imgui_internal.lua:867 + [x] 89% ImRect::GetBL imgui_internal.lua:869 + [x] 33% ImRect::GetBR imgui_internal.lua:871 + [x] 43% ImRect::Contains imgui_internal.lua:834 + [x] 96% ImRect::ContainsWithPad imgui_internal.lua:846 + [x] 14% ImRect::Overlaps imgui_internal.lua:852 + [x] 59% ImRect::Add imgui_internal.lua:896 + [x] 96% ImRect::AddX imgui_internal.lua:912 + [x] 96% ImRect::AddY imgui_internal.lua:913 + [x] 38% ImRect::Expand imgui_internal.lua:917 + [x] 64% ImRect::Translate imgui_internal.lua:934 + [x] 55% ImRect::TranslateX imgui_internal.lua:940 + [x] 55% ImRect::TranslateY imgui_internal.lua:945 + [x] 22% ImRect::ClipWith imgui_internal.lua:876 + [x] 20% ImRect::ClipWithFull imgui_internal.lua:886 + [x] 91% ImRect::IsInverted imgui_internal.lua:949 + [x] 94% ImRect::ToVec4 imgui_internal.lua:928 + [x] 36% ImRect::AsVec4 imgui_internal.lua:954 [ ] ImBitArrayGetStorageSizeInBytes [ ] ImBitArrayClearAllBits [ ] ImBitArrayTestBit [ ] ImBitArrayClearBit - [x] 34% ImBitArraySetBit imgui_internal.lua:976 + [x] 34% ImBitArraySetBit imgui_internal.lua:989 [ ] ImBitArraySetBitRange [ ] ImBitVector::Create [ ] ImBitVector::Clear @@ -2166,10 +2166,10 @@ imgui_internal.h [ ] template::offset_from_ptr [ ] template::ptr_from_offset [ ] template::swap - [x] 83% ImGuiTextIndex::clear imgui_internal.lua:1777 - [x] 100% ImGuiTextIndex::size imgui_internal.lua:1779 - [x] 71% ImGuiTextIndex::get_line_begin imgui_internal.lua:1781 - [x] 76% ImGuiTextIndex::get_line_end imgui_internal.lua:1785 + [x] 83% ImGuiTextIndex::clear imgui_internal.lua:1819 + [x] 100% ImGuiTextIndex::size imgui_internal.lua:1821 + [x] 71% ImGuiTextIndex::get_line_begin imgui_internal.lua:1823 + [x] 76% ImGuiTextIndex::get_line_end imgui_internal.lua:1827 [ ] ImGuiTextFilterItem::ImGuiTextFilterItem [ ] ImGuiPackedDate::ImGuiPackedDate [ ] ImGuiPackedDate::IsValid @@ -2177,37 +2177,37 @@ imgui_internal.h [ ] ImGuiPackedDate::SubtractMonths -- ImDrawList support - [x] 27% ImDrawDataBuilder::ImDrawDataBuilder imgui_internal.lua:2973 + [x] 27% ImDrawDataBuilder::ImDrawDataBuilder imgui_internal.lua:3015 -- Style support [ ] ImGuiStyleVarInfo::GetVarPtr - [x] 39% ImGuiStyleMod::ImGuiStyleMod imgui_internal.lua:1229 + [x] 39% ImGuiStyleMod::ImGuiStyleMod imgui_internal.lua:1268 -- Widgets support: flags, enums, data structures - [x] 21% ImGuiComboPreviewData::ImGuiComboPreviewData imgui_internal.lua:1623 - [x] 12% ImGuiMenuColumns::ImGuiMenuColumns imgui_internal.lua:2640 - [x] 20% ImGuiInputTextDeactivatedState::ImGuiInputTextDeactivatedState imgui_internal.lua:1709 + [x] 21% ImGuiComboPreviewData::ImGuiComboPreviewData imgui_internal.lua:1665 + [x] 12% ImGuiMenuColumns::ImGuiMenuColumns imgui_internal.lua:2682 + [x] 20% ImGuiInputTextDeactivatedState::ImGuiInputTextDeactivatedState imgui_internal.lua:1751 [ ] ImGuiInputTextDeactivatedState::ClearFreeMemory [ ] ImGuiInputTextState::ClearText [ ] ImGuiInputTextState::ClearFreeMemory [ ] ImGuiInputTextState::GetText - [x] 3% ImGuiNextWindowData::ImGuiNextWindowData imgui_internal.lua:1394 + [x] 3% ImGuiNextWindowData::ImGuiNextWindowData imgui_internal.lua:1433 [ ] ImGuiNextWindowData::ClearFlags - [x] 20% ImGuiNextItemData::ImGuiNextItemData imgui_internal.lua:1346 + [x] 20% ImGuiNextItemData::ImGuiNextItemData imgui_internal.lua:1385 [ ] ImGuiNextItemData::ClearFlags - [x] 11% ImGuiLastItemData::ImGuiLastItemData imgui_internal.lua:1266 - [x] 8% ImGuiErrorRecoveryState::ImGuiErrorRecoveryState imgui.lua:19784 + [x] 11% ImGuiLastItemData::ImGuiLastItemData imgui_internal.lua:1305 + [x] 8% ImGuiErrorRecoveryState::ImGuiErrorRecoveryState imgui.lua:19808 [ ] ImGuiPtrOrIndex::ImGuiPtrOrIndex -- Popup support - [x] 36% ImGuiPopupData::ImGuiPopupData imgui_internal.lua:3527 + [x] 36% ImGuiPopupData::ImGuiPopupData imgui_internal.lua:3569 -- Inputs support - [x] 9% ImGuiInputEvent::ImGuiInputEvent imgui_internal.lua:3321 - [x] 73% ImGuiKeyRoutingData::ImGuiKeyRoutingData imgui_internal.lua:1730 - [x] 10% ImGuiKeyRoutingTable::ImGuiKeyRoutingTable imgui_internal.lua:1757 + [x] 9% ImGuiInputEvent::ImGuiInputEvent imgui_internal.lua:3363 + [x] 73% ImGuiKeyRoutingData::ImGuiKeyRoutingData imgui_internal.lua:1772 + [x] 10% ImGuiKeyRoutingTable::ImGuiKeyRoutingTable imgui_internal.lua:1799 [ ] ImGuiKeyRoutingTable::Clear - [x] 64% ImGuiKeyOwnerData::ImGuiKeyOwnerData imgui_internal.lua:634 + [x] 64% ImGuiKeyOwnerData::ImGuiKeyOwnerData imgui_internal.lua:647 -- Clipper support [ ] ImGuiListClipperRange::FromIndices @@ -2216,25 +2216,25 @@ imgui_internal.h [ ] ImGuiListClipperData::Reset -- Navigation support - [x] 13% ImGuiNavItemData::ImGuiNavItemData imgui_internal.lua:1863 + [x] 13% ImGuiNavItemData::ImGuiNavItemData imgui_internal.lua:1905 [ ] ImGuiNavItemData::Clear -- Typing-select support - [x] 7% ImGuiTypingSelectState::ImGuiTypingSelectState imgui_internal.lua:1907 - [x] 71% ImGuiTypingSelectState::Clear imgui_internal.lua:1910 + [x] 7% ImGuiTypingSelectState::ImGuiTypingSelectState imgui_internal.lua:1949 + [x] 71% ImGuiTypingSelectState::Clear imgui_internal.lua:1952 -- Columns support [x] 19% ImGuiOldColumnData::ImGuiOldColumnData imgui_tables.lua:340 [x] 5% ImGuiOldColumns::ImGuiOldColumns imgui_tables.lua:345 -- Box-select support - [x] 6% ImGuiBoxSelectState::ImGuiBoxSelectState imgui_internal.lua:1934 + [x] 6% ImGuiBoxSelectState::ImGuiBoxSelectState imgui_internal.lua:1976 -- Multi-select support - [x] 9% ImGuiMultiSelectTempData::ImGuiMultiSelectTempData imgui_internal.lua:1980 - [x] 10% ImGuiMultiSelectTempData::Clear imgui_internal.lua:1986 - [x] 67% ImGuiMultiSelectTempData::ClearIO imgui_internal.lua:2004 - [x] 72% ImGuiMultiSelectState::ImGuiMultiSelectState imgui_internal.lua:2017 + [x] 9% ImGuiMultiSelectTempData::ImGuiMultiSelectTempData imgui_internal.lua:2022 + [x] 10% ImGuiMultiSelectTempData::Clear imgui_internal.lua:2028 + [x] 67% ImGuiMultiSelectTempData::ClearIO imgui_internal.lua:2046 + [x] 72% ImGuiMultiSelectState::ImGuiMultiSelectState imgui_internal.lua:2059 -- Docking support [x] 100% ImGuiDockNode::IsRootNode imgui.lua:1095 @@ -2252,23 +2252,23 @@ imgui_internal.h [x] 20% ImGuiDockContext::ImGuiDockContext imgui.lua:1040 -- Viewport support - [x] 36% ImGuiViewportP::ImGuiViewportP imgui_internal.lua:3060 + [x] 36% ImGuiViewportP::ImGuiViewportP imgui_internal.lua:3102 [-] ImGuiViewportP::~ImGuiViewportP c++-only - [x] 78% ImGuiViewportP::ClearRequestFlags imgui_internal.lua:3015 - [x] 94% ImGuiViewportP::CalcWorkRectPos imgui_internal.lua:3022 - [x] 98% ImGuiViewportP::CalcWorkRectSize imgui_internal.lua:3026 - [x] 87% ImGuiViewportP::UpdateWorkRect imgui_internal.lua:3031 - [x] 92% ImGuiViewportP::GetMainRect imgui_internal.lua:3038 - [x] 92% ImGuiViewportP::GetWorkRect imgui_internal.lua:3045 - [x] 96% ImGuiViewportP::GetBuildWorkRect imgui_internal.lua:3052 + [x] 78% ImGuiViewportP::ClearRequestFlags imgui_internal.lua:3057 + [x] 94% ImGuiViewportP::CalcWorkRectPos imgui_internal.lua:3064 + [x] 98% ImGuiViewportP::CalcWorkRectSize imgui_internal.lua:3068 + [x] 87% ImGuiViewportP::UpdateWorkRect imgui_internal.lua:3073 + [x] 92% ImGuiViewportP::GetMainRect imgui_internal.lua:3080 + [x] 92% ImGuiViewportP::GetWorkRect imgui_internal.lua:3087 + [x] 96% ImGuiViewportP::GetBuildWorkRect imgui_internal.lua:3094 -- Settings support - [x] 18% ImGuiWindowSettings::ImGuiWindowSettings imgui_internal.lua:2607 + [x] 18% ImGuiWindowSettings::ImGuiWindowSettings imgui_internal.lua:2649 [ ] ImGuiWindowSettings::GetName - [x] 13% ImGuiSettingsHandler::ImGuiSettingsHandler imgui_h.lua:2835 + [x] 13% ImGuiSettingsHandler::ImGuiSettingsHandler imgui_h.lua:2898 -- Metrics, Debug Tools - [x] 7% ImGuiDebugAllocInfo::ImGuiDebugAllocInfo imgui_internal.lua:1811 + [x] 7% ImGuiDebugAllocInfo::ImGuiDebugAllocInfo imgui_internal.lua:1853 [ ] ImGuiStackLevelInfo::ImGuiStackLevelInfo [ ] ImGuiDebugItemPathQuery::ImGuiDebugItemPathQuery [ ] ImGuiIDStackTool::ImGuiIDStackTool @@ -2277,12 +2277,12 @@ imgui_internal.h [ ] ImGuiContextHook::ImGuiContextHook -- ImGuiWindowTempData, ImGuiWindow - [x] 92% ImGuiWindow::Rect imgui_internal.lua:2781 - [x] 84% ImGuiWindow::TitleBarRect imgui_internal.lua:2787 - [x] 91% ImGuiWindow::MenuBarRect imgui_internal.lua:2793 + [x] 92% ImGuiWindow::Rect imgui_internal.lua:2823 + [x] 84% ImGuiWindow::TitleBarRect imgui_internal.lua:2829 + [x] 91% ImGuiWindow::MenuBarRect imgui_internal.lua:2835 -- Tab bar, Tab item support - [x] 50% ImGuiTabItem::ImGuiTabItem imgui_internal.lua:2149 + [x] 50% ImGuiTabItem::ImGuiTabItem imgui_internal.lua:2191 -- Table support + internal API [x] 28% ImGuiTableColumn::ImGuiTableColumn imgui_tables.lua:240 @@ -2298,36 +2298,36 @@ imgui_internal.h [x] 86% TableGetInstanceID imgui_tables.lua:445 -- ImGui internal API - [x] 100% GetScale imgui.lua:13577 - [x] 100% GetCurrentWindowRead imgui.lua:13583 - [x] 100% GetCurrentWindow imgui.lua:13590 + [x] 100% GetScale imgui.lua:13586 + [x] 100% GetCurrentWindowRead imgui.lua:13592 + [x] 100% GetCurrentWindow imgui.lua:13599 [ ] SetWindowParentWindowForFocusRoute - [x] 99% WindowRectAbsToRel imgui_internal.lua:651 - [x] 99% WindowRectRelToAbs imgui_internal.lua:656 - [x] 98% WindowPosAbsToRel imgui_internal.lua:669 - [x] 98% WindowPosRelToAbs imgui_internal.lua:675 + [x] 99% WindowRectAbsToRel imgui_internal.lua:664 + [x] 99% WindowRectRelToAbs imgui_internal.lua:669 + [x] 98% WindowPosAbsToRel imgui_internal.lua:682 + [x] 98% WindowPosRelToAbs imgui_internal.lua:688 [ ] GetPixelDensity - [x] 55% GetRoundedFontSize imgui_internal.lua:645 - [x] 15% GetForegroundDrawList imgui.lua:13665 - [x] 80% LocalizeGetMsg imgui_internal.lua:3631 + [x] 55% GetRoundedFontSize imgui_internal.lua:658 + [x] 15% GetForegroundDrawList imgui.lua:13675 + [x] 80% LocalizeGetMsg imgui_internal.lua:3673 [ ] ScrollToBringRectIntoView [ ] GetItemStatusFlags [ ] GetActiveID [ ] GetFocusID [x] 2% ItemSize imgui.lua:6492 - [x] 100% IsNamedKey imgui_internal.lua:559 - [x] 75% IsNamedKeyOrMod imgui_internal.lua:590 + [x] 100% IsNamedKey imgui_internal.lua:572 + [x] 75% IsNamedKeyOrMod imgui_internal.lua:603 [ ] IsLegacyKey - [x] 60% IsKeyboardKey imgui_internal.lua:564 - [x] 60% IsGamepadKey imgui_internal.lua:569 - [x] 60% IsMouseKey imgui_internal.lua:574 - [x] 60% IsAliasKey imgui_internal.lua:579 - [x] 100% IsLRModKey imgui_internal.lua:584 - [x] 76% ConvertSingleModFlagToKey imgui_internal.lua:596 + [x] 60% IsKeyboardKey imgui_internal.lua:577 + [x] 60% IsGamepadKey imgui_internal.lua:582 + [x] 60% IsMouseKey imgui_internal.lua:587 + [x] 60% IsAliasKey imgui_internal.lua:592 + [x] 100% IsLRModKey imgui_internal.lua:597 + [x] 76% ConvertSingleModFlagToKey imgui_internal.lua:609 [x] 17% GetKeyData imgui.lua:8440 - [x] 97% MouseButtonToKey imgui_internal.lua:556 - [x] 81% IsActiveIdUsingNavDir imgui_internal.lua:660 - [x] 88% GetKeyOwnerData imgui_internal.lua:622 + [x] 97% MouseButtonToKey imgui_internal.lua:569 + [x] 81% IsActiveIdUsingNavDir imgui_internal.lua:673 + [x] 88% GetKeyOwnerData imgui_internal.lua:635 [x] 100% DockNodeGetRootNode imgui.lua:4488 [x] 100% DockNodeIsInHierarchyOf imgui.lua:4493 [x] 86% DockNodeGetDepth imgui.lua:4501 @@ -2335,26 +2335,26 @@ imgui_internal.h [ ] GetWindowDockNode [x] 97% DockBuilderGetCentralNode imgui.lua:3661 [ ] GetCurrentFocusScope - [x] 91% GetBoxSelectState imgui_internal.lua:2023 - [x] 95% GetMultiSelectState imgui_internal.lua:2026 + [x] 91% GetBoxSelectState imgui_internal.lua:2065 + [x] 95% GetMultiSelectState imgui_internal.lua:2068 [ ] GetCurrentTabBar [x] 35% TabBarGetTabOrder imgui_widgets.lua:11210 [-] RenderNavHighlight obsolete - [x] 100% TempInputIsActive imgui_internal.lua:3638 - [x] 88% GetInputTextState imgui_internal.lua:3646 + [x] 100% TempInputIsActive imgui_internal.lua:3680 + [x] 88% GetInputTextState imgui_internal.lua:3688 [ ] IsItemActiveAsInputText - [x] 80% SetNextItemColorMarker imgui_internal.lua:1055 + [x] 80% SetNextItemColorMarker imgui_internal.lua:1083 -- ImFontLoader - [x] 6% ImFontLoader::ImFontLoader imgui_internal.lua:3122 + [x] 6% ImFontLoader::ImFontLoader imgui_internal.lua:3164 -- ImFontAtlas internal API [-] operator== c++-only [-] operator!= c++-only - [x] 75% ImFontAtlasRectId_GetIndex imgui_internal.lua:3174 - [x] 62% ImFontAtlasRectId_GetGeneration imgui_internal.lua:3178 - [x] 77% ImFontAtlasRectId_Make imgui_internal.lua:3183 - [x] 19% ImFontAtlasBuilder::ImFontAtlasBuilder imgui_internal.lua:1153 + [x] 75% ImFontAtlasRectId_GetIndex imgui_internal.lua:3216 + [x] 62% ImFontAtlasRectId_GetGeneration imgui_internal.lua:3220 + [x] 77% ImFontAtlasRectId_Make imgui_internal.lua:3225 + [x] 18% ImFontAtlasBuilder::ImFontAtlasBuilder imgui_internal.lua:1184 ==================================================================================================== API declarations not ported (by upstream line) @@ -2403,9 +2403,9 @@ API declarations not ported (by upstream line) ==================================================================================================== Enums (values ported / total; missing listed) ==================================================================================================== - ImDrawFlags_ 3 / 31 missing: ImDrawFlags_RoundTopLeft, ImDrawFlags_RoundTopRight, ImDrawFlags_RoundBottomLeft, ImDrawFlags_RoundBottomRight, ImDrawFlags_RoundNone, ImDrawFlags_RoundAll, ImDrawFlags_RoundTop, ImDrawFlags_RoundBottom, ImDrawFlags_RoundLeft, ImDrawFlags_RoundRight, ImDrawFlags_JoinMiter, ImDrawFlags_CapSquare, ImDrawFlags_AAFill, ImDrawFlags_AALines, ImDrawFlags_AALineEnds, ImDrawFlags_StrokeInside, ImDrawFlags_StrokeCenter, ImDrawFlags_StrokeCenterBiased, ImDrawFlags_StrokeOutside, ImDrawFlags_StrokeLegacy, ImDrawFlags_TextNoPixelSnap, ImDrawFlags_UseTexForRoundCorners, ImDrawFlags_UseVtxOffset, ImDrawFlags_AllowTexForRoundCorners_, ImDrawFlags_RoundMask_, ImDrawFlags_AllowInPushScope_, ImDrawFlags_AllowInFrameScope_, ImDrawFlags_StrokeMask_ + ImDrawFlags_ 31 / 31 ImDrawTextFlags_ 4 / 4 - ImFontAtlasFlags_ 4 / 5 missing: ImFontAtlasFlags_NoBakedRoundCorners + ImFontAtlasFlags_ 5 / 5 ImFontFlags_ 5 / 5 ImGuiActivateFlags_ 7 / 7 ImGuiAxis 3 / 3 @@ -2501,20 +2501,20 @@ Struct fields (name found in the port / total; missing listed). Field names are ExampleMemberInfo 4 / 4 ExampleTreeNode 9 / 9 IM_MSVC_RUNTIME_CHECKS_OFF 7 / 7 - IM_MSVC_RUNTIME_CHECKS_RESTORE 79 / 81 missing: CurveTessellationMaxError, DataEnd + IM_MSVC_RUNTIME_CHECKS_RESTORE 80 / 81 missing: DataEnd ImBitVector 1 / 1 ImDrawChannel 2 / 2 ImDrawCmd 9 / 9 ImDrawCmdHeader 3 / 3 ImDrawData 10 / 10 ImDrawDataBuilder 2 / 2 - ImDrawList 16 / 19 missing: _DrawFlagsStack, _InvFringeScale, _FringeScaleIsInteger - ImDrawListSharedData 14 / 17 missing: TexUvCorners, CurveTessellationMaxError, InitialDrawFlags + ImDrawList 19 / 19 + ImDrawListSharedData 17 / 17 ImDrawListSplitter 3 / 3 ImDrawVert 1 / 3 missing: uv, col ImFont 11 / 11 ImFontAtlas 30 / 30 - ImFontAtlasBuilder 21 / 24 missing: PackIdLineFractTexData, PackIdCornersTexData, TexUvCorners + ImFontAtlasBuilder 24 / 24 ImFontAtlasPostProcessData 10 / 10 ImFontAtlasRect 6 / 6 ImFontAtlasRectEntry 3 / 3 diff --git a/imgui_demo.lua b/imgui_demo.lua index 6399cce..af216b6 100644 --- a/imgui_demo.lua +++ b/imgui_demo.lua @@ -10413,6 +10413,9 @@ S.ShowExampleAppCustomRendering = { curve_segments_override_v = 8, colf_stroke = ImVec4(1.000, 0.384, 0.169, 1.000), colf_fill = ImVec4(0.416, 0.378, 0.420, 1.000), + prim_stroke_flags = ImDrawFlags.None, + prim_other_flags = ImDrawFlags.AALineEnds, + scope_flags = nil, -- First init copy current state (see below). -- Canvas points = ImVector(), scrolling = ImVec2(0.0, 0.0), @@ -10496,25 +10499,35 @@ function ShowExampleAppCustomRendering(p_open) ImGui.ColorEdit4("Stroke Color", s.colf_stroke) ImGui.ColorEdit4("Fill Color", s.colf_fill) - -- [Port] ImDrawFlags_StrokeInside/StrokeCenter/StrokeCenterBiased/StrokeOutside/StrokeLegacy, AAFill/AALines/AALineEnds, - -- [Port] JoinMiter/CapSquare, AllowInPushScope_ and ImDrawList::PushDrawFlag()/PopDrawFlag() don't exist in this port - -- [Port] (1.92 stroker): prim_stroke_flags/prim_other_flags/scope_flags and their widgets are not ported, flags = None. ImGui.SeparatorText("Per primitive flags (AddXXX functions)") - ImGui.TextDisabled("(not supported by this port)") -- [Port] + _, s.prim_stroke_flags = ImGui.RadioButton("Default", s.prim_stroke_flags, ImDrawFlags.None); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Inside", s.prim_stroke_flags, ImDrawFlags.StrokeInside); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Center", s.prim_stroke_flags, ImDrawFlags.StrokeCenter); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("CenterBiased", s.prim_stroke_flags, ImDrawFlags.StrokeCenterBiased); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Outside", s.prim_stroke_flags, ImDrawFlags.StrokeOutside); ImGui.SameLine() + _, s.prim_stroke_flags = ImGui.RadioButton("Legacy", s.prim_stroke_flags, ImDrawFlags.StrokeLegacy) + _, s.prim_other_flags = ImGui.CheckboxFlags("AAFill", s.prim_other_flags, ImDrawFlags.AAFill); ImGui.SameLine() -- Note: Only for functions taking flags. + _, s.prim_other_flags = ImGui.CheckboxFlags("AALines", s.prim_other_flags, ImDrawFlags.AALines); ImGui.SameLine() -- Note: Only for functions taking flags. + _, s.prim_other_flags = ImGui.CheckboxFlags("AALineEnds", s.prim_other_flags, ImDrawFlags.AALineEnds); ImGui.SameLine() + _, s.prim_other_flags = ImGui.CheckboxFlags("JoinMiter", s.prim_other_flags, ImDrawFlags.JoinMiter); ImGui.SameLine() + _, s.prim_other_flags = ImGui.CheckboxFlags("CapSquare", s.prim_other_flags, ImDrawFlags.CapSquare) --ImGui.CheckboxFlags("UseTexForRoundCorners", &prim_other_flags, ImDrawFlags_UseTexForRoundCorners); + if s.scope_flags == nil then s.scope_flags = bit32.band(draw_list.Flags, ImDrawFlags.AllowInPushScope_) end -- First init copy current state. ImGui.SeparatorText("Current ImDrawList flags (e.g. PushDrawFlag function)") ImGui.PushID("ScopeFlags") - ImGui.TextDisabled("(not supported by this port)") -- [Port] + _, s.scope_flags = ImGui.CheckboxFlags("AAFill", s.scope_flags, ImDrawFlags.AAFill); ImGui.SameLine() + _, s.scope_flags = ImGui.CheckboxFlags("AALines", s.scope_flags, ImDrawFlags.AALines); ImGui.SameLine() + _, s.scope_flags = ImGui.CheckboxFlags("AALineEnds", s.scope_flags, ImDrawFlags.AALineEnds); ImGui.SameLine() + _, s.scope_flags = ImGui.CheckboxFlags("StrokeLegacy", s.scope_flags, ImDrawFlags.StrokeLegacy) --ImGui.CheckboxFlags("UseTexForRoundCorners", &scope_flags, ImDrawFlags_UseTexForRoundCorners); ImGui.PopID() ImGui.Spacing() - -- [Port] draw_list->PushDrawFlag(ImDrawFlags_AllowInPushScope_, false); // Reset all existing (very unusual: done for this demo) - -- [Port] draw_list->PushDrawFlag(scope_flags, true); - local flags = ImDrawFlags.None -- [Port] prim_stroke_flags | prim_other_flags + draw_list:PushDrawFlag(ImDrawFlags.AllowInPushScope_, false) -- Reset all existing (very unusual: done for this demo) + draw_list:PushDrawFlag(s.scope_flags, true) + local flags = bit32.bor(s.prim_stroke_flags, s.prim_other_flags) - -- [Port] In this port AddNgon/AddCircle/AddEllipse/AddTriangle/AddLine*/AddBezier*/PathFillConvex take no flags argument (dropped below). local sz = s.sz local start_pos = ImGui.GetCursorScreenPos() local pi = 3.141592 @@ -10582,7 +10595,7 @@ function ShowExampleAppCustomRendering(p_open) for n = 0, 4 - 1 do draw_list:PathLineTo(ImVec2(x + half_sz + rotating_square[n + 1].x, y + half_sz + rotating_square[n + 1].y)) end - draw_list:PathFillConvex(col) -- [Port] no flags argument + draw_list:PathFillConvex(col, flags) x = x + step -- Path @@ -10609,14 +10622,14 @@ function ShowExampleAppCustomRendering(p_open) -- First row uses a thickness of 1.0f, second row uses the configurable thickness local col = ImColor(s.colf_stroke):U32() local th = (row == 0) and 1.0 or thickness - draw_list:AddNgon(ImVec2(x + half_sz, y + half_sz), half_sz, col, s.ngon_segments, th); x = x + step -- N-gon - draw_list:AddCircle(ImVec2(x + half_sz, y + half_sz), half_sz, col, circle_segments, th); x = x + step -- Circle - draw_list:AddEllipse(ImVec2(x + half_sz, y + half_sz), ImVec2(half_sz, sz * 0.3), col, -0.3, circle_segments, th); x = x + step -- Ellipse + draw_list:AddNgon(ImVec2(x + half_sz, y + half_sz), half_sz, col, s.ngon_segments, th, flags); x = x + step -- N-gon + draw_list:AddCircle(ImVec2(x + half_sz, y + half_sz), half_sz, col, circle_segments, th, flags); x = x + step -- Circle + draw_list:AddEllipse(ImVec2(x + half_sz, y + half_sz), ImVec2(half_sz, sz * 0.3), col, -0.3, circle_segments, th, flags); x = x + step -- Ellipse draw_list:AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0, th, flags); x = x + step -- Square draw_list:AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, flags); x = x + step -- Square with all rounded corners draw_list:AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, bit32.bor(flags, corners_tl_br)); x = x + step -- Square with two rounded corners - draw_list:AddTriangle(ImVec2(x + sz * 0.5, y), ImVec2(x + sz, y + sz), ImVec2(x, y + sz), col, th); x = x + step -- Triangle - draw_list:AddTriangle(ImVec2(x + sz * 0.2, y), ImVec2(x + sz * 0.4, y + sz), ImVec2(x, y + sz), col, th); x = x + step - math.floor(sz * 0.6) -- Thin triangle + draw_list:AddTriangle(ImVec2(x + sz * 0.5, y), ImVec2(x + sz, y + sz), ImVec2(x, y + sz), col, th, flags); x = x + step -- Triangle + draw_list:AddTriangle(ImVec2(x + sz * 0.2, y), ImVec2(x + sz * 0.4, y + sz), ImVec2(x, y + sz), col, th, flags); x = x + step - math.floor(sz * 0.6) -- Thin triangle -- Concave shape: U for _, p in ipairs(concave_shape) do @@ -10634,15 +10647,15 @@ function ShowExampleAppCustomRendering(p_open) -- Horizontal/Vertical/Diagonal lines local off = math.floor(sz * 0.4) - draw_list:AddLineH(x, x + sz, y, col, th) - draw_list:AddLine(ImVec2(x, y + off), ImVec2(x + sz, y + off), col, th) + draw_list:AddLineH(x, x + sz, y, col, th, flags) + draw_list:AddLine(ImVec2(x, y + off), ImVec2(x + sz, y + off), col, th, flags) x = x + step - draw_list:AddLineV(x, y, y + sz, col, th) - draw_list:AddLine(ImVec2(x + off, y + sz), ImVec2(x + off, y), col, th) + draw_list:AddLineV(x, y, y + sz, col, th, flags) + draw_list:AddLine(ImVec2(x + off, y + sz), ImVec2(x + off, y), col, th, flags) x = x + step - math.floor(half_sz) if not (draw_fill and draw_strokes) then -- -V560 - draw_list:AddLine(ImVec2(x, y), ImVec2(x + half_sz, y + sz), col, th) - draw_list:AddLine(ImVec2(x, y + sz), ImVec2(x + half_sz, y), col, th) + draw_list:AddLine(ImVec2(x, y), ImVec2(x + half_sz, y + sz), col, th, flags) + draw_list:AddLine(ImVec2(x, y + sz), ImVec2(x + half_sz, y), col, th, flags) end x = x + step - math.floor(half_sz) @@ -10661,12 +10674,12 @@ function ShowExampleAppCustomRendering(p_open) x = x + step -- Quadratic Bezier Curve (3 control points) - draw_list:AddBezierQuadratic(ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), ImVec2(x + cp3[3].x, y + cp3[3].y), col, th, curve_segments) + draw_list:AddBezierQuadratic(ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), ImVec2(x + cp3[3].x, y + cp3[3].y), col, th, curve_segments, flags) x = x + step -- Cubic Bezier Curve (4 control points) if not (draw_fill and draw_strokes) then -- -V560 - draw_list:AddBezierCubic(ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), ImVec2(x + cp4[4].x, y + cp4[4].y), col, th, curve_segments) + draw_list:AddBezierCubic(ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), ImVec2(x + cp4[4].x, y + cp4[4].y), col, th, curve_segments, flags) x = x + step end end @@ -10674,7 +10687,8 @@ function ShowExampleAppCustomRendering(p_open) end ImGui.Dummy(ImVec2(step * 15.5, step * 4.0)) - -- [Port] draw_list->PopDrawFlag(); x2 + draw_list:PopDrawFlag() + draw_list:PopDrawFlag() ImGui.Text("ImDrawList Vector Rendering Reference:") ImGui.TextLinkOpenURL("https://github.com/ocornut/imgui/wiki/Draw-List") diff --git a/imgui_draw.lua b/imgui_draw.lua index 5474d00..4080fb7 100644 --- a/imgui_draw.lua +++ b/imgui_draw.lua @@ -34,7 +34,6 @@ local function WV(b, i, x, y, u, v, col) local o = (i - 1) * 36 writef64(b, o, x); writef64(b, o + 8, y); writef64(b, o + 16, u); writef64(b, o + 24, v); writeu32(b, o + 32, col) end -local function WVP(b, i, p, uv, col) WV(b, i, p.x, p.y, uv.x, uv.y, col) end local IM_FONTGLYPH_INDEX_UNUSED = 0xFFFF -- ((ImU16)-1) local IM_FONTGLYPH_INDEX_NOT_FOUND = 0xFFFE -- ((ImU16)-2) @@ -1951,8 +1950,9 @@ end --- @param atlas ImFontAtlas function ImFontAtlasBuildUpdateTexData(atlas) - ImFontAtlasBuildUpdateTexDataLines(atlas) ImFontAtlasBuildUpdateTexDataBasic(atlas) + ImFontAtlasBuildUpdateTexDataLines(atlas) + -- [Port] TODO baked round corners: ImFontAtlasBuildUpdateTexDataCorners(atlas) end -- Create a new texture, discard previous one @@ -2090,59 +2090,121 @@ function ImFontAtlasBuildUpdateTexDataLines(atlas) if add_and_draw then -- Register texture region for thick lines -- The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row - local pack_size = ImVec2(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) -- ImVec2i + local pack_size = ImVec2(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT) -- ImVec2i builder.PackIdLinesTexData = atlas:AddCustomRect(pack_size.x, pack_size.y, r) IM_ASSERT(builder.PackIdLinesTexData ~= ImFontAtlasRectId_Invalid) end - for n = 0, IM_DRAWLIST_TEX_LINES_WIDTH_MAX do -- +1 because of the zero-width row - -- Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - local y = n - local line_width = n + do + -- FIXME: Lines 0 to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH(4) are currently never used and could be ditched, saving DETAILED_WIDTH*WIDTH_MAX + for n = 0, IM_DRAWLIST_TEX_LINES_WIDTH_MAX do -- +1 because of the zero-width row + -- Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle + local y = n + local line_width = n + local pad_left = math.floor((r.w - line_width) / 2) + local pad_right = r.w - (pad_left + line_width) + IM_ASSERT(pad_left + line_width + pad_right == r.w and y < r.h) -- Make sure we're inside the texture bounds before we start writing pixels - --- @type int # If not floored, will cause black and purple horizontal stripes appearing on the right of the arrow(triangle) in the upper-left most area of the atlas - local pad_left = math.floor((r.w - line_width) / 2) - --- @type int - local pad_right = r.w - (pad_left + line_width) - - IM_ASSERT(pad_left + line_width + pad_right == r.w and y < r.h) -- Make sure we're inside the texture bounds before we start writing pixels - - -- Write each slice - if (add_and_draw and tex.Format == ImTextureFormat.Alpha8) then - local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) - - for i = 0, pad_left - 1 do - write_ptr[write_ptr_base + i] = 0x00 + -- Write each slice + if add_and_draw and tex.Format == ImTextureFormat.Alpha8 then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + for i = 0, pad_left - 1 do + write_ptr[write_ptr_base + i] = 0x00 + end + for i = 0, line_width - 1 do + write_ptr[write_ptr_base + pad_left + i] = 0xFF + end + for i = 0, pad_right - 1 do + write_ptr[write_ptr_base + pad_left + line_width + i] = 0x00 + end + elseif add_and_draw and tex.Format == ImTextureFormat.RGBA32 then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + for i = 0, pad_left - 1 do + write_u32(write_ptr, write_ptr_base + i * 4, IM_COL32(255, 255, 255, 0)) + end + for i = 0, line_width - 1 do + write_u32(write_ptr, write_ptr_base + (pad_left + i) * 4, IM_COL32_WHITE) + end + for i = 0, pad_right - 1 do + write_u32(write_ptr, write_ptr_base + (pad_left + line_width + i) * 4, IM_COL32(255, 255, 255, 0)) + end end - for i = 0, line_width - 1 do - write_ptr[write_ptr_base + pad_left + i] = 0xFF - end + -- Refresh UV coordinates + local uv0_x, uv0_y = (r.x + pad_left - 0.5) * atlas.TexUvScale.x, (r.y + y) * atlas.TexUvScale.y + local uv1_x, uv1_y = (r.x + pad_left + line_width + 0.5) * atlas.TexUvScale.x, (r.y + y + 1) * atlas.TexUvScale.y + local half_v = (uv0_y + uv1_y) * 0.5 -- Calculate a constant V in the middle of the row to avoid sampling artifacts + -- Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. + local inv_line_width = (line_width > 0) and (1.0 / line_width) or 0.0 + builder.TexUvLines[n] = ImVec4(uv0_x, uv1_x, half_v, inv_line_width) + end + end - for i = 0, pad_right - 1 do - write_ptr[write_ptr_base + pad_left + line_width + i] = 0x00 - end - elseif (add_and_draw and tex.Format == ImTextureFormat.RGBA32) then - local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + do + -- Calculate super sampled line textures for smaller line widths to make transition between sizes smoother. - for i = 0, pad_left - 1 do - write_u32(write_ptr, write_ptr_base + i * 4, IM_COL32(255, 255, 255, 0)) - end - - for i = 0, line_width - 1 do - write_u32(write_ptr, write_ptr_base + (pad_left + i) * 4, IM_COL32_WHITE) - end - - for i = 0, pad_right - 1 do - write_u32(write_ptr, write_ptr_base + (pad_left + line_width + i) * 4, IM_COL32(255, 255, 255, 0)) - end + -- Calculate ramp used for all the textures. + local ramp = {} -- ImU8[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT], 0-based + for n = 0, IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT - 1 do + ramp[n] = math.floor((n / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) * 255.0) end - -- Refresh UV coordinates - local uv0 = ImVec2(r.x + pad_left - 1, r.y + y) * atlas.TexUvScale - local uv1 = ImVec2(r.x + pad_left + line_width + 1, r.y + y + 1) * atlas.TexUvScale - local half_v = (uv0.y + uv1.y) * 0.5 -- Calculate a constant V in the middle of the row to avoid sampling artifacts - atlas.TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v) + for n = 0, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT - 1 do + local y = IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n + -- For integer thickness lines the one pixel line texture looks like this: + -- [ 0 | 1 | 0 ] + -- :...........: + -- Since the texture samples are at the center of the texel, we only use range above. + -- The result after bilinear filtering is a triangle across the used uv range. + -- + -- To super 2x sample that signal, we end up with following texture: + -- [ 0 | .5 | 1 | .5 | 0 ] + -- :.......................: + -- One might think that there should be 2 opaque pixels in the middle section, but no since we're super sampling the triangle signal. + local line_width = 1 + n + IM_ASSERT(IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + line_width + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT <= r.w and y < r.h) -- Make sure we're inside the texture bounds before we start writing pixels + + -- Write each slice + if add_and_draw and tex.Format == ImTextureFormat.Alpha8 then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + local idx = 0 + for i = 0, IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT - 1 do + write_ptr[write_ptr_base + idx] = ramp[i]; idx = idx + 1 + end + for _ = 0, line_width - 1 do + write_ptr[write_ptr_base + idx] = 0xFF; idx = idx + 1 + end + for i = IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT - 1, 0, -1 do + write_ptr[write_ptr_base + idx] = ramp[i]; idx = idx + 1 + end + while idx < r.w do + write_ptr[write_ptr_base + idx] = 0x0; idx = idx + 1 + end + elseif add_and_draw and tex.Format == ImTextureFormat.RGBA32 then + local write_ptr, write_ptr_base = tex:GetPixelsAt(r.x, r.y + y) + local idx = 0 + for i = 0, IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT - 1 do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, ramp[i])); idx = idx + 1 + end + for _ = 0, line_width - 1 do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, 255)); idx = idx + 1 + end + for i = IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT - 1, 0, -1 do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, ramp[i])); idx = idx + 1 + end + while idx < r.w do + write_u32(write_ptr, write_ptr_base + idx * 4, IM_COL32(255, 255, 255, 0)); idx = idx + 1 + end + end + + -- Refresh UV coordinates + local uv0_x, uv0_y = (r.x + 0.5) * atlas.TexUvScale.x, (r.y + y) * atlas.TexUvScale.y + local uv1_x, uv1_y = (r.x - 0.5 + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT * 2 + line_width) * atlas.TexUvScale.x, (r.y + y + 1) * atlas.TexUvScale.y + local half_v = (uv0_y + uv1_y) * 0.5 -- Calculate a constant V in the middle of the row to avoid sampling artifacts + -- Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. + local inv_line_width = 1.0 / (1.0 + n / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) + builder.TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n] = ImVec4(uv0_x, uv1_x, half_v, inv_line_width) + end end end @@ -2220,7 +2282,7 @@ function ImFontAtlasUpdateDrawListsSharedData(atlas) for _, shared_data in atlas.DrawListSharedDatas:iter() do if (shared_data.FontAtlas == atlas) then shared_data.TexUvWhitePixel = atlas.TexUvWhitePixel - shared_data.TexUvLines = atlas.TexUvLines + shared_data.TexUvLines = atlas.Builder.TexUvLines end end end @@ -3574,6 +3636,37 @@ local function IM_FIXNORMAL2F(VX, VY) return VX, VY end +-- - The miter limit describes how much the miter cross section can be scaled before we turn it into a bevel instead. +-- The minimum value of miter limit is 1.0, which translates to 180 (all corners are bevels), the higher the number, +-- the closer to zero the angle is (never bevel a corner). The miter limit angle can be calculated as: +-- miter_limit_angle = 2 * acos(1 / miter_limit) +-- - We can use the miter limit angle to figure maximum number of beveled corners in a convex polygon, +-- to prevent having allocate much extra memory (for a reasonable miter limit). +-- (n - 2) * 360 > k * miter_limit_angle + (n - k) * 360 +-- Where, n = number of corners in a polygon, k = beveled corners. Solved for k, simplifies to: +-- k < 360 / (180 - miter_limit_angle) +-- To find value strictly less than: +-- max_bevels = k = floor(360/(180-miter_limit_angle)) +local IM_POLYLINE_MITER_ANGLE_LIMIT = -0.9999619 -- Safeguard for miter corner calculation to avoid div by zero. cos(179.5) +local IM_POLYLINE_MITER_LIMIT = 4.0 -- (~29 deg corner) +local IM_POLYLINE_CONVEX_POLY_MAX_BEVELS = 2 -- For miter value 4.0 + +--- @param col ImU32 +--- @param alpha_mul float +local function ImAlphaMultiply(col, alpha_mul) + IM_ASSERT_PARANOID(alpha_mul >= 0.0 and alpha_mul < 1.0) -- We don't clamp! + local a = bit32.rshift(bit32.band(col, IM_COL32_A_MASK), IM_COL32_A_SHIFT) + a = math.floor(a * alpha_mul) -- We don't need to clamp 0..255 because alpha is in 0..1 range. + return bit32.bor(bit32.band(col, bit32.bnot(IM_COL32_A_MASK)), bit32.lshift(a, IM_COL32_A_SHIFT)) +end + +-- #define _GetStrokePos(_FLAGS, _DEFAULT_STROKE_POS) +local function _GetStrokePos(flags, default_stroke_pos) + local stroke_pos = bit32.band(flags, ImDrawFlags.StrokeMask_) + if stroke_pos ~= 0 then return stroke_pos end + return default_stroke_pos +end + ------------------------------------------------------------------------------- -- [SECTION] ImDrawData ------------------------------------------------------------------------------- @@ -3625,6 +3718,25 @@ local function IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(_RAD, _MAXERROR) return ImCl local function IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(_N, _MAXERROR) return (_MAXERROR / (1 - ImCos(IM_PI / ImMax(_N, IM_PI)))) end local function IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_ERROR(_N, _RAD) return ((1 - ImCos(IM_PI / ImMax(_N, IM_PI))) / _RAD) end +-- Calculates arc step and step count for 90 degree arc. The calculated arc step will complete a 90 degree arc when stepped arc_step_count times. +-- [Luau] returns arc_step, arc_step_count (C++ out-references) +local function CalcCornerArcStepAndCount(segment_count) + local quarter = IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4 + local arc_step = ImClamp(math.floor(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / segment_count), 1, quarter) + local arc_step_count = math.floor(quarter / arc_step) + if (arc_step * arc_step_count) ~= quarter then -- Make sure that the arc step can iterate 90 degree arc. + arc_step = math.floor(quarter / arc_step_count) + end + return arc_step, arc_step_count +end + +local function ApproxApothem(num_segments) + -- Calculates approximation (within 0.001%) distance from the center of the circumscribed circle to the edge of the polygon, cos(pi / n) + IM_ASSERT_PARANOID(num_segments >= 3) + local u = 1.0 / num_segments + return ((4.058712 * u * u) - 4.934802) * u * u + 1.0 +end + ------------------------------------------------------------------------------- -- [SECTION] ImDrawList ------------------------------------------------------------------------------- @@ -4093,101 +4205,6 @@ function MT.ImDrawList:ChannelsMerge() self._Splitter:Merge(self) end --- @param n int # 0-based function MT.ImDrawList:ChannelsSetCurrent(n) self._Splitter:SetCurrentChannel(self, n) end --- - We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds. --- - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. ---- @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 - end - - local uv = self._Data.TexUvWhitePixel - - if bit32.band(self.Flags, ImDrawFlags.AAFill) ~= 0 then - local AA_SIZE = self._FringeScale - local col_trans = bit32.band(col, bit32.bnot(IM_COL32_A_MASK)) - local idx_count = (points_count - 2) * 3 + points_count * 6 - local vtx_count = points_count * 2 - self:PrimReserve(idx_count, vtx_count) - - local vtx_inner_idx = self._VtxCurrentIdx - local vtx_outer_idx = self._VtxCurrentIdx + 1 - local idx_data = self.IdxBuffer.Data - local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops - for i = 2, points_count - 1 do - idx_data[idx_write_ptr + 0] = vtx_inner_idx; idx_data[idx_write_ptr + 1] = vtx_inner_idx + ((i - 1) * 2); idx_data[idx_write_ptr + 2] = vtx_inner_idx + (i * 2) - idx_write_ptr = idx_write_ptr + 3 - end - - -- Compute normals - local temp_buffer = self._Data.TempBuffer - if points_count > temp_buffer.Capacity then temp_buffer:reserve_discard(points_count) end -- [Luau] skip the method lookup when no growth is needed - local temp_normals = self._Data.TempBuffer.Data - - local i0 = points_count - for i1 = 1, points_count do - local p0 = points[i0] - local p1 = points[i1] - local dx = p1.x - p0.x - local dy = p1.y - p0.y - dx, dy = IM_NORMALIZE2F_OVER_ZERO(dx, dy) - temp_normals[i0].x = dy - temp_normals[i0].y = -dx - - i0 = i1 - end - - local vtx_data = self.VtxBuffer.Buf - local vtx_write_ptr = self._VtxWritePtr - local uv_x, uv_y = uv.x, uv.y - i0 = points_count - for i1 = 1, points_count do - local n0 = temp_normals[i0] - local n1 = temp_normals[i1] - local dm_x = (n0.x + n1.x) * 0.5 - local dm_y = (n0.y + n1.y) * 0.5 - dm_x, dm_y = IM_FIXNORMAL2F(dm_x, dm_y) - dm_x = dm_x * AA_SIZE * 0.5 - dm_y = dm_y * AA_SIZE * 0.5 - - local p1 = points[i1] - WV(vtx_data, vtx_write_ptr + 0, p1.x - dm_x, p1.y - dm_y, uv_x, uv_y, col) - WV(vtx_data, vtx_write_ptr + 1, p1.x + dm_x, p1.y + dm_y, uv_x, uv_y, col_trans) - vtx_write_ptr = vtx_write_ptr + 2 - - idx_data[idx_write_ptr + 0] = vtx_inner_idx + ((i1 - 1) * 2); idx_data[idx_write_ptr + 1] = vtx_inner_idx + ((i0 - 1) * 2); idx_data[idx_write_ptr + 2] = vtx_outer_idx + ((i0 - 1) * 2) - idx_data[idx_write_ptr + 3] = vtx_outer_idx + ((i0 - 1) * 2); idx_data[idx_write_ptr + 4] = vtx_outer_idx + ((i1 - 1) * 2); idx_data[idx_write_ptr + 5] = vtx_inner_idx + ((i1 - 1) * 2) - idx_write_ptr = idx_write_ptr + 6 - - i0 = i1 - end - self._VtxWritePtr = vtx_write_ptr - self._IdxWritePtr = idx_write_ptr - self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count - else - local idx_count = (points_count - 2) * 3 - local vtx_count = points_count - self:PrimReserve(idx_count, vtx_count) - - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - for i = 1, vtx_count do - local vtx_write_ptr = self._VtxWritePtr - WVP(vtx_data, vtx_write_ptr + 0, points[i], uv, col) - self._VtxWritePtr = vtx_write_ptr + 1 - end - for i = 3, points_count do - local idx_write_ptr = self._IdxWritePtr - idx_data[idx_write_ptr + 0] = self._VtxCurrentIdx; idx_data[idx_write_ptr + 1] = self._VtxCurrentIdx + i - 2; idx_data[idx_write_ptr + 2] = self._VtxCurrentIdx + i - 1 - self._IdxWritePtr = idx_write_ptr + 3 - end - - self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count - end -end - function MT.ImDrawList:PushTexture(tex_ref) self._TextureStack:push_back(tex_ref) self._CmdHeader.TexRef = tex_ref @@ -4358,249 +4375,641 @@ function MT.ImDrawList:PrimRectUV(a, c, uv_a, uv_c, col) self._IdxWritePtr = idx_write_ptr + 6 end ---- @param points ImVec2[] ---- @param points_count int ---- @param col ImU32 ---- @param thickness float ---- @param flags ImDrawFlags ---- @param max_inner_offset float # [Luau] unused by the pre-1.93 stroker +--- @param thickness float +--- @return float +-- [Luau] local function (also MT.ImDrawList._CalculateCenterBiasedOffset) +local function _CalculateCenterBiasedOffset(self, thickness) + -- Old jumpy one. + -- return IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + + -- Calculate outside offset for stroke position ImDrawFlags_StrokeCenterBiased + -- so that one side the of the line is always at pixel boundary. + -- On integer thickness both sides of the line are on pixel boundary. + local screen_thickness = thickness * self._InvFringeScale + local s_thickness = math.floor(screen_thickness) -- (int) of a positive value + return (math.floor(s_thickness / 2) + (s_thickness % 2) * (screen_thickness - s_thickness)) * self._FringeScale +end +MT.ImDrawList._CalculateCenterBiasedOffset = _CalculateCenterBiasedOffset + +-- Check if R is considered to be worthy rounding. Can be only used within ImDrawList due to use of _FringeScale. +-- #define IM_HAS_ROUNDING(RAD) ((RAD) >= (0.25f * _FringeScale)) -- [Luau] written inline at each use + +-- Select texture and adjust fringe size for specified line thickness. +-- [Luau] returns uv0.x, uv0.y, uv1.x, uv1.y, fringe instead of writing to out parameters +--- @param screen_thickness float +--- @param flags ImDrawFlags +local function _SelectLineTexture(self, screen_thickness, flags) + -- We assume that the screen_thickness has been clamped to 1.0 by the caller. + IM_ASSERT_PARANOID(screen_thickness >= 1.0) + + if bit32.band(flags, ImDrawFlags.AALines) ~= 0 then + -- Handle the thickness between 1.0 - IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. + -- We use super sampled textures in this range to make texture changes less noticeable. + -- There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT-1 maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). + local texture_idx + if screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH then + texture_idx = math.floor((screen_thickness - 1.0) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.5) + (IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + elseif screen_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1.5 then + texture_idx = math.floor(screen_thickness + 0.5) + else + texture_idx = IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1 -- Catches large values of screen_thickness (and +inf/NaN) that a ImMin() wouldn't. + end + + -- Scale the fringe so that the texture matches the line thickness. + local tex_uvs = self._Data.TexUvLines[texture_idx] -- 0-based table + return tex_uvs.x, tex_uvs.z, tex_uvs.y, tex_uvs.z, self._FringeScale * screen_thickness * tex_uvs.w -- tex_uvs.w = 1 / thickness + else + -- For no anti-alias we use fully opaque texture, and no fringe expansion. + local uv = self._Data.TexUvWhitePixel + return uv.x, uv.y, uv.x, uv.y, 0.0 + end +end +MT.ImDrawList._SelectLineTexture = _SelectLineTexture + +-- [Luau] scratch arrays (normals x/y and squared segment lengths) used instead of _Data->TempBuffer: no ImVec2 per point +local poly_normals_x, poly_normals_y, poly_sqr_lengths = {}, {}, {} + +--- @param points ImVec2[] # 1-based +--- @param points_count int +--- @param col ImU32 +--- @param thickness float +--- @param flags ImDrawFlags +--- @param max_inner_offset float +-- [Luau] IM_APPEND_VTX()/IM_APPEND_TRI() are expanded inline: vertex/index write pointers and _VtxCurrentIdx kept in locals, stored back at the end @native function MT.ImDrawList:_AddPolyline(points, points_count, col, thickness, flags, max_inner_offset) - if points_count < 2 or bit32.band(col, IM_COL32_A_MASK) == 0 then return end - local closed = bit32.band(flags, ImDrawFlags.Closed) ~= 0 - local opaque_uv = self._Data.TexUvWhitePixel - local count = closed and points_count or points_count - 1 -- Number of line segments - local thick_line = thickness > self._FringeScale + local screen_thickness = thickness * self._InvFringeScale + if screen_thickness < 1.0 then + if screen_thickness < 1.0 / 255.0 then + return + end + col = ImAlphaMultiply(col, screen_thickness) + screen_thickness = 1.0 + thickness = self._FringeScale + end + -- If this assert triggers on legacy code: -- - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. -- - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. -- Read more details near AddRect() + see "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. + flags = bit32.bor(flags, self.Flags) IM_ASSERT_USER_ERROR_RET(bit32.band(flags, ImDrawFlags.InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?") - if bit32.band(self.Flags, ImDrawFlags.AALines) ~= 0 then - -- Anti-aliased stroke - local AA_SIZE = self._FringeScale - local col_trans = bit32.band(col, bit32.bnot(IM_COL32_A_MASK)) + -- Allocate data for temp buffers + local normals_x, normals_y, sqr_lengths = poly_normals_x, poly_normals_y, poly_sqr_lengths - -- Thicknesses <1.0 should behave like thickness 1.0 - thickness = ImMax(thickness, 1.0) - local integer_thickness = ImFloor(thickness) - local fractional_thickness = thickness - integer_thickness - - -- Do we want to draw this line using a texture? - -- [Luau] pre-1.93 stroker kept until the 1.93 one is ported. ImDrawListFlags_AntiAliasedLinesUseTex no longer exists (== AALines) - -- and was never set by this port's SetupDrawListSharedData() before, so the texture path stays disabled (unchanged output). - local use_texture = false - - IM_ASSERT_PARANOID((not use_texture) or bit32.band(self._Data.Font.OwnerAtlas.Flags, ImFontAtlasFlags.NoBakedLines) == 0) - - local idx_count = use_texture and (count * 6) or (thick_line and count * 18 or count * 12) - local vtx_count = use_texture and (points_count * 2) or (thick_line and points_count * 4 or points_count * 3) - PrimReserve(self, idx_count, vtx_count) - - -- Temporary buffer - local temp_buffer_size = points_count * ((use_texture or not thick_line) and 3 or 5) - local temp_buffer = self._Data.TempBuffer - if temp_buffer_size > temp_buffer.Capacity then temp_buffer:reserve_discard(temp_buffer_size) end -- [Luau] skip the method lookup when no growth is needed - local temp_normals = self._Data.TempBuffer.Data - local temp_points = self._Data.TempBuffer.Data; local temp_points_start = points_count + 1 - - -- Calculate normals for each line segment - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local p1 = points[i1] - local p2 = points[i2] - local dx = p2.x - p1.x - local dy = p2.y - p1.y - dx, dy = IM_NORMALIZE2F_OVER_ZERO(dx, dy) - temp_normals[i1].x = dy - temp_normals[i1].y = -dx - end - if not closed then - ImVec2_Copy(temp_normals[points_count], temp_normals[points_count - 1]) - end - - -- If we are drawing a one-pixel-wide line without a texture, or a textured line of any width - if use_texture or not thick_line then - -- [PATH 1] Texture-based lines (thick or non-thick) - -- [PATH 2] Non texture-based lines (non-thick) - local half_draw_size = use_texture and (thickness * 0.5 + 1) or AA_SIZE - - -- If line is not closed, the first and last points need to be generated differently - if not closed then - ImVec2_CopyV(temp_points[temp_points_start + 0], ImVec2_AddVA(points[1], ImVec2_MulVX(temp_normals[1], half_draw_size))) - ImVec2_CopyV(temp_points[temp_points_start + 1], ImVec2_SubVA(points[1], ImVec2_MulVX(temp_normals[1], half_draw_size))) - ImVec2_CopyV(temp_points[temp_points_start + (points_count - 1) * 2 + 0], ImVec2_AddVA(points[points_count], ImVec2_MulVX(temp_normals[points_count], half_draw_size))) - ImVec2_CopyV(temp_points[temp_points_start + (points_count - 1) * 2 + 1], ImVec2_SubVA(points[points_count], ImVec2_MulVX(temp_normals[points_count], half_draw_size))) - end - - -- Generate indices and vertices - local idx1 = self._VtxCurrentIdx - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local idx2 = (i1 == points_count) and self._VtxCurrentIdx or (idx1 + (use_texture and 2 or 3)) - - -- Average normals - local dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5 - local dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5 - dm_x, dm_y = IM_FIXNORMAL2F(dm_x, dm_y) - dm_x = dm_x * half_draw_size - dm_y = dm_y * half_draw_size - - -- Add temporary vertices for the outer edges - local out_off = (i2 - 1) * 2 - temp_points[temp_points_start + out_off + 0].x = points[i2].x + dm_x - temp_points[temp_points_start + out_off + 0].y = points[i2].y + dm_y - temp_points[temp_points_start + out_off + 1].x = points[i2].x - dm_x - temp_points[temp_points_start + out_off + 1].y = points[i2].y - dm_y - - if use_texture then - -- Add indices for two triangles - idx_data[idx_write_ptr + 0] = idx2 + 0; idx_data[idx_write_ptr + 1] = idx1 + 0; idx_data[idx_write_ptr + 2] = idx1 + 1 - idx_data[idx_write_ptr + 3] = idx2 + 1; idx_data[idx_write_ptr + 4] = idx1 + 1; idx_data[idx_write_ptr + 5] = idx2 + 0 - idx_write_ptr = idx_write_ptr + 6 - else - -- Add indices for four triangles - idx_data[idx_write_ptr + 0] = idx2 + 0; idx_data[idx_write_ptr + 1] = idx1 + 0; idx_data[idx_write_ptr + 2] = idx1 + 2 - idx_data[idx_write_ptr + 3] = idx1 + 2; idx_data[idx_write_ptr + 4] = idx2 + 2; idx_data[idx_write_ptr + 5] = idx2 + 0 - idx_data[idx_write_ptr + 6] = idx2 + 1; idx_data[idx_write_ptr + 7] = idx1 + 1; idx_data[idx_write_ptr + 8] = idx1 + 0 - idx_data[idx_write_ptr + 9] = idx1 + 0; idx_data[idx_write_ptr + 10] = idx2 + 0; idx_data[idx_write_ptr + 11] = idx2 + 1 - idx_write_ptr = idx_write_ptr + 12 - end - - idx1 = idx2 - end - self._IdxWritePtr = idx_write_ptr - - -- Add vertices - local vtx_write_ptr = self._VtxWritePtr - if use_texture then - -- Texture-based: need to implement TexUvLines lookup - local tex_uvs = self._Data.TexUvLines[integer_thickness] - local tex_uv0 = ImVec2(tex_uvs.x, tex_uvs.y) - local tex_uv1 = ImVec2(tex_uvs.z, tex_uvs.w) - for i = 0, points_count - 1 do - WVP(vtx_data, vtx_write_ptr + 0, temp_points[temp_points_start + i * 2 + 0], tex_uv0, col) - WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 2 + 1], tex_uv1, col) - vtx_write_ptr = vtx_write_ptr + 2 - end - else - -- If we're not using a texture, we need the center vertex as well - for i = 0, points_count - 1 do - WVP(vtx_data, vtx_write_ptr + 0, points[i + 1], opaque_uv, col) - WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 2 + 0], opaque_uv, col_trans) - WVP(vtx_data, vtx_write_ptr + 2, temp_points[temp_points_start + i * 2 + 1], opaque_uv, col_trans) - vtx_write_ptr = vtx_write_ptr + 3 - end - end - self._VtxWritePtr = vtx_write_ptr - else - -- [PATH 3] Non texture-based lines (thick) - local half_inner_thickness = (thickness - AA_SIZE) * 0.5 - - -- If line is not closed, handle first and last points - if not closed then - local points_last = points_count - 1 - ImVec2_CopyV(temp_points[temp_points_start + 0], ImVec2_AddVA(points[1], ImVec2_MulVX(temp_normals[1], (half_inner_thickness + AA_SIZE)))) - ImVec2_CopyV(temp_points[temp_points_start + 1], ImVec2_AddVA(points[1], ImVec2_MulVX(temp_normals[1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + 2], ImVec2_SubVA(points[1], ImVec2_MulVX(temp_normals[1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + 3], ImVec2_SubVA(points[1], ImVec2_MulVX(temp_normals[1], (half_inner_thickness + AA_SIZE)))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 0], ImVec2_AddVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], (half_inner_thickness + AA_SIZE)))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 1], ImVec2_AddVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 2], ImVec2_SubVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], half_inner_thickness))) - ImVec2_CopyV(temp_points[temp_points_start + points_last * 4 + 3], ImVec2_SubVA(points[points_count], ImVec2_MulVX(temp_normals[points_last + 1], (half_inner_thickness + AA_SIZE)))) - end - - -- Generate indices and vertices - local idx1 = self._VtxCurrentIdx - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local idx2 = (i1 == points_count) and self._VtxCurrentIdx or (idx1 + 4) - - -- Average normals - local dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5 - local dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5 - dm_x, dm_y = IM_FIXNORMAL2F(dm_x, dm_y) - local dm_out_x = dm_x * (half_inner_thickness + AA_SIZE) - local dm_out_y = dm_y * (half_inner_thickness + AA_SIZE) - local dm_in_x = dm_x * half_inner_thickness - local dm_in_y = dm_y * half_inner_thickness - - -- Add temporary vertices - local out_off = (i2 - 1) * 4 - temp_points[temp_points_start + out_off + 0].x = points[i2].x + dm_out_x - temp_points[temp_points_start + out_off + 0].y = points[i2].y + dm_out_y - temp_points[temp_points_start + out_off + 1].x = points[i2].x + dm_in_x - temp_points[temp_points_start + out_off + 1].y = points[i2].y + dm_in_y - temp_points[temp_points_start + out_off + 2].x = points[i2].x - dm_in_x - temp_points[temp_points_start + out_off + 2].y = points[i2].y - dm_in_y - temp_points[temp_points_start + out_off + 3].x = points[i2].x - dm_out_x - temp_points[temp_points_start + out_off + 3].y = points[i2].y - dm_out_y - - -- Add indices - idx_data[idx_write_ptr + 0] = idx2 + 1; idx_data[idx_write_ptr + 1] = idx1 + 1; idx_data[idx_write_ptr + 2] = idx1 + 2 - idx_data[idx_write_ptr + 3] = idx1 + 2; idx_data[idx_write_ptr + 4] = idx2 + 2; idx_data[idx_write_ptr + 5] = idx2 + 1 - idx_data[idx_write_ptr + 6] = idx2 + 1; idx_data[idx_write_ptr + 7] = idx1 + 1; idx_data[idx_write_ptr + 8] = idx1 + 0 - idx_data[idx_write_ptr + 9] = idx1 + 0; idx_data[idx_write_ptr + 10] = idx2 + 0; idx_data[idx_write_ptr + 11] = idx2 + 1 - idx_data[idx_write_ptr + 12] = idx2 + 2; idx_data[idx_write_ptr + 13] = idx1 + 2; idx_data[idx_write_ptr + 14] = idx1 + 3 - idx_data[idx_write_ptr + 15] = idx1 + 3; idx_data[idx_write_ptr + 16] = idx2 + 3; idx_data[idx_write_ptr + 17] = idx2 + 2 - idx_write_ptr = idx_write_ptr + 18 - - idx1 = idx2 - end - self._IdxWritePtr = idx_write_ptr - - -- Add vertices - local vtx_write_ptr = self._VtxWritePtr - for i = 0, points_count - 1 do - WVP(vtx_data, vtx_write_ptr + 0, temp_points[temp_points_start + i * 4 + 0], opaque_uv, col_trans) - WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 4 + 1], opaque_uv, col) - WVP(vtx_data, vtx_write_ptr + 2, temp_points[temp_points_start + i * 4 + 2], opaque_uv, col) - WVP(vtx_data, vtx_write_ptr + 3, temp_points[temp_points_start + i * 4 + 3], opaque_uv, col_trans) - vtx_write_ptr = vtx_write_ptr + 4 - end - self._VtxWritePtr = vtx_write_ptr - end - self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count + -- Calculate normals for each line segment + for i = 1, points_count - 1 do + local p0 = points[i] + local p1 = points[i + 1] + local dx = p1.x - p0.x + local dy = p1.y - p0.y + local d2 = dx * dx + dy * dy + local inv_len = 0.0 + if d2 > 0.0 then inv_len = 1.0 / math.sqrt(d2) end -- ImRsqrtPrecise() + normals_x[i] = -dy * inv_len + normals_y[i] = dx * inv_len + sqr_lengths[i] = d2 + end + if bit32.band(flags, ImDrawFlags.Closed) ~= 0 then + local p0 = points[points_count] + local p1 = points[1] + local dx = p1.x - p0.x + local dy = p1.y - p0.y + local d2 = dx * dx + dy * dy + local inv_len = 0.0 + if d2 > 0.0 then inv_len = 1.0 / math.sqrt(d2) end -- ImRsqrtPrecise() + normals_x[points_count] = -dy * inv_len + normals_y[points_count] = dx * inv_len + sqr_lengths[points_count] = d2 else - -- [PATH 4] Non texture-based, Non anti-aliased lines - local idx_count = count * 6 - local vtx_count = count * 4 - PrimReserve(self, idx_count, vtx_count) + normals_x[points_count] = normals_x[points_count - 1] + normals_y[points_count] = normals_y[points_count - 1] + sqr_lengths[points_count] = 0.0 + end - local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf - for i1 = 1, count do - local i2 = (i1 == points_count) and 1 or i1 + 1 - local p1 = points[i1] - local p2 = points[i2] + local u0, v0, u1, v1, fringe = _SelectLineTexture(self, screen_thickness, flags) - local dx = p2.x - p1.x - local dy = p2.y - p1.y - dx, dy = IM_NORMALIZE2F_OVER_ZERO(dx, dy) - dx = dx * (thickness * 0.5) - dy = dy * (thickness * 0.5) + local col_trans = bit32.band(col, bit32.bnot(IM_COL32_A_MASK)) - local vtx_write_ptr = self._VtxWritePtr - WV(vtx_data, vtx_write_ptr + 0, p1.x + dy, p1.y - dx, opaque_uv.x, opaque_uv.y, col) - WV(vtx_data, vtx_write_ptr + 1, p2.x + dy, p2.y - dx, opaque_uv.x, opaque_uv.y, col) - WV(vtx_data, vtx_write_ptr + 2, p2.x - dy, p2.y + dx, opaque_uv.x, opaque_uv.y, col) - WV(vtx_data, vtx_write_ptr + 3, p1.x - dy, p1.y + dx, opaque_uv.x, opaque_uv.y, col) - self._VtxWritePtr = vtx_write_ptr + 4 + local stroke_pos = _GetStrokePos(flags, ImDrawFlags.StrokeCenter) + if stroke_pos == ImDrawFlags.StrokeLegacy then + flags = bit32.band(bit32.bor(flags, ImDrawFlags.JoinMiter), bit32.bnot(ImDrawFlags.AALineEnds)) + end - local idx_write_ptr = self._IdxWritePtr - idx_data[idx_write_ptr + 0] = self._VtxCurrentIdx + 0; idx_data[idx_write_ptr + 1] = self._VtxCurrentIdx + 1; idx_data[idx_write_ptr + 2] = self._VtxCurrentIdx + 2 - idx_data[idx_write_ptr + 3] = self._VtxCurrentIdx + 0; idx_data[idx_write_ptr + 4] = self._VtxCurrentIdx + 2; idx_data[idx_write_ptr + 5] = self._VtxCurrentIdx + 3 - self._IdxWritePtr = idx_write_ptr + 6 - self._VtxCurrentIdx = self._VtxCurrentIdx + 4 + local closed = bit32.band(flags, ImDrawFlags.Closed) ~= 0 + local miters_only = bit32.band(flags, ImDrawFlags.JoinMiter) ~= 0 + local miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT + + local thickness0 = (thickness + fringe) * 0.5 -- Center (or legacy) + if stroke_pos == ImDrawFlags.StrokeOutside then + thickness0 = thickness + fringe * 0.5 + elseif stroke_pos == ImDrawFlags.StrokeCenterBiased then + thickness0 = _CalculateCenterBiasedOffset(self, thickness) + fringe * 0.5 + elseif stroke_pos == ImDrawFlags.StrokeInside then + thickness0 = fringe * 0.5 + end + local thickness1 = (thickness + fringe) - thickness0 + + local idx_count = 0 + local vtx_count = 0 + local max_verts_per_point = miters_only and 2 or 4 + local max_tris_per_point = miters_only and 2 or 5 + if closed then + vtx_count = --[[body]] points_count * max_verts_per_point + --[[closing]] 2 + idx_count = (--[[body]] points_count * max_tris_per_point + --[[closing]] 2) * 3 + else + vtx_count = --[[body]] (points_count - 2) * max_verts_per_point + --[[caps]] (4 * 2) + idx_count = (--[[body]] (points_count - 2) * max_tris_per_point + --[[last seg]] 2 + --[[caps]] (2 * 2)) * 3 + end + PrimReserve(self, idx_count, vtx_count) + + local ratio = thickness0 / (thickness0 + thickness1) -- The points that use uv2 are placed directly on the path (offset = 0), calculate the texture position from stroke offsets. + local u2, v2 = u0 + (u1 - u0) * ratio, v0 + + -- Clamp inner offset and adjust texture coordinates to match the change. + -- This is used by some of the convex shape rendering to avoid rendering artifacts when thickness is about to fill the whole shape. + if thickness1 > max_inner_offset then + thickness1 = max_inner_offset + local s = (thickness0 + thickness1) / (thickness + fringe) + local du = u1 - u0 + u1 = u0 + du * s + end + + local half_aa = self._FringeScale * 0.5 -- Used for end caps, does not use "fringe" since end cap AA is not using the texture. + local use_aa_ends = bit32.band(flags, bit32.bor(ImDrawFlags.AALines, ImDrawFlags.AALineEnds)) == bit32.bor(ImDrawFlags.AALines, ImDrawFlags.AALineEnds) + + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr -- 1-based write position in VtxBuffer + local iw = self._IdxWritePtr -- 1-based write position in IdxBuffer + local vtx_idx = self._VtxCurrentIdx + local first_vtx_offset = vw + local first_vtx_idx = vtx_idx -- [Luau] VtxBuffer position of index 'n' is first_vtx_offset + (n - first_vtx_idx) + local base_idx = vtx_idx + + local p1x, p1y + local n1x, n1y + local len_sqr1 + + local point_idx = 1 -- [Luau] 1-based, iterate point_idx..point_last inclusive + local point_last = points_count + if not closed then + -- Start cap + point_idx = point_idx + 1 + point_last = point_last - 1 + local p = points[1] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[1], normals_y[1] + len_sqr1 = sqr_lengths[1] + + local dir_x = n1y + local dir_y = -n1x + if bit32.band(flags, ImDrawFlags.CapSquare) ~= 0 then + p1x = p1x - dir_x * thickness * 0.5 + p1y = p1y - dir_y * thickness * 0.5 end + + if not use_aa_ends then + base_idx = vtx_idx + WV(vb, vw + 0, p1x - n1x * thickness0, p1y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 1, p1x + n1x * thickness1, p1y + n1y * thickness1, u1, v1, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + else + local pa_x = p1x - dir_x * half_aa + local pa_y = p1y - dir_y * half_aa + local pb_x = p1x + dir_x * half_aa + local pb_y = p1y + dir_y * half_aa + + base_idx = vtx_idx + WV(vb, vw + 0, pa_x - n1x * thickness0, pa_y - n1y * thickness0, u0, v0, col_trans) + WV(vb, vw + 1, pa_x + n1x * thickness1, pa_y + n1y * thickness1, u1, v1, col_trans) + + local next_base_idx = vtx_idx + 2 + WV(vb, vw + 2, pb_x - n1x * thickness0, pb_y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 3, pb_x + n1x * thickness1, pb_y + n1y * thickness1, u1, v1, col) + vw = vw + 4; vtx_idx = vtx_idx + 4 + + -- AA cap + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + base_idx = next_base_idx + end + else + -- Wrap around segment + local p = points[points_count] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[points_count], normals_y[points_count] + len_sqr1 = sqr_lengths[points_count] + + -- This will be filled later, allocate space. + base_idx = vtx_idx + WV(vb, vw + 0, 0, 0, u0, v0, col) + WV(vb, vw + 1, 0, 0, u1, v1, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + end + + if miters_only then + for pi = point_idx, point_last do + local n0x, n0y = n1x, n1y + local p = points[pi] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[pi], normals_y[pi] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor + local miter_offset_y = (n0y + n1y) * miter_scale_factor + + -- Miter + local next_base_idx = vtx_idx + WV(vb, vw + 0, p1x - miter_offset_x * thickness0, p1y - miter_offset_y * thickness0, u0, v0, col) -- 2 + WV(vb, vw + 1, p1x + miter_offset_x * thickness1, p1y + miter_offset_y * thickness1, u1, v1, col) -- 3 + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + + base_idx = next_base_idx + end + else + for pi = point_idx, point_last do + local n0x, n0y = n1x, n1y + local len_sqr0 = len_sqr1 + local p = points[pi] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[pi], normals_y[pi] + len_sqr1 = sqr_lengths[pi] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + local sin_theta = n0y * n1x - n0x * n1y + + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor + local miter_offset_y = (n0y + n1y) * miter_scale_factor + + local miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y + + if miter_distance_sqr > miter_distance_limit_sqr then + -- Clipped bevel + + -- Limit the inner miter offset to avoid overshooting. + local ref_thickness = (sin_theta < 0.0) and thickness1 or thickness0 + local ref_thickness_sqr = ref_thickness * ref_thickness + local limit_sqr = ImMax(ImMax(len_sqr0, len_sqr1), ref_thickness_sqr) + local ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr + if ref_miter_dist_sqr > limit_sqr then + local scale = ImSqrt(limit_sqr / ref_miter_dist_sqr) + miter_offset_x = miter_offset_x * scale + miter_offset_y = miter_offset_y * scale + end + + local bn_x, bn_y = IM_NORMALIZE2F_OVER_ZERO(n0x + n1x, n0y + n1y) + + local side_offset = ((n0x * bn_x + n0y * bn_y) - 1.0) / (n0y * bn_x - n0x * bn_y) + local sd_x = bn_y * side_offset + local sd_y = -bn_x * side_offset + + -- Bevel + local next_base_idx = vtx_idx + 2 + if sin_theta < 0.0 then + WV(vb, vw + 0, p1x - (bn_x + sd_x) * thickness0, p1y - (bn_y + sd_y) * thickness0, u0, v0, col) -- 2 + WV(vb, vw + 1, p1x, p1y, u2, v2, col) -- 3 + WV(vb, vw + 2, p1x - (bn_x - sd_x) * thickness0, p1y - (bn_y - sd_y) * thickness0, u0, v0, col) -- 4 + WV(vb, vw + 3, p1x + miter_offset_x * thickness1, p1y + miter_offset_y * thickness1, u1, v1, col) -- 5 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 5 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 5; ib[iw + 5] = base_idx + 1 + -- Bevel + ib[iw + 6] = base_idx + 3; ib[iw + 7] = base_idx + 5; ib[iw + 8] = base_idx + 2 + ib[iw + 9] = base_idx + 3; ib[iw + 10] = base_idx + 2; ib[iw + 11] = base_idx + 4 + ib[iw + 12] = base_idx + 3; ib[iw + 13] = base_idx + 4; ib[iw + 14] = base_idx + 5 + else + WV(vb, vw + 0, p1x + (bn_x + sd_x) * thickness1, p1y + (bn_y + sd_y) * thickness1, u1, v1, col) -- 2 + WV(vb, vw + 1, p1x, p1y, u2, v2, col) -- 3 + WV(vb, vw + 2, p1x - miter_offset_x * thickness0, p1y - miter_offset_y * thickness0, u0, v0, col) -- 4 + WV(vb, vw + 3, p1x + (bn_x - sd_x) * thickness1, p1y + (bn_y - sd_y) * thickness1, u1, v1, col) -- 5 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 4; ib[iw + 2] = base_idx + 2 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 2; ib[iw + 5] = base_idx + 1 + -- Bevel + ib[iw + 6] = base_idx + 3; ib[iw + 7] = base_idx + 2; ib[iw + 8] = base_idx + 4 + ib[iw + 9] = base_idx + 3; ib[iw + 10] = base_idx + 4; ib[iw + 11] = base_idx + 5 + ib[iw + 12] = base_idx + 3; ib[iw + 13] = base_idx + 5; ib[iw + 14] = base_idx + 2 + end + vw = vw + 4; vtx_idx = vtx_idx + 4 + iw = iw + 15 + base_idx = next_base_idx + else + -- Miter + local next_base_idx = vtx_idx + WV(vb, vw + 0, p1x - miter_offset_x * thickness0, p1y - miter_offset_y * thickness0, u0, v0, col) -- 2 + WV(vb, vw + 1, p1x + miter_offset_x * thickness1, p1y + miter_offset_y * thickness1, u1, v1, col) -- 3 + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Connect prev to next + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + base_idx = next_base_idx + end + end + end + + -- End cap + if not closed then + local p = points[points_count] + p1x, p1y = p.x, p.y + n1x, n1y = normals_x[points_count], normals_y[points_count] + + -- End cap + local dir_x = n1y + local dir_y = -n1x + if bit32.band(flags, ImDrawFlags.CapSquare) ~= 0 then + p1x = p1x + dir_x * thickness * 0.5 + p1y = p1y + dir_y * thickness * 0.5 + end + + if not use_aa_ends then + WV(vb, vw + 0, p1x - n1x * thickness0, p1y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 1, p1x + n1x * thickness1, p1y + n1y * thickness1, u1, v1, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Connect + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + iw = iw + 6 + else + local pa_x = p1x - dir_x * half_aa + local pa_y = p1y - dir_y * half_aa + local pb_x = p1x + dir_x * half_aa + local pb_y = p1y + dir_y * half_aa + + local next_base_idx = vtx_idx + WV(vb, vw + 0, pa_x - n1x * thickness0, pa_y - n1y * thickness0, u0, v0, col) + WV(vb, vw + 1, pa_x + n1x * thickness1, pa_y + n1y * thickness1, u1, v1, col) + + WV(vb, vw + 2, pb_x - n1x * thickness0, pb_y - n1y * thickness0, u0, v0, col_trans) + WV(vb, vw + 3, pb_x + n1x * thickness1, pb_y + n1y * thickness1, u1, v1, col_trans) + vw = vw + 4; vtx_idx = vtx_idx + 4 + + -- Connect + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + base_idx = next_base_idx + + -- AA cap + ib[iw + 6] = base_idx + 0; ib[iw + 7] = base_idx + 2; ib[iw + 8] = base_idx + 3 + ib[iw + 9] = base_idx + 0; ib[iw + 10] = base_idx + 3; ib[iw + 11] = base_idx + 1 + iw = iw + 12 + end + else + -- Connect the path. + -- [Luau] VtxBuffer.Data[first_vtx_offset + 0..1] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 0..1] (2 vertices of 36 bytes) + buffer.copy(vb, (first_vtx_offset - 1) * 36, vb, (first_vtx_offset + (base_idx - first_vtx_idx) - 1) * 36, 2 * 36) + end + + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = vtx_idx + + -- Restore unused memory + local remaining_idx_count = self.IdxBuffer.Size - (iw - 1) + local remaining_vtx_count = self.VtxBuffer.Size - (vw - 1) + IM_ASSERT_PARANOID(remaining_idx_count >= 0 and remaining_vtx_count >= 0) + if remaining_idx_count > 0 or remaining_vtx_count > 0 then -- [Luau] skip the call when nothing is left + self:PrimUnreserve(remaining_idx_count, remaining_vtx_count) + end +end + +-- - We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds. +-- - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. +--- @param points ImVec2[] # 1-based +--- @param points_count int +--- @param col ImU32 +--- @param flags? ImDrawFlags +@native +function MT.ImDrawList:AddConvexPolyFilled(points, points_count, col, flags) + if points_count < 3 or bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + if flags == nil then flags = 0 end + + local uv = self._Data.TexUvWhitePixel + local uv_x, uv_y = uv.x, uv.y + flags = bit32.bor(flags, self.Flags) + if bit32.band(flags, ImDrawFlags.AAFill) ~= 0 then + local half_aa = self._FringeScale * 0.5 + local col_trans = bit32.band(col, bit32.bnot(IM_COL32_A_MASK)) + local miters_only = bit32.band(flags, ImDrawFlags.JoinMiter) ~= 0 + local miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT + + local idx_count = ((points_count - 2) + points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) * 3 + local vtx_count = points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local start_vtx_ptr = self._VtxWritePtr + local start_idx_ptr = self._IdxWritePtr + local iw = start_idx_ptr + + -- Compute normals and segment lengths + local temp_normals_x, temp_normals_y, temp_sqr_lengths = poly_normals_x, poly_normals_y, poly_sqr_lengths + local i0 = points_count + for i1 = 1, points_count do + local p0 = points[i0] + local p1 = points[i1] + local dx = p1.x - p0.x + local dy = p1.y - p0.y + local d2 = dx * dx + dy * dy + if d2 > 0.0 then + local inv_len = 1.0 / math.sqrt(d2) -- ImRsqrtPrecise() + dx = dx * inv_len + dy = dy * inv_len + end + temp_normals_x[i0] = dy + temp_normals_y[i0] = -dx + temp_sqr_lengths[i0] = d2 + i0 = i1 + end + + local vtx_inner_idx = self._VtxCurrentIdx + local inner_vtx_ptr = start_vtx_ptr + local vw = start_vtx_ptr + points_count + local vtx_idx = vtx_inner_idx + points_count + + local prev_outer_idx = 0 -- This should be the index of the last vertex, but we don't know it yet. Will need to patch once we're done. + if miters_only then + i0 = points_count + for i1 = 1, points_count do + local p1 = points[i1] + local p1x, p1y = p1.x, p1.y + local n0x, n0y = temp_normals_x[i0], temp_normals_y[i0] + local n1x, n1y = temp_normals_x[i1], temp_normals_y[i1] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor * half_aa + local miter_offset_y = (n0y + n1y) * miter_scale_factor * half_aa + + -- Inner + WV(vb, inner_vtx_ptr, p1x - miter_offset_x, p1y - miter_offset_y, uv_x, uv_y, col) + inner_vtx_ptr = inner_vtx_ptr + 1 + + local prev_inner_idx = vtx_inner_idx + (i0 - 1) + local inner_idx = vtx_inner_idx + (i1 - 1) + local outer_idx = vtx_idx + + -- Outer + WV(vb, vw, p1x + miter_offset_x, p1y + miter_offset_y, uv_x, uv_y, col_trans) + vw = vw + 1; vtx_idx = vtx_idx + 1 + -- Connect with previous + ib[iw + 0] = prev_outer_idx; ib[iw + 1] = outer_idx; ib[iw + 2] = inner_idx + ib[iw + 3] = prev_outer_idx; ib[iw + 4] = inner_idx; ib[iw + 5] = prev_inner_idx + iw = iw + 6 + + prev_outer_idx = outer_idx + i0 = i1 + end + else + local bevel_count = 0 + i0 = points_count + for i1 = 1, points_count do + local p1 = points[i1] + local p1x, p1y = p1.x, p1.y + local n0x, n0y = temp_normals_x[i0], temp_normals_y[i0] + local n1x, n1y = temp_normals_x[i1], temp_normals_y[i1] + + -- theta is the angle between two segments + local cos_theta = n0x * n1x + n0y * n1y + -- miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + local cos_theta_clamped = (IM_POLYLINE_MITER_ANGLE_LIMIT >= cos_theta) and IM_POLYLINE_MITER_ANGLE_LIMIT or cos_theta -- Avoid div by 0. + local miter_scale_factor = 1.0 / (1.0 + cos_theta_clamped) + if miter_scale_factor > 1000.0 then miter_scale_factor = 1000.0 end + local miter_offset_x = (n0x + n1x) * miter_scale_factor + local miter_offset_y = (n0y + n1y) * miter_scale_factor + + local miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y + local bevel = miter_distance_sqr > miter_distance_limit_sqr + if bevel then + -- Limit inner bevel so that it is does not shoot out outside the polygon. + -- (using ImMax is intentional: using min limited too much in Mikko's tests when you have short edge next to long one. + -- The idea of that check is to keep that worst case overshoot no bigger than the polygon) + local ref_thickness_sqr = half_aa * half_aa + local limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]) + local ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr + if ref_miter_dist_sqr > limit_sqr then + local scale = ImSqrt(limit_sqr / ref_miter_dist_sqr) + miter_offset_x = miter_offset_x * scale + miter_offset_y = miter_offset_y * scale + end + end + + miter_offset_x = miter_offset_x * half_aa + miter_offset_y = miter_offset_y * half_aa + + -- Inner + WV(vb, inner_vtx_ptr, p1x - miter_offset_x, p1y - miter_offset_y, uv_x, uv_y, col) + inner_vtx_ptr = inner_vtx_ptr + 1 + + local prev_inner_idx = vtx_inner_idx + (i0 - 1) + local inner_idx = vtx_inner_idx + (i1 - 1) + local outer_idx = vtx_idx + + -- Outer + if bevel and bevel_count < IM_POLYLINE_CONVEX_POLY_MAX_BEVELS then + -- Because the polygon is convex, we know the maximum number of bevel corners we can hit (which is very small number). + -- We keep track of them just in case the calculations disagree. + bevel_count = bevel_count + 1 + + WV(vb, vw + 0, p1x + n0x * half_aa, p1y + n0y * half_aa, uv_x, uv_y, col_trans) + WV(vb, vw + 1, p1x + n1x * half_aa, p1y + n1y * half_aa, uv_x, uv_y, col_trans) + vw = vw + 2; vtx_idx = vtx_idx + 2 + -- Connect with previous + ib[iw + 0] = prev_outer_idx; ib[iw + 1] = outer_idx; ib[iw + 2] = inner_idx + ib[iw + 3] = prev_outer_idx; ib[iw + 4] = inner_idx; ib[iw + 5] = prev_inner_idx + -- Fill bevel + ib[iw + 6] = outer_idx; ib[iw + 7] = outer_idx + 1; ib[iw + 8] = inner_idx + iw = iw + 9 + outer_idx = outer_idx + 1 + else + WV(vb, vw, p1x + miter_offset_x, p1y + miter_offset_y, uv_x, uv_y, col_trans) + vw = vw + 1; vtx_idx = vtx_idx + 1 + -- Connect with previous + ib[iw + 0] = prev_outer_idx; ib[iw + 1] = outer_idx; ib[iw + 2] = inner_idx + ib[iw + 3] = prev_outer_idx; ib[iw + 4] = inner_idx; ib[iw + 5] = prev_inner_idx + iw = iw + 6 + end + + prev_outer_idx = outer_idx + i0 = i1 + end + end + + -- Patch first segment to wrap around + ib[start_idx_ptr + 0] = prev_outer_idx + ib[start_idx_ptr + 3] = prev_outer_idx + + -- Add indices for fill + for i = 2, points_count - 1 do + ib[iw + 0] = vtx_inner_idx; ib[iw + 1] = vtx_inner_idx + i - 1; ib[iw + 2] = vtx_inner_idx + i + iw = iw + 3 + end + + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = vtx_idx + + local idx_used = iw - start_idx_ptr + local vtx_used = vw - start_vtx_ptr + IM_ASSERT(idx_used <= idx_count and vtx_used <= vtx_count) + if idx_used < idx_count or vtx_used < vtx_count then + self:PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used) + end + else + -- Non Anti-aliased Fill + local idx_count = (points_count - 2) * 3 + local vtx_count = points_count + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + local base_idx = self._VtxCurrentIdx + for i = 1, vtx_count do + local p = points[i] + WV(vb, vw, p.x, p.y, uv_x, uv_y, col) + vw = vw + 1 + end + for i = 2, points_count - 1 do + ib[iw + 0] = base_idx; ib[iw + 1] = base_idx + i - 1; ib[iw + 2] = base_idx + i + iw = iw + 3 + end + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = base_idx + vtx_count end end @@ -4621,15 +5030,16 @@ local function PathRectXY(self, ax, ay, bx, by, rounding, flags) if not rounding then rounding = 0.0 end if not flags then flags = 0 end - if rounding >= 0.5 then - IM_ASSERT(bit32.band(flags, 0x0F) == 0, "Misuse of legacy hardcoded ImDrawCornerFlags values!") + local has_rounding = rounding >= 0.25 * self._FringeScale -- IM_HAS_ROUNDING(rounding) + if has_rounding then if bit32.band(flags, ImDrawFlags.RoundMask_) == 0 then flags = bit32.bor(flags, ImDrawFlags.RoundAll) end + rounding = ImMin(rounding, ImAbs(bx - ax) * (((bit32.band(flags, ImDrawFlags.RoundTop) == ImDrawFlags.RoundTop) or (bit32.band(flags, ImDrawFlags.RoundBottom) == ImDrawFlags.RoundBottom)) and 0.5 or 1.0) - 1.0) rounding = ImMin(rounding, ImAbs(by - ay) * (((bit32.band(flags, ImDrawFlags.RoundLeft) == ImDrawFlags.RoundLeft) or (bit32.band(flags, ImDrawFlags.RoundRight) == ImDrawFlags.RoundRight)) and 0.5 or 1.0) - 1.0) end - if rounding < 0.5 or (bit32.band(flags, ImDrawFlags.RoundMask_) == ImDrawFlags.RoundNone) then + if not has_rounding or (bit32.band(flags, ImDrawFlags.RoundMask_) == ImDrawFlags.RoundNone) then PathLineToXY(self, ax, ay) PathLineToXY(self, bx, ay) PathLineToXY(self, bx, by) @@ -4639,7 +5049,7 @@ local function PathRectXY(self, ax, ay, bx, by, rounding, flags) local rounding_tr = (bit32.band(flags, ImDrawFlags.RoundTopRight) ~= 0) and rounding or 0.0 local rounding_br = (bit32.band(flags, ImDrawFlags.RoundBottomRight) ~= 0) and rounding or 0.0 local rounding_bl = (bit32.band(flags, ImDrawFlags.RoundBottomLeft) ~= 0) and rounding or 0.0 - -- [Luau] inline PathArcToFast() (radius < 0.5 handled by _PathArcToFastExXY()) + -- [Luau] inline PathArcToFast() (small radius handled by _PathArcToFastExXY()) _PathArcToFastExXY(self, ax + rounding_tl, ay + rounding_tl, rounding_tl, 6 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 9 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0) _PathArcToFastExXY(self, bx - rounding_tr, ay + rounding_tr, rounding_tr, 9 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0) _PathArcToFastExXY(self, bx - rounding_br, by - rounding_br, rounding_br, 0 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 3 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0) @@ -4651,19 +5061,654 @@ function MT.ImDrawList:PathRect(a, b, rounding, flags) PathRectXY(self, a.x, a.y, b.x, b.y, rounding, flags) end +-- [Luau] scalar _AddLine (p1 = p1x,p1y; p2 = p2x,p2y); _AddLine() below wraps it +@native +local function _AddLineXY(self, p1x, p1y, p2x, p2y, col, thickness, flags) + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + local screen_thickness = thickness * self._InvFringeScale + if screen_thickness < 1.0 then + if screen_thickness < 1.0 / 255.0 then + return + end + col = ImAlphaMultiply(col, screen_thickness) + screen_thickness = 1.0 + thickness = self._FringeScale + end + + flags = bit32.bor(flags, self.Flags) + local u0, v0, u1, v1, fringe = _SelectLineTexture(self, screen_thickness, flags) + + local dir_x = p2x - p1x + local dir_y = p2y - p1y + local d2 = dir_x * dir_x + dir_y * dir_y + local inv_len = 0.0 + if d2 > 0.0 then inv_len = 1.0 / math.sqrt(d2) end -- ImRsqrtPrecise() + dir_x = dir_x * inv_len + dir_y = dir_y * inv_len + local norm_x = -dir_y + local norm_y = dir_x + + local col_trans = bit32.band(col, bit32.bnot(IM_COL32_A_MASK)) + + local stroke_pos = _GetStrokePos(flags, ImDrawFlags.StrokeCenter) + if stroke_pos == ImDrawFlags.StrokeLegacy then + flags = bit32.band(bit32.bor(flags, ImDrawFlags.JoinMiter), bit32.bnot(ImDrawFlags.AALineEnds)) + end + + local thickness0 = (thickness + fringe) * 0.5 -- Center (or legacy) + if stroke_pos == ImDrawFlags.StrokeOutside then + thickness0 = thickness + fringe * 0.5 + elseif stroke_pos == ImDrawFlags.StrokeCenterBiased then + thickness0 = _CalculateCenterBiasedOffset(self, thickness) + fringe * 0.5 + elseif stroke_pos == ImDrawFlags.StrokeInside then + thickness0 = fringe * 0.5 + end + local thickness1 = (thickness + fringe) - thickness0 + + -- [Luau] uv2 (ratio) is unused by _AddLine(): not computed + + local half_aa = self._FringeScale * 0.5 -- Used for end caps, does not use "fringe" since end cap AA is not using the texture. + local use_aa_ends = bit32.band(flags, bit32.bor(ImDrawFlags.AALines, ImDrawFlags.AALineEnds)) == bit32.bor(ImDrawFlags.AALines, ImDrawFlags.AALineEnds) + + if not use_aa_ends then + PrimReserve(self, 2 * 3, 4) + else + PrimReserve(self, 6 * 3, 8) + end + + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + local base_idx = self._VtxCurrentIdx + + if bit32.band(flags, ImDrawFlags.CapSquare) ~= 0 then + local half_thickness = thickness * 0.5 + p1x = p1x - dir_x * half_thickness + p1y = p1y - dir_y * half_thickness + p2x = p2x + dir_x * half_thickness + p2y = p2y + dir_y * half_thickness + end + + -- Start cap + if not use_aa_ends then + WV(vb, vw + 0, p1x - norm_x * thickness0, p1y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 1, p1x + norm_x * thickness1, p1y + norm_y * thickness1, u1, v1, col) + WV(vb, vw + 2, p2x - norm_x * thickness0, p2y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 3, p2x + norm_x * thickness1, p2y + norm_y * thickness1, u1, v1, col) + + -- Connect + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + self._VtxWritePtr = vw + 4 + self._IdxWritePtr = iw + 6 + self._VtxCurrentIdx = base_idx + 4 + else + -- Start + local pa_x = p1x - dir_x * half_aa + local pa_y = p1y - dir_y * half_aa + local pb_x = p1x + dir_x * half_aa + local pb_y = p1y + dir_y * half_aa + + WV(vb, vw + 0, pa_x - norm_x * thickness0, pa_y - norm_y * thickness0, u0, v0, col_trans) + WV(vb, vw + 1, pa_x + norm_x * thickness1, pa_y + norm_y * thickness1, u1, v1, col_trans) + + WV(vb, vw + 2, pb_x - norm_x * thickness0, pb_y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 3, pb_x + norm_x * thickness1, pb_y + norm_y * thickness1, u1, v1, col) + + -- AA cap + ib[iw + 0] = base_idx + 0; ib[iw + 1] = base_idx + 2; ib[iw + 2] = base_idx + 3 + ib[iw + 3] = base_idx + 0; ib[iw + 4] = base_idx + 3; ib[iw + 5] = base_idx + 1 + base_idx = base_idx + 2 + + -- End + pa_x = p2x - dir_x * half_aa + pa_y = p2y - dir_y * half_aa + pb_x = p2x + dir_x * half_aa + pb_y = p2y + dir_y * half_aa + + WV(vb, vw + 4, pa_x - norm_x * thickness0, pa_y - norm_y * thickness0, u0, v0, col) + WV(vb, vw + 5, pa_x + norm_x * thickness1, pa_y + norm_y * thickness1, u1, v1, col) + WV(vb, vw + 6, pb_x - norm_x * thickness0, pb_y - norm_y * thickness0, u0, v0, col_trans) + WV(vb, vw + 7, pb_x + norm_x * thickness1, pb_y + norm_y * thickness1, u1, v1, col_trans) + + -- Connect + ib[iw + 6] = base_idx + 0; ib[iw + 7] = base_idx + 2; ib[iw + 8] = base_idx + 3 + ib[iw + 9] = base_idx + 0; ib[iw + 10] = base_idx + 3; ib[iw + 11] = base_idx + 1 + base_idx = base_idx + 2 + + -- AA cap + ib[iw + 12] = base_idx + 0; ib[iw + 13] = base_idx + 2; ib[iw + 14] = base_idx + 3 + ib[iw + 15] = base_idx + 0; ib[iw + 16] = base_idx + 3; ib[iw + 17] = base_idx + 1 + self._VtxWritePtr = vw + 8 + self._IdxWritePtr = iw + 18 + self._VtxCurrentIdx = base_idx + 4 + end +end + +function MT.ImDrawList:_AddLine(p1, p2, col, thickness, flags) + _AddLineXY(self, p1.x, p1.y, p2.x, p2.y, col, thickness, flags) +end + +--- @param p1 ImVec2 +--- @param p2 ImVec2 +--- @param col ImU32 +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddLine(p1, p2, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, ImDrawFlags.StrokeCenter) + if stroke_pos == ImDrawFlags.StrokeLegacy then + _AddLineXY(self, p1.x + 0.5, p1.y + 0.5, p2.x + 0.5, p2.y + 0.5, col, thickness, flags) + return + end + + _AddLineXY(self, p1.x, p1.y, p2.x, p2.y, col, thickness, flags) +end + +-- AddLineH(10, 20, 5, col, 1.0f) == AddLine( {10,5.5}, {20,5.5}, col, 1.0f) +-- == AddLine( {10,5}, {20,5}, col, 1.0f, ImDrawFlags_StrokeLegacy); // or before 1.93.0 +-- Thickness extend inside/bottom by default. Faster than equivalent AddLine() call. +--- @param min_x float +--- @param max_x float +--- @param y float +--- @param col ImU32 +--- @param thickness? float +--- @param flags? ImDrawFlags +@native +function MT.ImDrawList:AddLineH(min_x, max_x, y, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, ImDrawFlags.StrokeInside) + if stroke_pos == ImDrawFlags.StrokeLegacy then + _AddLineXY(self, min_x + 0.5, y + 0.5, max_x + 0.5, y + 0.5, col, thickness, flags) + return + end + + local top_y = y -- Inside + if stroke_pos == ImDrawFlags.StrokeCenter then + top_y = top_y - thickness * 0.5 + elseif stroke_pos == ImDrawFlags.StrokeCenterBiased then + top_y = top_y - _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == ImDrawFlags.StrokeOutside then + top_y = top_y - thickness + end + + local is_truncated = min_x % 1 == 0 and max_x % 1 == 0 and top_y % 1 == 0 and thickness % 1 == 0 -- IM_IS_TRUNCATED4() + if self._FringeScaleIsInteger and is_truncated and bit32.band(flags, ImDrawFlags.CapSquare) == 0 then + if thickness < self._FringeScale then + if thickness < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, thickness * self._InvFringeScale) + thickness = self._FringeScale + end + + -- For pixel aligned case, use simple rectangle. + PrimReserve(self, 6, 4) + PrimRectXY(self, min_x, top_y, max_x, top_y + thickness, col) + else + -- Replace stroke pos with the already calculated one, as AddPolyline() default is different. + flags = bit32.bor(bit32.band(flags, bit32.bnot(ImDrawFlags.StrokeMask_)), stroke_pos) + + -- For generic case use line, since it needs less triangles than AA rectangle. + _AddLineXY(self, min_x, y, max_x, y, col, thickness, flags) + end +end + +-- AddLineV(5, 10, 20, col, 1.0f) == AddLine( {5.5,10}, {5.5,20}, col, 1.0f) +-- == AddLine( {5,10}, {5,20}, col, 1.0f, ImDrawFlags_StrokeLegacy); // or before 1.93.0 +-- Thickness extend inside/right by default. Faster than equivalent AddLine() call. +--- @param x float +--- @param min_y float +--- @param max_y float +--- @param col ImU32 +--- @param thickness? float +--- @param flags? ImDrawFlags +@native +function MT.ImDrawList:AddLineV(x, min_y, max_y, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, ImDrawFlags.StrokeInside) + if stroke_pos == ImDrawFlags.StrokeLegacy then + _AddLineXY(self, x + 0.5, min_y + 0.5, x + 0.5, max_y + 0.5, col, thickness, flags) + return + end + + local left_x = x -- Inside + if stroke_pos == ImDrawFlags.StrokeCenter then + left_x = left_x - thickness * 0.5 + elseif stroke_pos == ImDrawFlags.StrokeCenterBiased then + left_x = left_x - _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == ImDrawFlags.StrokeOutside then + left_x = left_x - thickness + end + + local is_truncated = left_x % 1 == 0 and min_y % 1 == 0 and max_y % 1 == 0 and thickness % 1 == 0 -- IM_IS_TRUNCATED4() + if self._FringeScaleIsInteger and is_truncated and bit32.band(flags, ImDrawFlags.CapSquare) == 0 then + if thickness < self._FringeScale then + if thickness < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, thickness * self._InvFringeScale) + thickness = self._FringeScale + end + + -- For pixel aligned case, use simple rectangle. + PrimReserve(self, 6, 4) + PrimRectXY(self, left_x, min_y, left_x + thickness, max_y, col) + else + -- Replace stroke pos with the already calculated one, as AddPolyline() default is different. + flags = bit32.bor(bit32.band(flags, bit32.bnot(ImDrawFlags.StrokeMask_)), stroke_pos) + + -- For generic case use line, since it needs less triangles than AA rectangle. + -- Drawing for bottom to top, so that the inside stroke pos expands to right. + _AddLineXY(self, x, max_y, x, min_y, col, thickness, flags) + end +end + +-- [Port] TODO baked round corners: _AddRectBaked() (UseTexForRoundCorners) not ported yet, AddRect() falls back to the geometry path. + +local corner_offset_x = { 1, -1, -1, 1 } -- static const ImVec2 corner_offset[4] = { {1,1}, {-1,1}, {-1,-1}, {1,-1} }; +local corner_offset_y = { 1, 1, -1, -1 } +local corner_flags = { ImDrawFlags.RoundTopLeft, ImDrawFlags.RoundTopRight, ImDrawFlags.RoundBottomRight, ImDrawFlags.RoundBottomLeft } +local corner_pos_x, corner_pos_y = { 0, 0, 0, 0 }, { 0, 0, 0, 0 } -- [Luau] scratch + +-- Draws rounded rectangle where thickness > rounding. If rendered using the regular polyline, the stroke will fold and leave artifacts on the corners if rendered with transparency. +-- The stroke is positioned inside the rectangle. +--- @param p_min ImVec2 +--- @param p_max ImVec2 +--- @param col ImU32 +--- @param rounding float +--- @param thickness float +--- @param flags ImDrawFlags +function MT.ImDrawList:_AddRectTinyRounding(p_min, p_max, col, rounding, thickness, flags) + local screen_thickness = thickness * self._InvFringeScale + if screen_thickness < 1.0 then + if screen_thickness < 1.0 / 255.0 then + return + end + col = ImAlphaMultiply(col, screen_thickness) + screen_thickness = 1.0 + thickness = self._FringeScale + end + + flags = bit32.bor(flags, self.Flags) + local outer_u, outer_v, inner_u, inner_v, fringe = _SelectLineTexture(self, screen_thickness, flags) + + local auto_seg_count = self:_CalcCircleAutoSegmentCount(rounding) + local arc_step, arc_step_count = CalcCornerArcStepAndCount(auto_seg_count) + local arc_point_count = arc_step_count + 1 + + local vtx_count = (2 + arc_point_count) * 4 + 3 + local idx_count = (4 + arc_point_count - 1) * 4 * 3 + + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local start_vtx_ptr = self._VtxWritePtr + local start_idx_ptr = self._IdxWritePtr + local vw, iw = start_vtx_ptr, start_idx_ptr + local vtx_idx = self._VtxCurrentIdx + local first_vtx_idx = vtx_idx -- [Luau] VtxBuffer position of index 'n' is start_vtx_ptr + (n - first_vtx_idx) + + local half_fringe = fringe * 0.5 + corner_pos_x[1] = p_min.x - half_fringe; corner_pos_y[1] = p_min.y - half_fringe + corner_pos_x[2] = p_max.x + half_fringe; corner_pos_y[2] = p_min.y - half_fringe + corner_pos_x[3] = p_max.x + half_fringe; corner_pos_y[3] = p_max.y + half_fringe + corner_pos_x[4] = p_min.x - half_fringe; corner_pos_y[4] = p_max.y + half_fringe + thickness = thickness + fringe + rounding = rounding + half_fringe + + local stem_offset = ImMin(rounding, thickness) -- The function might get called with rounding slightly bigger than thickness, which is ok, but we should keep the stem inside the shape. + local ratio = stem_offset / thickness + local stem_u, stem_v = outer_u + (inner_u - outer_u) * ratio, outer_v + + -- Previous vertices, copied from the last vertices. + local base_idx = vtx_idx + WV(vb, vw + 0, 0, 0, inner_u, inner_v, col) + WV(vb, vw + 1, 0, 0, stem_u, stem_v, col) + WV(vb, vw + 2, 0, 0, outer_u, outer_v, col) + vw = vw + 3; vtx_idx = vtx_idx + 3 + + local arc_fast_vtx = self._Data.ArcFastVtx -- 1-based + for corner = 1, 4 do + local outer_x = corner_pos_x[corner] + local outer_y = corner_pos_y[corner] + local cox, coy = corner_offset_x[corner], corner_offset_y[corner] + local stem_x = outer_x + cox * stem_offset + local stem_y = outer_y + coy * stem_offset + local inner_x = outer_x + cox * thickness + local inner_y = outer_y + coy * thickness + local is_rounded = bit32.band(flags, corner_flags[corner]) ~= 0 + + local arc_idx = vtx_idx + if is_rounded then + local center_x = outer_x + cox * rounding + local center_y = outer_y + coy * rounding + local a = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) * (corner - 1) + for _ = 0, arc_point_count - 1 do + local s = arc_fast_vtx[a + 1] + WV(vb, vw, center_x - s.x * rounding, center_y - s.y * rounding, outer_u, outer_v, col) + vw = vw + 1; vtx_idx = vtx_idx + 1 + a = (a + arc_step) % IM_DRAWLIST_ARCFAST_TABLE_SIZE + end + else + WV(vb, vw, outer_x, outer_y, outer_u, outer_v, col) + vw = vw + 1; vtx_idx = vtx_idx + 1 + end + + local stem_idx = vtx_idx + WV(vb, vw, stem_x, stem_y, stem_u, stem_v, col) + local inner_idx = vtx_idx + 1 + WV(vb, vw + 1, inner_x, inner_y, inner_u, inner_v, col) + vw = vw + 2; vtx_idx = vtx_idx + 2 + + -- Arc + if is_rounded then + for i = 0, arc_point_count - 2 do + ib[iw + 0] = stem_idx; ib[iw + 1] = arc_idx + i; ib[iw + 2] = arc_idx + i + 1 + iw = iw + 3 + end + end + + -- Connect with previous + ib[iw + 0] = base_idx + 0; ib[iw + 1] = arc_idx; ib[iw + 2] = stem_idx + ib[iw + 3] = base_idx + 0; ib[iw + 4] = stem_idx; ib[iw + 5] = base_idx + 1 + ib[iw + 6] = base_idx + 1; ib[iw + 7] = stem_idx; ib[iw + 8] = inner_idx + ib[iw + 9] = base_idx + 1; ib[iw + 10] = inner_idx; ib[iw + 11] = base_idx + 2 + iw = iw + 12 + + base_idx = stem_idx - 1 + end + + -- Close + -- [Luau] start_vtx_ptr[0..2] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 0..2] (3 vertices of 36 bytes) + buffer.copy(vb, (start_vtx_ptr - 1) * 36, vb, (start_vtx_ptr + (base_idx - first_vtx_idx) - 1) * 36, 3 * 36) + + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = vtx_idx + + local idx_used = iw - start_idx_ptr + local vtx_used = vw - start_vtx_ptr + IM_ASSERT_PARANOID(idx_used <= idx_count and vtx_used <= vtx_count) + if idx_used < idx_count or vtx_used < vtx_count then + self:PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used) + end +end + +local prim_points = { ImVec2(), ImVec2(), ImVec2(), ImVec2() } -- [Luau] scratch for C++ 'const ImVec2 points[N]' locals (callees only read them) + +-- p_min = upper-left, p_max = lower-right +--- @param p_min ImVec2 +--- @param p_max ImVec2 +--- @param col ImU32 +--- @param rounding? float +--- @param thickness? float +--- @param flags? ImDrawFlags +@native +function MT.ImDrawList:AddRect(p_min, p_max, col, rounding, thickness, flags) + if rounding == nil then rounding = 0.0 end + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + -- If this assert triggers on legacy code: + -- - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. + -- - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. + -- - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundXXX values. + -- If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags! + -- - Hard coded support for ~0 == ImDrawFlags_RoundAll. + -- - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. + -- - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundNone or use 'float rounding = 0.0f'. + -- See "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. + IM_ASSERT_USER_ERROR_RET(bit32.band(flags, ImDrawFlags.InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?") -- Or misuse of legacy hard-coded ImDrawCornerFlags values + + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, ImDrawFlags.StrokeInside) + if stroke_pos == ImDrawFlags.StrokeLegacy then + if bit32.band(flags, ImDrawFlags.AALines) ~= 0 then + PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.50, p_max.y - 0.50, rounding, flags) -- [Luau] scalar math, no temporary ImVec2 + else + PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.49, p_max.y - 0.49, rounding, flags) -- Better looking lower-right corner and rounded non-AA shapes. + end + self:PathStroke(col, thickness, bit32.bor(ImDrawFlags.Closed, ImDrawFlags.JoinMiter, ImDrawFlags.StrokeCenter, bit32.band(flags, bit32.bnot(ImDrawFlags.StrokeMask_)))) + return + end + + local fringe_scale = self._FringeScale + local has_rounding = rounding >= 0.25 * fringe_scale -- IM_HAS_ROUNDING(rounding) + + -- Calculate the outer boundary of the rectangle based on the stroke position. + local offset = 0.0 -- Inside + if stroke_pos == ImDrawFlags.StrokeCenter then + offset = thickness * 0.5 + elseif stroke_pos == ImDrawFlags.StrokeCenterBiased then + offset = _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == ImDrawFlags.StrokeOutside then + offset = thickness + end + local outer_min_x = p_min.x - offset + local outer_min_y = p_min.y - offset + local outer_max_x = p_max.x + offset + local outer_max_y = p_max.y + offset + local outer_rounding = rounding + (has_rounding and offset or 0.0) + + local width = outer_max_x - outer_min_x + local height = outer_max_y - outer_min_y + local half_min_dim = ImMin(width, height) * 0.5 + if half_min_dim <= thickness then + -- Rectangle has collapsed into filled rectangle. + self:AddRectFilled(ImVec2(outer_min_x, outer_min_y), ImVec2(outer_max_x, outer_max_y), col, outer_rounding, flags) + return + end + + if bit32.band(flags, ImDrawFlags.RoundMask_) == 0 then + flags = bit32.bor(flags, ImDrawFlags.RoundAll) + end + if has_rounding then + -- Constrain rounding to rect dimensions + outer_rounding = ImMin(outer_rounding, ImAbs(width) * (((bit32.band(flags, ImDrawFlags.RoundTop) == ImDrawFlags.RoundTop) or (bit32.band(flags, ImDrawFlags.RoundBottom) == ImDrawFlags.RoundBottom)) and 0.5 or 1.0) - 1.0) + outer_rounding = ImMin(outer_rounding, ImAbs(height) * (((bit32.band(flags, ImDrawFlags.RoundLeft) == ImDrawFlags.RoundLeft) or (bit32.band(flags, ImDrawFlags.RoundRight) == ImDrawFlags.RoundRight)) and 0.5 or 1.0) - 1.0) + end + + local is_truncated = outer_min_x % 1 == 0 and outer_min_y % 1 == 0 and outer_max_x % 1 == 0 and outer_max_y % 1 == 0 and outer_rounding % 1 == 0 and thickness % 1 == 0 -- IM_IS_TRUNCATED4() x2 + if self._FringeScaleIsInteger and is_truncated then + local s_rounding = ImTrunc64(outer_rounding * self._InvFringeScale) + local s_thickness = ImTrunc64(thickness * self._InvFringeScale) + if s_thickness <= 0 then + return + end + + if s_rounding <= 0 or bit32.band(flags, ImDrawFlags.RoundMask_) == ImDrawFlags.RoundNone then + -- Pixel aligned non-rounded rect. + local opaque_uv = self._Data.TexUvWhitePixel + local u, v = opaque_uv.x, opaque_uv.y + local t = s_thickness * fringe_scale + + PrimReserve(self, 8 * 3, 8) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + local idx = self._VtxCurrentIdx + WV(vb, vw + 0, outer_min_x, outer_min_y, u, v, col) + WV(vb, vw + 1, outer_min_x + t, outer_min_y + t, u, v, col) + WV(vb, vw + 2, outer_max_x, outer_min_y, u, v, col) + WV(vb, vw + 3, outer_max_x - t, outer_min_y + t, u, v, col) + WV(vb, vw + 4, outer_max_x, outer_max_y, u, v, col) + WV(vb, vw + 5, outer_max_x - t, outer_max_y - t, u, v, col) + WV(vb, vw + 6, outer_min_x, outer_max_y, u, v, col) + WV(vb, vw + 7, outer_min_x + t, outer_max_y - t, u, v, col) + + ib[iw + 0] = idx + 0; ib[iw + 1] = idx + 3; ib[iw + 2] = idx + 1 + ib[iw + 3] = idx + 0; ib[iw + 4] = idx + 2; ib[iw + 5] = idx + 3 + + ib[iw + 6] = idx + 2; ib[iw + 7] = idx + 5; ib[iw + 8] = idx + 3 + ib[iw + 9] = idx + 2; ib[iw + 10] = idx + 4; ib[iw + 11] = idx + 5 + + ib[iw + 12] = idx + 4; ib[iw + 13] = idx + 7; ib[iw + 14] = idx + 5 + ib[iw + 15] = idx + 4; ib[iw + 16] = idx + 6; ib[iw + 17] = idx + 7 + + ib[iw + 18] = idx + 6; ib[iw + 19] = idx + 1; ib[iw + 20] = idx + 7 + ib[iw + 21] = idx + 6; ib[iw + 22] = idx + 0; ib[iw + 23] = idx + 1 + + self._VtxWritePtr = vw + 8 + self._IdxWritePtr = iw + 24 + self._VtxCurrentIdx = idx + 8 + return + end + -- Textured corners are baked with AA, do not use them if no-AA is requested. + -- [Port] TODO baked round corners: allow_tex_corners -> _AddRectBaked(), falls back to the geometry path below. + end + + if not has_rounding or bit32.band(flags, ImDrawFlags.RoundMask_) == ImDrawFlags.RoundNone then + local points = prim_points + points[1].x = outer_min_x; points[1].y = outer_min_y + points[2].x = outer_max_x; points[2].y = outer_min_y + points[3].x = outer_max_x; points[3].y = outer_max_y + points[4].x = outer_min_x; points[4].y = outer_max_y + self:_AddPolyline(points, 4, col, thickness, bit32.bor(ImDrawFlags.Closed, ImDrawFlags.JoinMiter, ImDrawFlags.StrokeInside), half_min_dim) + self._Path.Size = 0 + else + -- Switch to specialized rendering a bit early to avoid overdraw visible in transparent shapes. + if thickness + (0.75 * fringe_scale) > outer_rounding then + -- Special case rendering to avoid rendering artifacts at the corners. + -- If rendered using the regular polyline, the stroke will fold and leave artifacts on the corners if rendered with transparency. + self:_AddRectTinyRounding(ImVec2(outer_min_x, outer_min_y), ImVec2(outer_max_x, outer_max_y), col, outer_rounding, thickness, flags) + return + end + local rounding_tl = (bit32.band(flags, ImDrawFlags.RoundTopLeft) ~= 0) and outer_rounding or 0.0 + local rounding_tr = (bit32.band(flags, ImDrawFlags.RoundTopRight) ~= 0) and outer_rounding or 0.0 + local rounding_br = (bit32.band(flags, ImDrawFlags.RoundBottomRight) ~= 0) and outer_rounding or 0.0 + local rounding_bl = (bit32.band(flags, ImDrawFlags.RoundBottomLeft) ~= 0) and outer_rounding or 0.0 + -- [Luau] inline PathArcToFast() + _PathArcToFastExXY(self, outer_min_x + rounding_tl, outer_min_y + rounding_tl, rounding_tl, 6 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 9 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0) + _PathArcToFastExXY(self, outer_max_x - rounding_tr, outer_min_y + rounding_tr, rounding_tr, 9 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0) + _PathArcToFastExXY(self, outer_max_x - rounding_br, outer_max_y - rounding_br, rounding_br, 0 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 3 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0) + _PathArcToFastExXY(self, outer_min_x + rounding_bl, outer_max_y - rounding_bl, rounding_bl, 3 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 6 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0) + + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(ImDrawFlags.Closed, ImDrawFlags.JoinMiter, ImDrawFlags.StrokeInside, bit32.band(flags, bit32.bnot(ImDrawFlags.StrokeMask_))), half_min_dim) + self._Path.Size = 0 + end +end + +-- [Port] TODO baked round corners: _AddRectFilledBaked() (UseTexForRoundCorners) not ported yet, AddRectFilled() falls back to the geometry path. + +--- @param p_min ImVec2 +--- @param p_max ImVec2 +--- @param col ImU32 +--- @param rounding? float +--- @param flags? ImDrawFlags @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 - if bit32.band(col, IM_COL32_A_MASK) == 0 then return end + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end - if rounding < 0.5 or (bit32.band(flags, ImDrawFlags.RoundMask_) == ImDrawFlags.RoundNone) then + local ax, ay, bx, by = p_min.x, p_min.y, p_max.x, p_max.y + local width = bx - ax + local height = by - ay + local min_dim = ImMin(width, height) + flags = bit32.bor(flags, self.Flags) + + if bit32.band(flags, ImDrawFlags.StrokeMask_) == ImDrawFlags.StrokeLegacy and rounding < 0.5 then PrimReserve(self, 6, 4) -- [Luau] local calls - PrimRectXY(self, p_min.x, p_min.y, p_max.x, p_max.y, col) + PrimRectXY(self, ax, ay, bx, by, col) + return + end + + -- The rect is smaller than pixel in one dimension. + local fringe_scale = self._FringeScale + if min_dim < fringe_scale then + if min_dim < (1.0 / 255.0) * fringe_scale then + return + end + if width < fringe_scale then + col = ImAlphaMultiply(col, width * self._InvFringeScale) + width = fringe_scale + end + if height < fringe_scale then + col = ImAlphaMultiply(col, height * self._InvFringeScale) + height = fringe_scale + end + local points = prim_points + points[1].x = ax; points[1].y = ay + points[2].x = ax + width; points[2].y = ay + points[3].x = ax + width; points[3].y = ay + height + points[4].x = ax; points[4].y = ay + height + self:AddConvexPolyFilled(points, 4, col, ImDrawFlags.JoinMiter) + return + end + + -- When rounding non-integer and under threshold, has_rounding may be false but still affect is_truncated. Likely not worth catering for. + local has_rounding = bit32.band(flags, ImDrawFlags.RoundMask_) ~= ImDrawFlags.RoundNone and rounding >= 0.25 * fringe_scale -- IM_HAS_ROUNDING(rounding) + local is_truncated = ax % 1 == 0 and ay % 1 == 0 and bx % 1 == 0 and by % 1 == 0 and rounding % 1 == 0 -- IM_IS_TRUNCATED4() && IM_IS_TRUNCATED() + + if self._FringeScaleIsInteger and is_truncated then + -- Fast path for the non-AA non-rounded rect. + if not has_rounding then + PrimReserve(self, 6, 4) + PrimRectXY(self, ax, ay, bx, by, col) + return + end + + if bit32.band(flags, ImDrawFlags.RoundMask_) == 0 then + flags = bit32.bor(flags, ImDrawFlags.RoundAll) + end + + rounding = ImMin(rounding, ImAbs(width) * (((bit32.band(flags, ImDrawFlags.RoundTop) == ImDrawFlags.RoundTop) or (bit32.band(flags, ImDrawFlags.RoundBottom) == ImDrawFlags.RoundBottom)) and 0.5 or 1.0) - 1.0) + rounding = ImMin(rounding, ImAbs(height) * (((bit32.band(flags, ImDrawFlags.RoundLeft) == ImDrawFlags.RoundLeft) or (bit32.band(flags, ImDrawFlags.RoundRight) == ImDrawFlags.RoundRight)) and 0.5 or 1.0) - 1.0) + + local s_rounding = ImTrunc64(rounding * self._InvFringeScale) + if s_rounding <= 0 then + PrimReserve(self, 6, 4) + PrimRectXY(self, ax, ay, bx, by, col) + return + end + -- Textured corners are baked with AA, do not use them if no-AA is requested. + -- [Port] TODO baked round corners: allow_tex_corners -> _AddRectFilledBaked(), falls back to the geometry path below. + end + + if not has_rounding then + local points = prim_points + points[1].x = ax; points[1].y = ay + points[2].x = bx; points[2].y = ay + points[3].x = bx; points[3].y = by + points[4].x = ax; points[4].y = by + self:AddConvexPolyFilled(points, 4, col, ImDrawFlags.JoinMiter) else - PathRectXY(self, p_min.x, p_min.y, p_max.x, p_max.y, rounding, flags) - self:PathFillConvex(col) + PathRectXY(self, ax, ay, bx, by, rounding, flags) + self:PathFillConvex(col, ImDrawFlags.JoinMiter) end end @@ -4696,189 +5741,353 @@ function MT.ImDrawList:AddRectFilledMultiColor(p_min, p_max, col_upr_left, col_u self._VtxCurrentIdx = idx + 4 end --- p_min = upper-left, p_max = lower-right ---- @param p_min ImVec2 ---- @param p_max ImVec2 ---- @param col ImU32 ---- @param rounding float --- @param thickness? float --- @param flags? ImDrawFlags -@native -function MT.ImDrawList:AddRect(p_min, p_max, col, rounding, thickness, flags) +function MT.ImDrawList:AddQuad(p1, p2, p3, p4, col, thickness, flags) if thickness == nil then thickness = 1.0 end if flags == nil then flags = 0 end - -- If this assert triggers on legacy code: - -- - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. - -- - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. - -- - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundXXX values. - -- If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags! - -- - Hard coded support for ~0 == ImDrawFlags_RoundAll. - -- - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. - -- - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundNone or use 'float rounding = 0.0f'. - -- See "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. - IM_ASSERT_USER_ERROR_RET(bit32.band(flags, ImDrawFlags.InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?") -- Or misuse of legacy hard-coded ImDrawCornerFlags values - - if bit32.band(col, IM_COL32_A_MASK) == 0 then - return - end - -- [Luau] square corners + whole-pixel thickness: 4 filled rects instead of a stroked path (same pixels, far cheaper here) - if (rounding == nil or rounding < 0.5 or bit32.band(flags, ImDrawFlags.RoundMask_) == ImDrawFlags.RoundNone) - and thickness >= 1.0 and thickness % 1 == 0 and p_max.x - p_min.x > 2 * thickness and p_max.y - p_min.y > 2 * thickness then - local x0, y0, x1, y1, t = p_min.x, p_min.y, p_max.x, p_max.y, thickness - PrimReserve(self, 24, 16) - PrimRectXY(self, x0, y0, x1, y0 + t, col) - PrimRectXY(self, x0, y1 - t, x1, y1, col) - PrimRectXY(self, x0, y0 + t, x0 + t, y1 - t, col) - PrimRectXY(self, x1 - t, y0 + t, x1, y1 - t, col) - return - end - if bit32.band(self.Flags, ImDrawFlags.AALines) ~= 0 then - PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.50, p_max.y - 0.50, rounding, flags) -- [Luau] scalar math, no temporary ImVec2 - else - PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.49, p_max.y - 0.49, rounding, flags) -- Better looking lower-right corner and rounded non-AA shapes. - end - - self:PathStroke(col, thickness, ImDrawFlags.Closed) -end - -function MT.ImDrawList:AddLine(p1, p2, col, thickness) - if bit32.band(col, IM_COL32_A_MASK) == 0 then return end - - PathLineToXY(self, p1.x + 0.5, p1.y + 0.5) -- [Luau] scalar math, no temporary ImVec2 - PathLineToXY(self, p2.x + 0.5, p2.y + 0.5) - self:PathStroke(col, thickness) -end - ---- @param min_x float ---- @param max_x float ---- @param y float ---- @param col ImU32 ---- @param thickness? float -function MT.ImDrawList:AddLineH(min_x, max_x, y, col, thickness) - if thickness == nil then thickness = 1.0 end - - if bit32.band(col, IM_COL32_A_MASK) == 0 then - return - end - PathLineToXY(self, min_x + 0.5, y + 0.5) - PathLineToXY(self, max_x + 0.5, y + 0.5) - self:PathStroke(col, thickness) -end - ---- @param x float ---- @param min_y float ---- @param max_y float ---- @param col ImU32 ---- @param thickness? float -function MT.ImDrawList:AddLineV(x, min_y, max_y, col, thickness) - if thickness == nil then thickness = 1.0 end - - if bit32.band(col, IM_COL32_A_MASK) == 0 then - return - end - PathLineToXY(self, x + 0.5, min_y + 0.5) - PathLineToXY(self, x + 0.5, max_y + 0.5) - self:PathStroke(col, thickness) -end - -function MT.ImDrawList:AddTriangle(p1, p2, p3, col, thickness) if bit32.band(col, IM_COL32_A_MASK) == 0 then return end - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathStroke(col, thickness, ImDrawFlags.Closed) -end + --flags |= Flags; // Not needed. + local stroke_pos = _GetStrokePos(flags, ImDrawFlags.StrokeInside) + flags = bit32.bor(bit32.band(flags, bit32.bnot(ImDrawFlags.StrokeMask_)), stroke_pos) -- As AddPolyline() default is different -function MT.ImDrawList:AddQuad(p1, p2, p3, p4, col, thickness) - if thickness == nil then thickness = 1.0 end - if bit32.band(col, IM_COL32_A_MASK) == 0 then return end - - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathLineTo(p4) - self:PathStroke(col, thickness, ImDrawFlags.Closed) + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3); ImVec2_Copy(points[4], p4) + self:_AddPolyline(points, 4, col, thickness, bit32.bor(flags, ImDrawFlags.Closed), FLT_MAX) end function MT.ImDrawList:AddQuadFilled(p1, p2, p3, p4, col) - if bit32.band(col, IM_COL32_A_MASK) == 0 then return end + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathLineTo(p4) - self:PathFillConvex(col) + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3); ImVec2_Copy(points[4], p4) + self:AddConvexPolyFilled(points, 4, col) +end + +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddTriangle(p1, p2, p3, col, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + --flags |= Flags; // Not needed. + local stroke_pos = _GetStrokePos(flags, ImDrawFlags.StrokeInside) + flags = bit32.bor(bit32.band(flags, bit32.bnot(ImDrawFlags.StrokeMask_)), stroke_pos) -- As AddPolyline() default is different + + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3) + self:_AddPolyline(points, 3, col, thickness, bit32.bor(flags, ImDrawFlags.Closed), FLT_MAX) end function MT.ImDrawList:AddTriangleFilled(p1, p2, p3, col) - if bit32.band(col, IM_COL32_A_MASK) == 0 then return end - - self:PathLineTo(p1) - self:PathLineTo(p2) - self:PathLineTo(p3) - self:PathFillConvex(col) -end - ---- @param center ImVec2 ---- @param radius float ---- @param col ImU32 ---- @param num_segments int ---- @param thickness float -function MT.ImDrawList:AddCircle(center, radius, col, num_segments, thickness) - if num_segments == nil then num_segments = 0 end - if thickness == nil then thickness = 1.0 end - - if bit32.band(col, IM_COL32_A_MASK) == 0 or radius < 0.5 then + if bit32.band(col, IM_COL32_A_MASK) == 0 then return end - if num_segments <= 0 then + local points = prim_points + ImVec2_Copy(points[1], p1); ImVec2_Copy(points[2], p2); ImVec2_Copy(points[3], p3) + self:AddConvexPolyFilled(points, 3, col) +end + +--- @param center ImVec2 +--- @param radius float +--- @param col ImU32 +--- @param num_segments? int +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddCircle(center, radius, col, num_segments, thickness, flags) + if num_segments == nil then num_segments = 0 end + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, ImDrawFlags.StrokeInside) + if stroke_pos == ImDrawFlags.StrokeLegacy then + radius = radius - 0.5 + stroke_pos = ImDrawFlags.StrokeCenter + end + radius = ImMax(0.01, radius) + + local outer_radius = radius + if stroke_pos == ImDrawFlags.StrokeCenter then + outer_radius = radius + thickness * 0.5 + elseif stroke_pos == ImDrawFlags.StrokeCenterBiased then + outer_radius = radius + _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == ImDrawFlags.StrokeInside then + outer_radius = radius + elseif stroke_pos == ImDrawFlags.StrokeOutside then + outer_radius = radius + thickness + end + + if outer_radius <= thickness then + -- The circle has collapsed into a filled circle. + self:AddCircleFilled(center, outer_radius, col, num_segments) + return + end + + if num_segments <= 0 and outer_radius <= self._Data.ArcFastRadiusCutoff then -- Use arc with automatic segment count - _PathArcToFastExXY(self, center.x, center.y, radius - 0.5, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0) + local a_step = math.floor(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / self:_CalcCircleAutoSegmentCount(outer_radius)) -- Use outer_radius for segment count to be consistent with rounded rect. + _PathArcToFastExXY(self, center.x, center.y, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, a_step) self._Path.Size = self._Path.Size - 1 else - -- Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) + -- Explicit segment count or above fast arc cutoff (still clamp to avoid drawing insanely tessellated shapes) + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(outer_radius) + end num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (IM_PI * 2.0) * (num_segments - 1) / num_segments - self:PathArcTo(center, radius - 0.5, 0.0, a_max, num_segments - 1) + local a_max = (IM_PI * 2.0) * (num_segments - 1.0) / num_segments + self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) end - self:PathStroke(col, thickness, ImDrawFlags.Closed) + if self._Path.Size >= 3 then + -- Calculate distance to the edge of the polygon describing the circle. + -- This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. + local unit_apothem = ApproxApothem(self._Path.Size) + local edge_dist = radius * unit_apothem + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(ImDrawFlags.Closed, ImDrawFlags.JoinMiter, stroke_pos), edge_dist) + end + self._Path.Size = 0 end ---- @param center ImVec2 ---- @param radius float ---- @param col ImU32 ---- @param num_segments int +--- @param center ImVec2 +--- @param radius float +--- @param col ImU32 +--- @param num_segments? int function MT.ImDrawList:AddCircleFilled(center, radius, col, num_segments) if num_segments == nil then num_segments = 0 end - if bit32.band(col, IM_COL32_A_MASK) == 0 or radius < 0.5 then + if bit32.band(col, IM_COL32_A_MASK) == 0 then return end - if num_segments <= 0 then + local diameter = radius * 2.0 + if diameter < self._FringeScale then + if diameter < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, diameter * self._InvFringeScale) + radius = self._FringeScale * 0.5 + end + + if num_segments <= 0 and radius <= self._Data.ArcFastRadiusCutoff then -- Use arc with automatic segment count _PathArcToFastExXY(self, center.x, center.y, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0) self._Path.Size = self._Path.Size - 1 else - -- Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) + -- Explicit segment count or above cutoff (still clamp to avoid drawing insanely tessellated shapes) + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(radius) + end num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (IM_PI * 2.0) * (num_segments - 1) / num_segments + local a_max = (IM_PI * 2.0) * (num_segments - 1.0) / num_segments self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) end + self:PathFillConvex(col, ImDrawFlags.JoinMiter) +end + +-- Guaranteed to honor 'num_segments' +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddNgon(center, radius, col, num_segments, thickness, flags) + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, IM_COL32_A_MASK) == 0 or num_segments <= 2 then + return + end + + flags = bit32.bor(flags, self.Flags) + local stroke_pos = _GetStrokePos(flags, ImDrawFlags.StrokeInside) + if stroke_pos == ImDrawFlags.StrokeLegacy then + radius = radius - 0.5 + stroke_pos = ImDrawFlags.StrokeCenter + end + radius = ImMax(0.01, radius) + + -- Calculate distance to the edge of the polygon describing the circle. + -- This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. + local unit_apothem = ApproxApothem(num_segments) + local edge_dist = radius * unit_apothem + + -- Check overestimate first before testing details. + if radius < thickness * 2.0 then + local outer_radius = radius + if stroke_pos == ImDrawFlags.StrokeCenter then + outer_radius = radius + thickness * 0.5 + elseif stroke_pos == ImDrawFlags.StrokeCenterBiased then + outer_radius = radius + _CalculateCenterBiasedOffset(self, thickness) + elseif stroke_pos == ImDrawFlags.StrokeInside then + outer_radius = radius + elseif stroke_pos == ImDrawFlags.StrokeOutside then + outer_radius = radius + thickness + end + if outer_radius * unit_apothem <= thickness then + -- The polygon has collapsed into a filled polygon. + self:AddNgonFilled(center, outer_radius, col, num_segments) + return + end + end + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = (IM_PI * 2.0) * (num_segments - 1.0) / num_segments + self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(ImDrawFlags.Closed, ImDrawFlags.JoinMiter, stroke_pos), edge_dist) + self._Path.Size = 0 +end + +-- Guaranteed to honor 'num_segments' +function MT.ImDrawList:AddNgonFilled(center, radius, col, num_segments) + if bit32.band(col, IM_COL32_A_MASK) == 0 or num_segments <= 2 then + return + end + + local diameter = radius * 2.0 + if diameter < self._FringeScale then + if diameter < (1.0 / 255.0) * self._FringeScale then + return + end + col = ImAlphaMultiply(col, diameter * self._InvFringeScale) + radius = self._FringeScale * 0.5 + end + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = (IM_PI * 2.0) * (num_segments - 1.0) / num_segments + self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) + self:PathFillConvex(col, ImDrawFlags.JoinMiter) +end + +-- Ellipse +--- @param rot? float +--- @param num_segments? int +--- @param thickness? float +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddEllipse(center, radius, col, rot, num_segments, thickness, flags) + if rot == nil then rot = 0.0 end + if num_segments == nil then num_segments = 0 end + if thickness == nil then thickness = 1.0 end + if flags == nil then flags = 0 end + + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + local stroke_pos = _GetStrokePos(flags, ImDrawFlags.StrokeInside) + if stroke_pos == ImDrawFlags.StrokeLegacy then + stroke_pos = ImDrawFlags.StrokeCenter + end + + local rad = ImVec2(radius.x, radius.y) + rad.x = ImMax(0.01, rad.x) + rad.y = ImMax(0.01, rad.y) + + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(ImMax(rad.x, rad.y)) -- A bit pessimistic, maybe there's a better computation to do here. + end + + self._Path:reserve(self._Path.Size + (num_segments + 1)) + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = IM_PI * 2.0 * (num_segments - 1.0) / num_segments + self:PathEllipticalArcTo(center, rad, rot, 0.0, a_max, num_segments - 1) + + -- Prevent the inside part of the stroke to be expanded too much to prevent some rendering artifacts. + local max_inner_offset = ImMin(rad.x, rad.y) + self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, bit32.bor(ImDrawFlags.Closed, ImDrawFlags.JoinMiter, stroke_pos), max_inner_offset) + self._Path.Size = 0 +end + +--- @param rot? float +--- @param num_segments? int +function MT.ImDrawList:AddEllipseFilled(center, radius, col, rot, num_segments) + if rot == nil then rot = 0.0 end + if num_segments == nil then num_segments = 0 end + + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + local rad = ImVec2(radius.x, radius.y) + local diameter_x = rad.x * 2.0 + local diameter_y = rad.y * 2.0 + local min_dim = ImMin(diameter_x, diameter_y) + + -- The ellipse is smaller than pixel in one dimension. + if min_dim < self._FringeScale then + if min_dim < (1.0 / 255.0) * self._FringeScale then + return + end + if diameter_x < self._FringeScale then + col = ImAlphaMultiply(col, diameter_x * self._InvFringeScale) + rad.x = self._FringeScale * 0.5 + end + if diameter_y < self._FringeScale then + col = ImAlphaMultiply(col, diameter_y * self._InvFringeScale) + rad.y = self._FringeScale * 0.5 + end + end + + if num_segments <= 0 then + num_segments = self:_CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)) -- A bit pessimistic, maybe there's a better computation to do here. + end + + -- Because we are filling a closed shape we remove 1 from the count of segments/points + local a_max = IM_PI * 2.0 * (num_segments - 1.0) / num_segments + self:PathEllipticalArcTo(center, radius, rot, 0.0, a_max, num_segments - 1) -- [Port] upstream passes 'radius' (not 'rad') here, kept as-is self:PathFillConvex(col) end +-- Cubic Bezier takes 4 controls points +--- @param num_segments? int +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments, flags) + if num_segments == nil then num_segments = 0 end + if flags == nil then flags = 0 end + + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + self:PathLineTo(p1) + self:PathBezierCubicCurveTo(p2, p3, p4, num_segments) + self:PathStroke(col, thickness, flags) +end + +-- Quadratic Bezier takes 3 controls points +--- @param num_segments? int +--- @param flags? ImDrawFlags +function MT.ImDrawList:AddBezierQuadratic(p1, p2, p3, col, thickness, num_segments, flags) + if num_segments == nil then num_segments = 0 end + if flags == nil then flags = 0 end + + if bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + self:PathLineTo(p1) + self:PathBezierQuadraticCurveTo(p2, p3, num_segments) + self:PathStroke(col, thickness, flags) +end + --- @param font ImFont --- @param font_size float --- @param pos ImVec2 @@ -4968,6 +6177,7 @@ function MT.ImDrawList:AddImageRounded(tex_ref, p_min, p_max, uv_min, uv_max, co if bit32.band(col, IM_COL32_A_MASK) == 0 then return end + if flags == nil then flags = 0 end IM_ASSERT(bit32.band(flags, 0x0F) == 0, "Misuse of legacy hardcoded ImDrawCornerFlags values!") -- If this assert triggers on legacy code: see comments in ImDrawList:PathRect(). if bit32.band(flags, ImDrawFlags.RoundMask_) == 0 then @@ -4986,7 +6196,7 @@ function MT.ImDrawList:AddImageRounded(tex_ref, p_min, p_max, uv_min, uv_max, co local vert_start_idx = self.VtxBuffer.Size + 1 self:PathRect(p_min, p_max, rounding, flags) - self:PathFillConvex(col) + self:PathFillConvex(col, ImDrawFlags.JoinMiter) local vert_end_idx = self.VtxBuffer.Size + 1 ImGui.ShadeVertsLinearUV(self, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true) @@ -5056,19 +6266,21 @@ end -- [Luau] scalar center (cx, cy); assigned to the forward-declared local, _PathArcToFastEx() wraps it @native function _PathArcToFastExXY(self, cx, cy, radius, a_min_sample, a_max_sample, a_step) - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) PathLineToXY(self, cx, cy) return end -- Calculate arc auto segment step size if a_step <= 0 then - a_step = math.floor(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / self:_CalcCircleAutoSegmentCount(radius)) + -- Calculate step size using the common function. No need to clamp, the step is already in range. + local auto_seg_count = self:_CalcCircleAutoSegmentCount(radius) + a_step = CalcCornerArcStepAndCount(auto_seg_count) + else + -- Make sure we never do steps larger than one quarter of the circle + a_step = ImClamp(a_step, 1, math.floor(IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4)) end - -- Make sure we never do steps larger than one quarter of the circle - a_step = ImClamp(a_step, 1, math.floor(IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4)) - local sample_range = ImAbs(a_max_sample - a_min_sample) local a_next_step = a_step @@ -5160,7 +6372,7 @@ end -- 0: East, 3: South, 6: West, 9: North, 12: East @native function MT.ImDrawList:PathArcToFast(center, radius, a_min_of_12, a_max_of_12) - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) self._Path:push_back(center) return end @@ -5174,7 +6386,7 @@ end --- @param a_max float --- @param num_segments int function MT.ImDrawList:_PathArcToN(center, radius, a_min, a_max, num_segments) - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) self._Path:push_back(center) return end @@ -5202,7 +6414,7 @@ end function MT.ImDrawList:PathArcTo(center, radius, a_min, a_max, num_segments) if num_segments == nil then num_segments = 0 end - if radius < 0.5 then + if not (radius >= 0.25 * self._FringeScale) then -- !IM_HAS_ROUNDING(radius) self._Path:push_back(center) return end @@ -5216,13 +6428,21 @@ function MT.ImDrawList:PathArcTo(center, radius, a_min, a_max, num_segments) if radius <= self._Data.ArcFastRadiusCutoff then local a_is_reverse = a_max < a_min + -- Calculate number of segments per circle. + -- First we calculate the step count as usual, then match to the precomputed directions. + -- This will give us the spacing between the samples (step), and how many segments to use in total for a full circle. + local auto_seg_count = self:_CalcCircleAutoSegmentCount(radius) + local arc_step, arc_step_count = CalcCornerArcStepAndCount(auto_seg_count) + local arc_seg_count = arc_step_count * 4 -- Seg count for full circle. + -- We are going to use precomputed values for mid samples. -- Determine first and last sample in lookup table that belong to the arc. - local a_min_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_min / (IM_PI * 2.0) - local a_max_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_max / (IM_PI * 2.0) + -- In first step we use the coarser segment count, and after rounding, convert back to the precomputed array index. + local a_min_sample_f = arc_seg_count * a_min / (IM_PI * 2.0) + local a_max_sample_f = arc_seg_count * a_max / (IM_PI * 2.0) - local a_min_sample = a_is_reverse and ImFloor(a_min_sample_f) or ImCeil(a_min_sample_f) - local a_max_sample = a_is_reverse and ImCeil(a_max_sample_f) or ImFloor(a_max_sample_f) + local a_min_sample = (a_is_reverse and ImFloor(a_min_sample_f) or ImCeil(a_min_sample_f)) * arc_step + local a_max_sample = (a_is_reverse and ImCeil(a_max_sample_f) or ImFloor(a_max_sample_f)) * arc_step local a_mid_samples = a_is_reverse and ImMax(a_min_sample - a_max_sample, 0) or ImMax(a_max_sample - a_min_sample, 0) local a_min_segment_angle = a_min_sample * IM_PI * 2.0 / IM_DRAWLIST_ARCFAST_SAMPLE_MAX @@ -5233,7 +6453,7 @@ function MT.ImDrawList:PathArcTo(center, radius, a_min, a_max, num_segments) self._Path:reserve(self._Path.Size + (a_mid_samples + 1 + (a_emit_start and 1 or 0) + (a_emit_end and 1 or 0))) if a_emit_start then PathLineToXY(self, center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius) end -- [Luau] no temporary ImVec2 - if a_mid_samples > 0 then _PathArcToFastExXY(self, center.x, center.y, radius, a_min_sample, a_max_sample, 0) end + if a_mid_samples > 0 then _PathArcToFastExXY(self, center.x, center.y, radius, a_min_sample, a_max_sample, arc_step) end if a_emit_end then PathLineToXY(self, center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius) end else local arc_length = ImAbs(a_max - a_min) @@ -5267,7 +6487,8 @@ function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_b if y > clip_rect.w then return end - if bit32.band(draw_list.Flags, ImDrawFlags.TextNoPixelSnap) == 0 then + flags = bit32.bor(flags, draw_list.Flags) + if bit32.band(flags, ImDrawFlags.TextNoPixelSnap) == 0 then x = IM_TRUNC(x) y = IM_TRUNC(y) end @@ -5784,9 +7005,8 @@ function ImGui.RenderRectFilledWithHole(draw_list, outer, inner, col, rounding) end ------------------------------------------------------------------------------- --- Extra primitives (Ngon, Ellipse, Bezier, concave fill) +-- Extra path functions (elliptical arc, bezier curves) and concave fill ------------------------------------------------------------------------------- --- [port] These follow the 1.92 stroker this port uses (no ImDrawFlags_StrokeXXX placement flags). function MT.ImDrawList:PathEllipticalArcTo(center, radius, rot, a_min, a_max, num_segments) if num_segments == nil or num_segments <= 0 then @@ -5869,63 +7089,6 @@ function MT.ImDrawList:PathBezierQuadraticCurveTo(p2, p3, num_segments) end end --- Guaranteed to honor 'num_segments' -function MT.ImDrawList:AddNgon(center, radius, col, num_segments, thickness) - if thickness == nil then thickness = 1.0 end - if bit32.band(col, IM_COL32_A_MASK) == 0 or num_segments <= 2 then return end - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (IM_PI * 2.0) * (num_segments - 1.0) / num_segments - self:PathArcTo(center, radius - 0.5, 0.0, a_max, num_segments - 1) - self:PathStroke(col, thickness, ImDrawFlags.Closed) -end - --- Guaranteed to honor 'num_segments' -function MT.ImDrawList:AddNgonFilled(center, radius, col, num_segments) - if bit32.band(col, IM_COL32_A_MASK) == 0 or num_segments <= 2 then return end - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = (IM_PI * 2.0) * (num_segments - 1.0) / num_segments - self:PathArcTo(center, radius, 0.0, a_max, num_segments - 1) - self:PathFillConvex(col) -end - --- Ellipse -function MT.ImDrawList:AddEllipse(center, radius, col, rot, num_segments, thickness) - if rot == nil then rot = 0.0 end - if thickness == nil then thickness = 1.0 end - if bit32.band(col, IM_COL32_A_MASK) == 0 then return end - if num_segments == nil or num_segments <= 0 then num_segments = self:_CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)) end -- A bit pessimistic, maybe there's a better computation to do here. - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = IM_PI * 2.0 * (num_segments - 1.0) / num_segments - self:PathEllipticalArcTo(center, radius, rot, 0.0, a_max, num_segments - 1) - self:PathStroke(col, thickness, ImDrawFlags.Closed) -end - -function MT.ImDrawList:AddEllipseFilled(center, radius, col, rot, num_segments) - if rot == nil then rot = 0.0 end - if bit32.band(col, IM_COL32_A_MASK) == 0 then return end - if num_segments == nil or num_segments <= 0 then num_segments = self:_CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)) end -- A bit pessimistic, maybe there's a better computation to do here. - -- Because we are filling a closed shape we remove 1 from the count of segments/points - local a_max = IM_PI * 2.0 * (num_segments - 1.0) / num_segments - self:PathEllipticalArcTo(center, radius, rot, 0.0, a_max, num_segments - 1) - self:PathFillConvex(col) -end - --- Cubic Bezier takes 4 controls points -function MT.ImDrawList:AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments) - if bit32.band(col, IM_COL32_A_MASK) == 0 then return end - self:PathLineTo(p1) - self:PathBezierCubicCurveTo(p2, p3, p4, num_segments or 0) - self:PathStroke(col, thickness or 1.0) -end - --- Quadratic Bezier takes 3 controls points -function MT.ImDrawList:AddBezierQuadratic(p1, p2, p3, col, thickness, num_segments) - if bit32.band(col, IM_COL32_A_MASK) == 0 then return end - self:PathLineTo(p1) - self:PathBezierQuadraticCurveTo(p2, p3, num_segments or 0) - self:PathStroke(col, thickness or 1.0) -end - -- ponytail: ear-clipping, no anti-aliased fringe (upstream's AA triangulator not ported). O(n^2) is fine for UI polygons. --- @param points ImVec2[] # 1-based function MT.ImDrawList:AddConcavePolyFilled(points, points_count, col) diff --git a/tools/test/check_all.sh b/tools/test/check_all.sh index 0dea0d6..8104f58 100755 --- a/tools/test/check_all.sh +++ b/tools/test/check_all.sh @@ -32,6 +32,8 @@ CHECKS=( "libhash|LIBHASH|NO_DEMO=1 $R --main tools/test/scripts/t_libhash.luau --frames 120" "demo_open|DRAWHASH|$R --main tools/test/scripts/t_drawhash.luau --frames 150 --open" "demo_random|^|python3 tools/test/run.py --roblox --timeout 600 --frames 400 --open" +"pixels|PIXELS SAME|a=\$($R --main tools/test/scripts/t_pixels.luau --frames 600 | grep -P '^PIX\t'); b=\$(RUN_FLAGS=ARG_FORCE=true $R --main tools/test/scripts/t_pixels.luau --frames 600 | grep -P '^PIX\t'); [ -n \"\$a\" ] && [ \"\$a\" = \"\$b\" ] && echo PIXELS SAME" +"shot|wrote|python3 tools/test/shot.py /tmp/imgui_shot.png --app" "love|DRAWHASH|python3 tools/bundle.py --target love >/dev/null && luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic && luajit tools/test/love/t_examples.lua" ) diff --git a/tools/test/love/run.sh b/tools/test/love/run.sh index 9201a96..9dd7d76 100755 --- a/tools/test/love/run.sh +++ b/tools/test/love/run.sh @@ -4,4 +4,4 @@ set -e cd "$(dirname "$0")/../../.." python3 tools/bundle.py --target love >/dev/null nix-shell -p luajit --run "luajit tools/test/love/t_drawhash.lua 150 Widgets,Basic && luajit tools/test/love/t_drawhash.lua 150 all && luajit tools/test/love/t_examples.lua" -echo "expected (Luau, tools/test/scripts/t_drawhash.luau): 855922436 / 1889516030" +echo "expected (Luau, tools/test/scripts/t_drawhash.luau): 2057580240 / 2004376225" diff --git a/tools/test/scripts/t_shot.luau b/tools/test/scripts/t_shot.luau new file mode 100644 index 0000000..2b29ae3 --- /dev/null +++ b/tools/test/scripts/t_shot.luau @@ -0,0 +1,138 @@ +-- Screenshot of a fixed scene (t_libhash's widgets + primitives, optionally the Custom Rendering example): +-- composites the ScreenGui like t_composite and prints "ROW y hex". tools/test/shot.py turns it into a PNG. +-- RUN_FLAGS: ARG_APP=true also opens ShowExampleAppCustomRendering (needs the demo). +local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui")) +local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4 +ImGui.CreateContext(); G.ImGui_ImplRoblox.Init(); G.ImGui_ImplRoblox.SetRenderRate(0) +local io = ImGui.GetIO() +local W, H = 1280, 760 +local frame = 0 +local st = { b = false, f = 0.25, i = 42, c = ImVec4(0.2, 0.7, 0.4, 1.0), buf = ImGui.NewTextBuffer("hello", 64), r = 0, tab = 0, combo = 1, list = 2 } +local plot = {} +for i = 1, 40 do plot[i] = math.sin(i * 0.3) end +local poly = { ImVec2(0, 0), ImVec2(60, 10), ImVec2(30, 30), ImVec2(70, 60), ImVec2(0, 50) } + +local function composite(gui) + local screen = buffer.create(W * H * 3) + local function walk(inst, ox, oy, kx0, ky0, kx1, ky1) + local kids = inst:GetChildren() + table.sort(kids, function(a, b) return (a.ZIndex or 1) < (b.ZIndex or 1) end) -- table.sort isn't stable, but siblings with equal ZIndex never overlap here + for _, c in ipairs(kids) do + if c.Visible ~= false then + local x, y = ox + (c.Position and c.Position.X or 0), oy + (c.Position and c.Position.Y or 0) + local cx0, cy0, cx1, cy1 = kx0, ky0, kx1, ky1 + if c.ClipsDescendants then + cx0, cy0 = math.max(cx0, x), math.max(cy0, y) + cx1, cy1 = math.min(cx1, x + c.Size.X), math.min(cy1, y + c.Size.Y) + end + if c.ClassName == "ImageLabel" then + local img = c.ImageContent.Object + local px, sz = img.Pixels, img.Size.X + for ty = 0, sz - 1 do + local sy = y + ty + if sy >= ky0 and sy < ky1 then + for tx = 0, sz - 1 do + local sx = x + tx + if sx >= kx0 and sx < kx1 then + local o = (ty * sz + tx) * 4 + local a = buffer.readu8(px, o + 3) + if a > 0 then + local d = (sy * W + sx) * 3 + local fa = a / 255 + for ch = 0, 2 do + local v = buffer.readu8(px, o + ch) * fa + buffer.readu8(screen, d + ch) * (1 - fa) + buffer.writeu8(screen, d + ch, math.floor(v + 0.5)) + end + end + end + end + end + end + end + walk(c, x, y, cx0, cy0, cx1, cy1) + end + end + end + walk(gui, 0, 0, 0, 0, W, H) + return screen +end + +game:GetService("RunService").Stepped:Connect(function() + frame += 1 + G.ImGui_ImplRoblox.NewFrame() + io.DeltaTime = 1 / 60 + ImGui.NewFrame() + ImGui.SetNextWindowPos(ImVec2(10, 10), G.ImGuiCond.Always); ImGui.SetNextWindowSize(ImVec2(320, 560), G.ImGuiCond.Always) + ImGui.Begin("Widgets") + ImGui.Text("Text %d", 123); ImGui.TextColored(ImVec4(1, 0.5, 0, 1), "Colored"); ImGui.TextWrapped("Wrapped text that is long enough to wrap around the window edge at least once.") + ImGui.Button("Button"); ImGui.SameLine(); ImGui.SmallButton("Small"); ImGui.SameLine(); ImGui.ArrowButton("##a", G.ImGuiDir.Left) + st.b = ImGui.Checkbox("Checkbox", st.b) + st.r = ImGui.RadioButton("R0", st.r, 0); ImGui.SameLine(); st.r = ImGui.RadioButton("R1", st.r, 1) + st.f = ImGui.SliderFloat("Slider", st.f, 0, 1); st.i = ImGui.DragInt("Drag", st.i, 1, 0, 100) + ImGui.InputText("Input", st.buf, 64) + ImGui.ColorEdit4("Color", st.c) + st.combo = ImGui.Combo("Combo", st.combo, { "AAA", "BBB", "CCC" }) + st.list = ImGui.ListBox("List", st.list, { "one", "two", "three", "four" }, 4, 3) + ImGui.ProgressBar(0.4, ImVec2(-1, 0)); ImGui.ProgressBar(-1.0 * frame / 60, ImVec2(-1, 0), "busy") + ImGui.PlotLines("Lines", plot, nil, #plot); ImGui.PlotHistogram("Hist", plot, nil, #plot, 0, nil, -1, 1, ImVec2(0, 40)) + ImGui.Bullet(); ImGui.Text("bullet"); ImGui.Separator(); ImGui.SeparatorText("Sep") + if ImGui.TreeNode("Tree") then ImGui.Text("inside"); ImGui.TreePop() end + ImGui.SetNextItemOpen(true, G.ImGuiCond.Once) + if ImGui.CollapsingHeader("Header") then + if ImGui.BeginTabBar("tabs") then + if ImGui.BeginTabItem("Tab A") then ImGui.Text("A"); ImGui.EndTabItem() end + if ImGui.BeginTabItem("Tab B") then ImGui.Text("B"); ImGui.EndTabItem() end + ImGui.EndTabBar() + end + if ImGui.BeginTable("t", 3, bit32.bor(G.ImGuiTableFlags.Borders, G.ImGuiTableFlags.RowBg, G.ImGuiTableFlags.Resizable)) then + ImGui.TableSetupColumn("One"); ImGui.TableSetupColumn("Two"); ImGui.TableSetupColumn("Three"); ImGui.TableHeadersRow() + for row = 0, 4 do ImGui.TableNextRow(); for col = 0, 2 do ImGui.TableSetColumnIndex(col); ImGui.Text("r%d c%d", row, col) end end + ImGui.EndTable() + end + end + ImGui.End() + + ImGui.SetNextWindowPos(ImVec2(340, 10), G.ImGuiCond.Always); ImGui.SetNextWindowSize(ImVec2(300, 560), G.ImGuiCond.Always) + ImGui.Begin("Draw") + local dl, p = ImGui.GetWindowDrawList(), ImGui.GetCursorScreenPos() + local x, y, col = p.x + 4, p.y + 4, 0xFF40C0FF + for i, th in { 1.0, 2.5, 6.0 } do + local yy = y + (i - 1) * 70 + dl:AddRect(ImVec2(x, yy), ImVec2(x + 50, yy + 50), col, 10.0, th) + dl:AddRectFilled(ImVec2(x + 60, yy), ImVec2(x + 110, yy + 50), col, 10.0, G.ImDrawFlags.RoundTopLeft or G.ImDrawFlags.RoundCornersTopLeft) -- RoundCornersTopLeft: pre-1.93 revs (hashes.sh) + dl:AddCircle(ImVec2(x + 145, yy + 25), 25, col, 0, th) + dl:AddNgon(ImVec2(x + 200, yy + 25), 25, col, 6, th) + dl:AddTriangle(ImVec2(x + 230, yy + 50), ImVec2(x + 255, yy), ImVec2(x + 280, yy + 50), col, th) + dl:AddLine(ImVec2(x, yy + 60), ImVec2(x + 280, yy + 55), col, th) + end + y += 210 + dl:AddBezierCubic(ImVec2(x, y), ImVec2(x + 60, y - 30), ImVec2(x + 120, y + 60), ImVec2(x + 180, y), col, 3.0) + dl:AddBezierQuadratic(ImVec2(x, y + 30), ImVec2(x + 90, y - 10), ImVec2(x + 180, y + 30), col, 2.0) + dl:AddEllipse(ImVec2(x + 230, y + 15), ImVec2(40, 15), col, 0.5, 0, 2.0) + y += 50 + local pts = {} + for i, v in poly do pts[i] = ImVec2(x + v.x, y + v.y) end + dl:AddConcavePolyFilled(pts, #pts, 0xFF8080FF) + for i, v in poly do pts[i] = ImVec2(x + 100 + v.x, y + v.y) end + dl:AddPolyline(pts, #pts, col, 4.0, G.ImDrawFlags.Closed) + for i, v in poly do pts[i] = ImVec2(x + 200 + v.x, y + v.y) end + dl:AddPolyline(pts, #pts, col, 1.0, 0) + y += 80 + dl:AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + 100, y + 40), 0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFFFFFF) + dl:AddCircleFilled(ImVec2(x + 140, y + 20), 20, col) + dl:AddEllipseFilled(ImVec2(x + 210, y + 20), ImVec2(30, 15), col, 0.3) + dl:AddText(ImVec2(x, y + 50), 0xFFFFFFFF, "AddText") + ImGui.End() + + if ARG_APP then ImGui.SetNextWindowPos(ImVec2(650, 10), G.ImGuiCond.Always); ImGui.SetNextWindowSize(ImVec2(620, 740), G.ImGuiCond.Always); G.ShowExampleAppCustomRendering(true) end + ImGui.Render() + G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData()) + if frame == ARG_FRAMES - 1 then + local screen = composite(game:GetService("Players").LocalPlayer.PlayerGui:FindFirstChild("ImGui")) + for y = 0, H - 1 do + local t = table.create(W * 3) + for i = 0, W * 3 - 1 do t[i + 1] = string.format("%02x", buffer.readu8(screen, y * W * 3 + i)) end + print("ROW", y, table.concat(t)) + end + end +end) diff --git a/tools/test/shot.py b/tools/test/shot.py new file mode 100755 index 0000000..260691f --- /dev/null +++ b/tools/test/shot.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +"""Headless screenshot: tools/test/shot.py out.png [--app] (runs scripts/t_shot.luau, writes a PNG; stdlib only)""" +import os, subprocess, sys, struct, zlib +HERE = os.path.dirname(os.path.abspath(__file__)) +env = dict(os.environ, FULL_OUTPUT="1", RUN_FLAGS="ARG_APP=true" if "--app" in sys.argv else "") +out = subprocess.run([sys.executable, os.path.join(HERE, "run.py"), "--quiet", "--roblox", "--frames", "30", "--timeout", "600", + "--main", os.path.join(HERE, "scripts", "t_shot.luau")], env=env, capture_output=True, text=True).stdout +rows = {int(l.split()[1]): bytes.fromhex(l.split()[2]) for l in out.splitlines() if l.startswith("ROW")} +assert rows, out[-2000:] +w, h = len(rows[0]) // 3, len(rows) +raw = b"".join(b"\0" + rows[y] for y in range(h)) +chunk = lambda t, d: struct.pack(">I", len(d)) + t + d + struct.pack(">I", zlib.crc32(t + d) & 0xffffffff) +open(sys.argv[1], "wb").write(b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0)) + chunk(b"IDAT", zlib.compress(raw, 6)) + chunk(b"IEND", b"")) +print("wrote", sys.argv[1], w, h)