28 Commits
Author SHA1 Message Date
4DBug c1696c799f 0.3.0
Build & Release / build (push) Waiting to run
2026-10-15 11:04:45 -05:00
4DBug 447230e688 CI/releases: drop the nodemo bundles (--no-demo build flag kept); remove examples/example_shared; Roblox backend RenderRate defaults to 60 (0 = every frame); README + TODO.txt updated 2026-10-15 10:57:00 -05:00
4DBug a1aa5cbb2d Fix: long TextUnformatted (>2000 bytes) rendered each visible line to the end of the text (RenderText end was nil, upstream passes line_end); PushFont() saved the new font instead of the current one, so PopFont() never restored it (font preview leaked into all later text); t_fixes covers both 2026-10-15 10:54:56 -05:00
4DBug dad1a0c0f1 Port leftovers: BringWindowToDisplayBehind, ImGuiSizeCallbackData + CalcWindowSizeAfterConstraint, ErrorCheckUsingSetCursorPosToExtendParentBoundaries, PopFocusScope user error (+ SetCurrentWindow stack sizes fix), log text decorations (Button/Menu/TreeNode/Drag/Slider/InputText/Checkbox/Radio), DataTypeFormatString display, ImGuiDataType S64/U64 (upstream enum layout; doubles), sscanf %u/length modifiers, mouse arrays sized ImGuiMouseButton.COUNT, AddCallback userdata copy; TODO.txt refreshed 2026-10-15 10:51:20 -05:00
4DBug b71e62b4d9 Integration: debug/context fields in ImGuiContext ctor, ImGuiContextHookType, DebugFlashStyleColorIdx=COUNT, CurrentPixelDensity, clipper range/data helpers, full Shutdown cleanup, settings dates (ImGuiSettingsCleanupArgs, CleanupIniSettings, LastUsed=, Platform_SessionDate) + metrics Settings section, ImGuiStyleVar SeparatorSize/DockingSeparatorSize (fixes off-by-one style var table), LogRenderedText restored in RenderText*, combo preview 1.93, InputScalar ValidatedThisFrame, ImGuiListClipperFlags/LogFlags/OldColumnFlags, ImGuiKey digit aliases, backend names 2026-10-15 09:39:17 -05:00
4DBug 92dbd9a580 compare_upstream: skip IM_MSVC_RUNTIME_CHECKS_*/template<> when naming structs; [=] status for intentional Lua replacements (ImGuiStorage, ImStr*/ImFormatString*, allocators, C++ container internals, sizeof/ABI helpers...) with evidence regexes, reported separately 2026-10-15 09:39:17 -05:00
4DBug e3840a938a Port debug tools (item picker, flash style color, debug locate, DebugDraw*, keyboard preview, debug break, DrawCmd mesh view), context hooks, SetContextName, AddInputCharactersUTF8, sanity checks, window resize ids, Register/UnregisterUserTexture, SetPixelDensity, TestShortcutRouting, DemoMarker; InputTextState selection API (fixes undo crash, callbacks get 0-based positions), ShortcutsForCancel (Esc/right-click reverts drags/sliders), ImParseFormatSanitizeForPrinting, ImDrawList CloneOutput/PrimQuadUV/AddImageQuad, ImDrawData DeIndexAllBuffers/ScaleClipRects, internal helpers (ImBitVector, ImGuiPackedDate, inline ImGui getters...); t_debugtools, t_helpers 2026-10-15 08:52:30 -05:00
4DBug 9be05aeb5c Fonts: ImFontGlyphRangesBuilder + GetGlyphRangesDefault, ImFont:CalcWordWrapPosition, stb_decompress + Decode85 + AddFontFromMemoryCompressed(Base85)TTF, atlas helpers (RemoveFont, RemoveCustomRect, AddRemapCodepoint (remap never triggered before), IsGlyphRangeUnused, texture status/format names...); t_fonts 2026-10-15 08:05:51 -05:00
4DBug f2c694bac5 Port ImTriangulator + upstream AddConcavePolyFilled (AA fringe, no per-triangle seams); [Luau] handles CCW winding and duplicate/collinear points that upstream mis-triangulates 2026-10-15 07:43:21 -05:00
4DBug 93a3c4faf5 Library draw call sites match 1.93: AddLineH/AddLineV + StrokeCenterBiased/StrokeInside (window/menu borders, separators, text cursor), tab border PathStroke, check mark AALineEnds, CloseButton/TreeNode lines/ColorButton/alpha checkerboard 1.93, Ctrl+Tab highlight AddRect arg order, metrics AA push/pop 2026-10-15 07:24:13 -05:00
4DBug 9872d43fd6 Draw list 1.93: baked round corners in the font atlas (ImFontAtlasBuildUpdateTexDataCorners, fill radii 1..16 + 3 stroke thicknesses) used by AddRect/AddRectFilled when pixel-aligned (_AddRectBaked/_AddRectFilledBaked) 2026-10-15 06:55:57 -05:00
4DBug af2026113a 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) 2026-10-15 06:37:49 -05:00
4DBug a111187591 Draw list 1.93 foundation (no output change): ImDrawFlags 1.93 values (Round*/Stroke*/AA*/JoinMiter/CapSquare/TextNoPixelSnap/UseTexForRoundCorners...; RoundCorners* kept as obsolete aliases), ImDrawList.Flags + PushDrawFlag/PopDrawFlag/_SetPixelDensity, SetupDrawListSharedData 1.93, style.CurveTessellationMaxError/AntiAliasedLineEnds, _AddPolyline entry point 2026-10-15 05:13:58 -05:00
4DBug 1e63710e33 compare_upstream: match XxxMT:Method and Class_Method globals to C++ class members (demo 100% ported by name) 2026-10-15 04:35:23 -05:00
4DBug d18bc714fc Demo: Selection State & Multi-Select section (ExampleSelectionWithDeletion, ExampleDualListBox, all 11 sub-demos) and Assets Browser ported; fix ImFloor() for negative non-integers (was floor-1) 2026-10-15 04:13:40 -05:00
4DBug 049e160852 Port multi-select, box-select and typing-select (BeginMultiSelect/EndMultiSelect, SetNextItemSelectionUserData, MultiSelectItemHeader/Footer, BeginBoxSelect/EndBoxSelect, GetTypingSelectRequest/TypingSelectFind*, ImGuiSelectionBasicStorage/ExternalStorage, Metrics nodes); wired into Selectable/Checkbox/TreeNode/clipper/tables; t_multiselect 2026-10-15 03:49:53 -05:00
4DBug 33ab5a1d84 Fix: InputTextMultiline crashed when drawn (InputTextLineIndexBuild/line offsets 0-based like upstream, inverted buf_display_end), InputText LiveEdit flag check; Shortcut()/IsKeyChordPressed() upstream defaults; FrameCount starts at 0 (IsWindowAppearing on first frame); missing ImGuiIO config fields with upstream defaults (+ ConfigDebugBeginReturnValueOnce/Loop); ColorButton drag source + ColorEdit4 drop target, IMGUI_PAYLOAD_TYPE_COLOR_3F/4F; t_fixes covers them 2026-10-15 03:03:10 -05:00
4DBug 9c8c1b018e Demo: re-ported imgui_demo.cpp line by line in upstream order (one function per upstream function, same labels/HelpMarkers/comments, 176/186 IMGUI_DEMO_MARKERs; port table driver/Sub()/Demo* helpers removed, statics in S.<Function>). New: DemoWindowInputs (Inputs & Focus), Image Viewer, ShowExampleAppDockSpaceBasic. Selection/Assets Browser stubbed until multi-select is ported. ImColor helper (imgui_h.lua). PORT_STATUS demo similarity 29% -> 51% 2026-10-15 02:32:58 -05:00
4DBug b929de5206 Fix: ImDrawList:PushClipRectFullScreen() was missing (drop target outside the window clip rect crashed); CalcItemSize() no longer writes to the caller's size (by value like upstream; fixes BeginTable UserOuterSize), scalar _CalcItemSizeXY for internal callers; t_fixes covers both 2026-10-15 02:05:43 -05:00
4DBug ac4398ee2a tools/test: check_all.sh (CI mirror + all scripted tests + demo forced open + LÖVE), hashes.sh (draw checksums vs a git rev), t_libhash (library-only draw checksum); regenerate PORT_STATUS 2026-10-15 02:03:40 -05:00
4DBug ff86ea5aa2 0.2.0
Build & Release / build (push) Waiting to run
2026-10-15 01:06:11 -05:00
4DBug 4ac57cb571 Roblox backend: redraw budget is opt-in (SetRedrawBudget default 0 = every changed tile drawn the same frame) 2026-10-15 00:55:43 -05:00
4DBug 15dc766d82 bundle.py: build-time Luau optimizations (draw-data identical; IMGUI_BUNDLE_NOOPT=1 disables): hot library globals as bundle locals, enum/constant folding (values dumped by the luau CLI; IMGUI_BUNDLE_STRICT=1 in CI), --@hoist pure helpers to inlinable bundle-level local functions, statement IM_ASSERT -> assert; builtin type instead of ImGui._GetTypeFunc() alias (GETGLOBAL 15903 -> 540) 2026-10-15 00:23:20 -05:00
4DBug ed53d0683e Library perf (draw-data identical): layout core (no ImVec2 __index in ItemSize, inlined ID/style/clip stacks, scalar ItemAdd/IsClippedEx/CalcItemSize/GetColorU32), text + widgets (scalar TextEx/ButtonEx/Checkbox/Slider/Drag/Selectable/TreeNode, word-wrap memo), tables (in-place clip rects, no per-row/cell temporaries, @native cell functions, scalar ClipWith), draw list (PrimRectXY, scalar AddRect/AddRectFilled/AddLine/PathRect/arcs) 2026-10-14 23:27:23 -05:00
4DBug b71e29ee35 Roblox backend perf (pixel-identical): redraw budget (SetRedrawBudget, default 1.5 ms; RenderRate default 0), exact in-place draw list compare + whole-pixel move fast path, partial tile redraws, flat rects hashed by in-tile rect, quads hashed by TL/BR, no full redraw on texture updates, @native CopyTexturePixels, reused partial upload buffers; imgui_draw: @native word wrap, ImStrByte/classifier inlined, text size cache covers wrapped text; t_pixels pixel oracle; run.py RUN_FLAGS 2026-10-14 22:26:38 -05:00
4DBug bcf383a079 tools/compare_upstream.py: port coverage + code similarity vs upstream, writes docs/PORT_STATUS.txt 2026-10-14 22:26:38 -05:00
4DBug ce82582e37 0.1.4
Build & Release / build (push) Canceled after 0s
2026-10-14 10:32:10 -05:00
4DBug 090525076f InputText: fix clipboard paste (filter was inverted, then off-by-one in the filtered buffer); ImParseFormatTrimDecorations: return [start, end) like upstream (Ctrl+click temp input showed decorations / crashed on "%d%%"); ShrinkWidths: tie order by Index descending like upstream; t_fixes test 2026-10-14 10:32:10 -05:00
45 changed files with 70479 additions and 34935 deletions
+5 -6
View File
@@ -116,7 +116,7 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
### Getting Started & Integration (Roblox) ### Getting Started & Integration (Roblox)
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/imgui-luau@0.1.3"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source: With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/imgui-luau@0.3.0"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source:
1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped. 1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped.
2. Sync with [Rojo](https://rojo.space): [`default.project.json`](/default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`. 2. Sync with [Rojo](https://rojo.space): [`default.project.json`](/default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`.
@@ -151,7 +151,8 @@ Backend options:
| --- | --- | | --- | --- |
| `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). | | `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). |
| `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. | | `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. |
| `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default 60, `0` = every frame). Your code still runs every frame. | | `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default `60`; `0` = every frame). Your code still runs every frame. |
| `ImGui_ImplRoblox.SetRedrawBudget(ms)` | Max milliseconds per frame spent redrawing changed tiles (opt-in; default `0` = no limit, every change is drawn the same frame). With a budget, tiles over it keep their old pixels for a few frames, so big changes are spread over frames instead of one long frame (parts of the UI may update a frame or two apart). |
| `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). | | `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). |
| `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. | | `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. |
| `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. | | `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. |
@@ -178,7 +179,7 @@ The cost is CPU time in Luau, so the backend is built around **not drawing**:
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers. 3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked. 4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer. 5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
6. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped. 6. **Redraw budget.** Changed tiles are redrawn in the frame they change. Optionally, `SetRedrawBudget(ms)` caps the redraw time per frame; tiles over it keep their old pixels and follow on the next frames. Redraws are capped at 60 per second by default (`SetRenderRate(hz)`, `0` = uncapped), optionally lower while busy with `SetBusyRenderRate(hz)`. Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too). **Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
@@ -204,12 +205,10 @@ love examples/example_love
Roblox stays the primary target: the Roblox build is untouched by this (no shims, `@native` kept); the LÖVE build strips `@native` and adds small `bit32`/`buffer`/`utf8`/`table` shims (the COMPAT section of [backends/imgui_impl_love.lua](/backends/imgui_impl_love.lua)). `tools/test/love/run.sh` checks that LuaJIT produces exactly the same draw data as Luau. Roblox stays the primary target: the Roblox build is untouched by this (no shims, `@native` kept); the LÖVE build strips `@native` and adds small `bit32`/`buffer`/`utf8`/`table` shims (the COMPAT section of [backends/imgui_impl_love.lua](/backends/imgui_impl_love.lua)). `tools/test/love/run.sh` checks that LuaJIT produces exactly the same draw data as Luau.
[examples/example_shared/app.lua](/examples/example_shared/app.lua) is one UI that runs unchanged on both: `roblox.client.luau` (Rojo: `examples/example_shared/default.project.json`) and `main.lua` (`love examples/example_shared`) are the only platform code. Write shared UI code in plain Lua 5.1 syntax (no `+=`, `//`, `continue`).
#### Prebuilt releases #### Prebuilt releases
Every push to `master` publishes a rolling `latest` release, and every `v*` tag a versioned release ([.github/workflows/release.yml](/.github/workflows/release.yml)), with: Every push to `master` publishes a rolling `latest` release, and every `v*` tag a versioned release ([.github/workflows/release.yml](/.github/workflows/release.yml)), with:
`ImGui.luau` / `ImGui-nodemo.luau` (Roblox ModuleScript), `imgui_love.lua` / `imgui_love-nodemo.lua` (LÖVE), and `imgui-luau-roblox-*.zip` / `imgui-luau-love-*.zip` (bundle + examples). `ImGui.luau` (Roblox ModuleScript), `imgui_love.lua` (LÖVE), their minified `.min` variants, and `imgui-luau-roblox-*.zip` / `imgui-luau-love-*.zip` (bundle + examples). For a library-only build without the demo, build it yourself with `--no-demo`.
### Differences from C++ Dear ImGui ### Differences from C++ Dear ImGui
+7 -15
View File
@@ -30,37 +30,33 @@ jobs:
unzip -q luau.zip -d luau && chmod +x luau/* && echo "$PWD/luau" >> "$GITHUB_PATH" unzip -q luau.zip -d luau && chmod +x luau/* && echo "$PWD/luau" >> "$GITHUB_PATH"
- name: Build - name: Build
env:
IMGUI_BUNDLE_STRICT: "1" # fail if enum folding (needs the luau CLI above) can't run
run: | run: |
mkdir -p dist mkdir -p dist
python3 tools/bundle.py dist/roblox # ImGui.luau (library + demo) python3 tools/bundle.py dist/roblox # ImGui.luau (library + demo)
python3 tools/bundle.py --no-demo dist/roblox-nodemo # ImGui.luau (library only)
python3 tools/bundle.py --target love dist/love # imgui_love.lua (library + demo) python3 tools/bundle.py --target love dist/love # imgui_love.lua (library + demo)
python3 tools/bundle.py --target love --no-demo dist/love-nodemo
# minified variants (comments/whitespace stripped, same code): tools/minify.py # minified variants (comments/whitespace stripped, same code): tools/minify.py
python3 tools/minify.py dist/roblox/ImGui.luau dist/ImGui.min.luau python3 tools/minify.py dist/roblox/ImGui.luau dist/ImGui.min.luau
python3 tools/minify.py dist/roblox-nodemo/ImGui.luau dist/ImGui-nodemo.min.luau
python3 tools/minify.py dist/love/imgui_love.lua dist/imgui_love.min.lua python3 tools/minify.py dist/love/imgui_love.lua dist/imgui_love.min.lua
python3 tools/minify.py dist/love-nodemo/imgui_love.lua dist/imgui_love-nodemo.min.lua
- name: Check - name: Check
run: | run: |
python3 tools/sync_github_readme.py --check python3 tools/sync_github_readme.py --check
luau-compile --null dist/roblox/ImGui.luau luau-compile --null dist/roblox/ImGui.luau
luau-compile --null dist/roblox-nodemo/ImGui.luau
sed 's|require(script:WaitForChild("Impl"))|nil|' dist/roblox/ImGuiTyped.luau > /tmp/ImGuiTyped.luau && luau-analyze /tmp/ImGuiTyped.luau sed 's|require(script:WaitForChild("Impl"))|nil|' dist/roblox/ImGuiTyped.luau > /tmp/ImGuiTyped.luau && luau-analyze /tmp/ImGuiTyped.luau
luajit -bl dist/love/imgui_love.lua > /dev/null luajit -bl dist/love/imgui_love.lua > /dev/null
luajit -bl dist/love-nodemo/imgui_love.lua > /dev/null
python3 tools/minify.py --test python3 tools/minify.py --test
luau-compile --null dist/ImGui.min.luau luau-compile --null dist/ImGui.min.luau
luau-compile --null dist/ImGui-nodemo.min.luau
luajit -bl dist/imgui_love.min.lua > /dev/null luajit -bl dist/imgui_love.min.lua > /dev/null
luajit -bl dist/imgui_love-nodemo.min.lua > /dev/null
python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/r.txt python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/r.txt
grep -q "README OK" /tmp/r.txt grep -q "README OK" /tmp/r.txt
python3 tools/test/run.py --main tools/test/scripts/t_cached_window.luau --frames 130 --quiet --roblox --timeout 300 | tee /tmp/c.txt python3 tools/test/run.py --main tools/test/scripts/t_cached_window.luau --frames 130 --quiet --roblox --timeout 300 | tee /tmp/c.txt
grep -q "CACHED OK" /tmp/c.txt grep -q "CACHED OK" /tmp/c.txt
MINIFY=1 python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/rm.txt MINIFY=1 python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/rm.txt
grep -q "README OK" /tmp/rm.txt grep -q "README OK" /tmp/rm.txt
python3 tools/test/run.py --main tools/test/scripts/t_fixes.luau --frames 55 --quiet --roblox --timeout 300 | tee /tmp/f.txt
grep -q "FIXES OK" /tmp/f.txt
python3 tools/bundle.py --target love >/dev/null python3 tools/bundle.py --target love >/dev/null
luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic | tee /tmp/h.txt luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic | tee /tmp/h.txt
cp build/imgui_love.lua /tmp/imgui_love.lua && cp dist/imgui_love.min.lua build/imgui_love.lua cp build/imgui_love.lua /tmp/imgui_love.lua && cp dist/imgui_love.min.lua build/imgui_love.lua
@@ -74,16 +70,14 @@ jobs:
V="${GITHUB_REF_NAME}" V="${GITHUB_REF_NAME}"
cd dist cd dist
cp roblox/ImGui.luau ImGui.luau cp roblox/ImGui.luau ImGui.luau
cp roblox-nodemo/ImGui.luau ImGui-nodemo.luau
cp love/imgui_love.lua imgui_love.lua cp love/imgui_love.lua imgui_love.lua
cp love-nodemo/imgui_love.lua imgui_love-nodemo.lua
# Roblox: module + example LocalScript + Rojo project # Roblox: module + example LocalScript + Rojo project
mkdir -p pkg-roblox/build && cp ImGui.luau roblox/ImGuiTyped.luau pkg-roblox/build/ && cp ../default.project.json pkg-roblox/ mkdir -p pkg-roblox/build && cp ImGui.luau roblox/ImGuiTyped.luau pkg-roblox/build/ && cp ../default.project.json pkg-roblox/
mkdir -p pkg-roblox/examples && cp -r ../examples/example_roblox ../examples/example_shared pkg-roblox/examples/ mkdir -p pkg-roblox/examples && cp -r ../examples/example_roblox pkg-roblox/examples/
(cd pkg-roblox && zip -qr ../imgui-luau-roblox-$V.zip .) (cd pkg-roblox && zip -qr ../imgui-luau-roblox-$V.zip .)
# LÖVE: bundle + fonts + examples (run: love examples/example_love) # LÖVE: bundle + fonts + examples (run: love examples/example_love)
mkdir -p pkg-love/build && cp imgui_love.lua pkg-love/build/ && cp -r ../fonts pkg-love/ && rm -f pkg-love/fonts/*.lua mkdir -p pkg-love/build && cp imgui_love.lua pkg-love/build/ && cp -r ../fonts pkg-love/ && rm -f pkg-love/fonts/*.lua
mkdir -p pkg-love/examples && cp -r ../examples/example_love ../examples/example_shared pkg-love/examples/ mkdir -p pkg-love/examples && cp -r ../examples/example_love pkg-love/examples/
(cd pkg-love && zip -qr ../imgui-luau-love-$V.zip .) (cd pkg-love && zip -qr ../imgui-luau-love-$V.zip .)
ls -la ls -la
@@ -93,9 +87,7 @@ jobs:
with: with:
files: | files: |
dist/ImGui.luau dist/ImGui.luau
dist/ImGui-nodemo.luau
dist/imgui_love.lua dist/imgui_love.lua
dist/imgui_love-nodemo.lua
dist/*.min.luau dist/*.min.luau
dist/*.min.lua dist/*.min.lua
dist/*.zip dist/*.zip
@@ -123,4 +115,4 @@ jobs:
gh release delete latest --yes --cleanup-tag || true gh release delete latest --yes --cleanup-tag || true
gh release create latest --latest --title "Latest build (${GITHUB_SHA::7})" \ gh release create latest --latest --title "Latest build (${GITHUB_SHA::7})" \
--notes "Automatic build of ${GITHUB_SHA} on ${GITHUB_REF_NAME}. Tag a commit v* for a versioned release." \ --notes "Automatic build of ${GITHUB_SHA} on ${GITHUB_REF_NAME}. Tag a commit v* for a versioned release." \
dist/ImGui.luau dist/ImGui-nodemo.luau dist/imgui_love.lua dist/imgui_love-nodemo.lua dist/*.min.luau dist/*.min.lua dist/*.zip dist/ImGui.luau dist/imgui_love.lua dist/*.min.luau dist/*.min.lua dist/*.zip
+5 -6
View File
@@ -113,7 +113,7 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
### Getting Started & Integration (Roblox) ### Getting Started & Integration (Roblox)
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/imgui-luau@0.1.3"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source: With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/imgui-luau@0.3.0"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source:
1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped. 1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped.
2. Sync with [Rojo](https://rojo.space): [`default.project.json`](default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`. 2. Sync with [Rojo](https://rojo.space): [`default.project.json`](default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`.
@@ -148,7 +148,8 @@ Backend options:
| --- | --- | | --- | --- |
| `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). | | `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). |
| `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. | | `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. |
| `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default 60, `0` = every frame). Your code still runs every frame. | | `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default `60`; `0` = every frame). Your code still runs every frame. |
| `ImGui_ImplRoblox.SetRedrawBudget(ms)` | Max milliseconds per frame spent redrawing changed tiles (opt-in; default `0` = no limit, every change is drawn the same frame). With a budget, tiles over it keep their old pixels for a few frames, so big changes are spread over frames instead of one long frame (parts of the UI may update a frame or two apart). |
| `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). | | `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). |
| `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. | | `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. |
| `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. | | `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. |
@@ -175,7 +176,7 @@ The cost is CPU time in Luau, so the backend is built around **not drawing**:
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers. 3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked. 4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer. 5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
6. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped. 6. **Redraw budget.** Changed tiles are redrawn in the frame they change. Optionally, `SetRedrawBudget(ms)` caps the redraw time per frame; tiles over it keep their old pixels and follow on the next frames. Redraws are capped at 60 per second by default (`SetRenderRate(hz)`, `0` = uncapped), optionally lower while busy with `SetBusyRenderRate(hz)`. Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too). **Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
@@ -201,12 +202,10 @@ love examples/example_love
Roblox stays the primary target: the Roblox build is untouched by this (no shims, `@native` kept); the LÖVE build strips `@native` and adds small `bit32`/`buffer`/`utf8`/`table` shims (the COMPAT section of [backends/imgui_impl_love.lua](backends/imgui_impl_love.lua)). `tools/test/love/run.sh` checks that LuaJIT produces exactly the same draw data as Luau. Roblox stays the primary target: the Roblox build is untouched by this (no shims, `@native` kept); the LÖVE build strips `@native` and adds small `bit32`/`buffer`/`utf8`/`table` shims (the COMPAT section of [backends/imgui_impl_love.lua](backends/imgui_impl_love.lua)). `tools/test/love/run.sh` checks that LuaJIT produces exactly the same draw data as Luau.
[examples/example_shared/app.lua](examples/example_shared/app.lua) is one UI that runs unchanged on both: `roblox.client.luau` (Rojo: `examples/example_shared/default.project.json`) and `main.lua` (`love examples/example_shared`) are the only platform code. Write shared UI code in plain Lua 5.1 syntax (no `+=`, `//`, `continue`).
#### Prebuilt releases #### Prebuilt releases
Every push to `master` publishes a rolling `latest` release, and every `v*` tag a versioned release ([.github/workflows/release.yml](.github/workflows/release.yml)), with: Every push to `master` publishes a rolling `latest` release, and every `v*` tag a versioned release ([.github/workflows/release.yml](.github/workflows/release.yml)), with:
`ImGui.luau` / `ImGui-nodemo.luau` (Roblox ModuleScript), `imgui_love.lua` / `imgui_love-nodemo.lua` (LÖVE), and `imgui-luau-roblox-*.zip` / `imgui-luau-love-*.zip` (bundle + examples). `ImGui.luau` (Roblox ModuleScript), `imgui_love.lua` (LÖVE), their minified `.min` variants, and `imgui-luau-roblox-*.zip` / `imgui-luau-love-*.zip` (bundle + examples). For a library-only build without the demo, build it yourself with `--no-demo`.
### Differences from C++ Dear ImGui ### Differences from C++ Dear ImGui
+4
View File
@@ -431,6 +431,8 @@ function ImGui_ImplLOVE_Init(opts)
local io = ImGui.GetIO() local io = ImGui.GetIO()
local bd = { Time = love.timer.getTime(), Root = opts.root or "", Textures = {}, TextureCount = 0, Meshes = {}, FrameNo = 0 } local bd = { Time = love.timer.getTime(), Root = opts.root or "", Textures = {}, TextureCount = 0, Meshes = {}, FrameNo = 0 }
io.BackendPlatformUserData = bd io.BackendPlatformUserData = bd
io.BackendPlatformName = "imgui_impl_love"
io.BackendRendererName = "imgui_impl_love"
io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset, ImGuiBackendFlags.HasMouseCursors) io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset, ImGuiBackendFlags.HasMouseCursors)
local platform_io = ImGui.GetPlatformIO() local platform_io = ImGui.GetPlatformIO()
@@ -449,6 +451,8 @@ function ImGui_ImplLOVE_Shutdown()
for _, m in pairs(bd.Meshes) do m.Mesh:release() end for _, m in pairs(bd.Meshes) do m.Mesh:release() end
for _, t in pairs(bd.Textures) do t:release() end for _, t in pairs(bd.Textures) do t:release() end
io.BackendPlatformUserData = nil io.BackendPlatformUserData = nil
io.BackendPlatformName = nil
io.BackendRendererName = nil
io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor(ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset, ImGuiBackendFlags.HasMouseCursors))) io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor(ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset, ImGuiBackendFlags.HasMouseCursors)))
end end
+359 -59
View File
@@ -142,6 +142,7 @@ end
--------------------------------------------------------- ---------------------------------------------------------
-- Copies a region of ImTextureData pixels (1-based byte table) into our RGBA8 buffer -- Copies a region of ImTextureData pixels (1-based byte table) into our RGBA8 buffer
@native
local function CopyTexturePixels(tex, dst, x, y, w, h) local function CopyTexturePixels(tex, dst, x, y, w, h)
local bpp, tex_w = tex.BytesPerPixel, tex.Width local bpp, tex_w = tex.BytesPerPixel, tex.Width
for row = 0, h - 1 do for row = 0, h - 1 do
@@ -208,6 +209,8 @@ end
-- Blending is non-premultiplied "over" with a transparent (alpha 0) background, so the game stays visible behind ImGui. -- Blending is non-premultiplied "over" with a transparent (alpha 0) background, so the game stays visible behind ImGui.
local buf, buf_w -- buffer/width of the tile being drawn local buf, buf_w -- buffer/width of the tile being drawn
local rect_cols = table.create(256, 0) -- scratch: texel column offsets for DrawRect
local sub_bufs, n_sub_bufs = {}, 0 -- scratch: partial tile upload buffers by size
local TILE_SIZE -- Vector2, set below local TILE_SIZE -- Vector2, set below
-- Blends one pixel (r, g, b, a are 0..255). One u32 read/write, one division in the general case. -- Blends one pixel (r, g, b, a are 0..255). One u32 read/write, one division in the general case.
@@ -337,18 +340,30 @@ local function DrawRect(x0, y0, x1, y1, u0, v0, u1, v1, col, t, cx0, cy0, cx1, c
local du, dv = (u1 - u0) / (x1 - x0), (v1 - v0) / (y1 - y0) local du, dv = (u1 - u0) / (x1 - x0), (v1 - v0) / (y1 - y0)
local tw, th, tbuf = t.Width, t.Height, t.Buffer local tw, th, tbuf = t.Width, t.Height, t.Buffer
local r255, g255, b255, a255 = r / 255, g / 255, b / 255, a / 255 local r255, g255, b255, a255 = r / 255, g / 255, b / 255, a / 255
-- The texel column only depends on x: compute it once per column instead of once per pixel
local ncol = px1 - px0
local cols = rect_cols
for c = 1, ncol do
local tx = floor((u0 + (px0 + c - 1 + 0.5 - x0) * du) * tw)
if tx < 0 then tx = 0 elseif tx >= tw then tx = tw - 1 end
cols[c] = tx * 4
end
for y = py0, py1 - 1 do for y = py0, py1 - 1 do
local ty = floor((v0 + (y + 0.5 - y0) * dv) * th) local ty = floor((v0 + (y + 0.5 - y0) * dv) * th)
if ty < 0 then ty = 0 elseif ty >= th then ty = th - 1 end if ty < 0 then ty = 0 elseif ty >= th then ty = th - 1 end
local trow = ty * tw local trow4 = ty * tw * 4
local o = (y * buf_w + px0) * 4 local o = (y * buf_w + px0) * 4
for x = px0, px1 - 1 do for c = 1, ncol do
local tx = floor((u0 + (x + 0.5 - x0) * du) * tw) local to = trow4 + cols[c]
if tx < 0 then tx = 0 elseif tx >= tw then tx = tw - 1 end
local to = (trow + tx) * 4
local ta = readu8(tbuf, to + 3) local ta = readu8(tbuf, to + 3)
if ta > 0 then if ta > 0 then
BlendPixel(o, readu8(tbuf, to) * r255, readu8(tbuf, to + 1) * g255, readu8(tbuf, to + 2) * b255, ta * a255) local sa = ta * a255
local sr, sg, sb = readu8(tbuf, to) * r255, readu8(tbuf, to + 1) * g255, readu8(tbuf, to + 2) * b255
if sa >= 255 then -- (BlendPixel's opaque case, inlined: most glyph texels)
writeu32(buf, o, floor(sr + 0.5) + floor(sg + 0.5) * 256 + floor(sb + 0.5) * 65536 + 4278190080)
else
BlendPixel(o, sr, sg, sb, sa)
end
end end
o = o + 4 o = o + 4
end end
@@ -442,6 +457,7 @@ local P1, P2 = 2147483647, 2147483629 -- two independent 31 bit hashes per tile
local KEY_W = 64 -- tile key = ty * KEY_W + tx (layers up to 16384 px wide) local KEY_W = 64 -- tile key = ty * KEY_W + tx (layers up to 16384 px wide)
local CLIP_PAD = 2048 -- content up to this far off screen is kept, so dragging a window past the edge doesn't redraw it local CLIP_PAD = 2048 -- content up to this far off screen is kept, so dragging a window past the edge doesn't redraw it
local LAYER_KEEP_FRAMES = 120 -- unused layers are destroyed after this many rendered frames local LAYER_KEEP_FRAMES = 120 -- unused layers are destroyed after this many rendered frames
local MAX_DEFER_FRAMES = 8 -- a tile is redrawn at the latest this many frames after it changed, even over the redraw budget
-- Primitive arrays (reused between frames, no per-frame allocation) -- Primitive arrays (reused between frames, no per-frame allocation)
local pr_a, pr_b, pr_c, pr_d, pr_t, pr_g = {}, {}, {}, {}, {}, {} -- vertices (pr_d == false -> triangle), texture, gradient kind local pr_a, pr_b, pr_c, pr_d, pr_t, pr_g = {}, {}, {}, {}, {}, {} -- vertices (pr_d == false -> triangle), texture, gradient kind
@@ -451,6 +467,7 @@ local pr_tex = {} -- texture id
local pr_vb = {} -- vertex buffer (draw list VtxBuffer.Buf) local pr_vb = {} -- vertex buffer (draw list VtxBuffer.Buf)
local pr_k = {} -- layer kind (1..3) local pr_k = {} -- layer kind (1..3)
local plist1, plist2, plist3 = {}, {}, {} -- primitive indices per layer kind local plist1, plist2, plist3 = {}, {}, {} -- primitive indices per layer kind
local cm_free, cm_kind, cm_x0, cm_y0, cm_x1, cm_y1 = {}, {}, {}, {}, {}, {} -- per draw command (collect -> Snapshot)
@native @native
local function HashVertex(h1, h2, vbuf, v, ox, oy) local function HashVertex(h1, h2, vbuf, v, ox, oy)
@@ -540,7 +557,7 @@ local function GetLayer(bd, draw_list, kind)
else else
frame = NewFrame(bd.Gui, false); root = frame frame = NewFrame(bd.Gui, false); root = frame
end end
layer = { Root = root, Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, TX = {}, TY = {}, Touched = {}, NTouched = 0, layer = { Root = root, Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, TL1 = {}, TL2 = {}, TLF = {}, TX = {}, TY = {}, Touched = {}, NTouched = 0,
Visible = false, X = false, Y = false, Z = -1, CX0 = false, CY0 = false, CX1 = false, CY1 = false } Visible = false, X = false, Y = false, Z = -1, CX0 = false, CY0 = false, CX1 = false, CY1 = false }
set[kind] = layer set[kind] = layer
return layer return layer
@@ -567,7 +584,8 @@ local function GetTile(bd, layer, key, tx, ty)
label.Visible = false label.Visible = false
label.Active = bd.TilesActive label.Active = bd.TilesActive
label.Parent = layer.Frame label.Parent = layer.Frame
tile = { Image = img, Label = label, Buffer = buffer.create(TILE * TILE * 4), H1 = -1, H2 = -1, Visible = false, BX0 = TILE, BY0 = TILE, BX1 = 0, BY1 = 0 } tile = { Image = img, Label = label, Buffer = buffer.create(TILE * TILE * 4), H1 = -1, H2 = -1, Visible = false, BX0 = TILE, BY0 = TILE, BX1 = 0, BY1 = 0,
ON = -1, OH1 = {}, OH2 = {}, OF = {}, OB0 = {}, OB1 = {}, OB2 = {}, OB3 = {} } -- last drawn primitive list (hashes, bounds in tile)
layer.Tiles[key] = tile layer.Tiles[key] = tile
return tile return tile
end end
@@ -587,31 +605,129 @@ local function DestroyTiles(bd)
bd.Layers = {} bd.Layers = {}
end end
-- Exact signature of everything in a draw list that affects its layers (nil = can't skip: user callbacks) -- Snapshot of everything in a draw list that affects its layers, compared exactly with the next frame's list:
local sig_buf = buffer.create(4096) -- 1 = identical (only restack), 2 = everything moved by the same whole pixels (dx, dy) (only move the layers: a window
-- being dragged), 0 = changed (full path). Compared in place (no strings built): early exit at the first difference.
local writef64 = buffer.writef64
local CMD_STRIDE = 70 -- clip (4 f64), ElemCount, IdxOffset, VtxOffset (u32), tex id (f64), kind (u8), free (u8), bounds of its primitives (4 x i32)
@native @native
local function DrawListSignature(draw_list, ox, oy) local function Snapshot(set, draw_list, ox, oy, sw, sh)
local nv = draw_list.VtxBuffer.Size * VTX_STRIDE
local idx, ni = draw_list.IdxBuffer.Data, draw_list.IdxBuffer.Size local idx, ni = draw_list.IdxBuffer.Data, draw_list.IdxBuffer.Size
local ncmd = draw_list.CmdBuffer.Size local ncmd = draw_list.CmdBuffer.Size
local need = ni * 4 + ncmd * 64 + 64 local sv, si, sc = set.SV, set.SI, set.SC
if buffer.len(sig_buf) < need then sig_buf = buffer.create(need * 2) end if not sv or buffer.len(sv) < nv then sv = buffer.create(max(nv, 64) * 2); set.SV = sv end
local b, o = sig_buf, 0 if not si or buffer.len(si) < ni * 4 then si = buffer.create(max(ni, 16) * 8); set.SI = si end
for i = 1, ni do writeu32(b, o, idx[i]); o += 4 end if not sc or buffer.len(sc) < ncmd * CMD_STRIDE then sc = buffer.create(max(ncmd, 4) * CMD_STRIDE * 2); set.SC = sc end
buffer.copy(sv, 0, draw_list.VtxBuffer.Buf, 0, nv)
for i = 1, ni do writeu32(si, (i - 1) * 4, idx[i]) end
local o, ci = 0, 0
for _, pcmd in draw_list.CmdBuffer:iter() do for _, pcmd in draw_list.CmdBuffer:iter() do
if pcmd.UserCallback ~= nil then return nil end if pcmd.UserCallback ~= nil then set.SnapOK = false; return end
local c = pcmd.ClipRect local c = pcmd.ClipRect
buffer.writef64(b, o, c.x); buffer.writef64(b, o + 8, c.y); buffer.writef64(b, o + 16, c.z); buffer.writef64(b, o + 24, c.w) writef64(sc, o, c.x); writef64(sc, o + 8, c.y); writef64(sc, o + 16, c.z); writef64(sc, o + 24, c.w)
writeu32(b, o + 32, pcmd.ElemCount); writeu32(b, o + 36, pcmd.IdxOffset); writeu32(b, o + 40, pcmd.VtxOffset) writeu32(sc, o + 32, pcmd.ElemCount); writeu32(sc, o + 36, pcmd.IdxOffset); writeu32(sc, o + 40, pcmd.VtxOffset)
buffer.writef64(b, o + 44, pcmd:GetTexID()) writef64(sc, o + 44, pcmd:GetTexID())
o += 52 ci += 1
buffer.writeu8(sc, o + 52, cm_kind[ci] or 0); buffer.writeu8(sc, o + 53, cm_free[ci] and 1 or 0)
if cm_free[ci] then
buffer.writei32(sc, o + 54, cm_x0[ci]); buffer.writei32(sc, o + 58, cm_y0[ci]); buffer.writei32(sc, o + 62, cm_x1[ci]); buffer.writei32(sc, o + 66, cm_y1[ci])
end
o += CMD_STRIDE
end
set.SVN, set.SIN, set.SCN = nv, ni, ncmd
set.SOX, set.SOY, set.SSW, set.SSH = ox, oy, sw, sh
set.SCC = draw_list._ContentClipX0 ~= nil
set.SCX0, set.SCY0, set.SCX1, set.SCY1 = draw_list._ContentClipX0 or 0, draw_list._ContentClipY0 or 0, draw_list._ContentClipX1 or 0, draw_list._ContentClipY1 or 0
set.SSX, set.SSY = draw_list._ScrollOriginX or 0, draw_list._ScrollOriginY or 0
set.SnapOK = true
end
-- a + d == b exactly, with no rounding in a + d (so every later "x - origin" gives the same result as before the move)
@native
local function ShiftedExact(a, d, b)
local s = a + d
if s ~= b then return false end
local bb = s - a
return (a - (s - bb)) + (d - bb) == 0
end
@native
local function CompareDrawList(set, draw_list, ox, oy, sw, sh)
if not set or not set.SnapOK then return 0 end
local nv = draw_list.VtxBuffer.Size * VTX_STRIDE
local ni, ncmd = draw_list.IdxBuffer.Size, draw_list.CmdBuffer.Size
if nv ~= set.SVN or ni ~= set.SIN or ncmd ~= set.SCN or ox ~= set.SOX or oy ~= set.SOY or sw ~= set.SSW or sh ~= set.SSH then return 0 end
if (draw_list._ContentClipX0 ~= nil) ~= set.SCC then return 0 end
local vb, sv = draw_list.VtxBuffer.Buf, set.SV
if nv == 0 then return 1 end
-- translation from the first vertex: whole pixels only, and only with DisplayPos at 0 (so it stays exact)
local dx, dy = readf64(vb, 0) - readf64(sv, 0), readf64(vb, 8) - readf64(sv, 8)
if dx ~= dx or dy ~= dy then return 0 end
if dx ~= 0 or dy ~= 0 then
if dx ~= floor(dx) or dy ~= floor(dy) or ox ~= 0 or oy ~= 0 or not set.SLimFree then return 0 end
-- everything must stay as far from the clip limits as before (nothing newly clamped)
if set.SEX0 + dx < -CLIP_PAD or set.SEY0 + dy < -CLIP_PAD or set.SEX1 + dx > sw + CLIP_PAD or set.SEY1 + dy > sh + CLIP_PAD then return 0 end
end
local idx, si = draw_list.IdxBuffer.Data, set.SI
for i = 1, ni do if idx[i] ~= readu32(si, (i - 1) * 4) then return 0 end end
local sc, o = set.SC, 0
local ccx0, ccy0, ccx1, ccy1 = draw_list._ContentClipX0, draw_list._ContentClipY0, draw_list._ContentClipX1, draw_list._ContentClipY1
local seen_content = false
for _, pcmd in draw_list.CmdBuffer:iter() do
if pcmd.UserCallback ~= nil then return 0 end
if pcmd.ElemCount ~= readu32(sc, o + 32) or pcmd.IdxOffset ~= readu32(sc, o + 36) or pcmd.VtxOffset ~= readu32(sc, o + 40) or pcmd:GetTexID() ~= readf64(sc, o + 44) then return 0 end
local c = pcmd.ClipRect
if dx == 0 and dy == 0 then
if c.x ~= readf64(sc, o) or c.y ~= readf64(sc, o + 8) or c.z ~= readf64(sc, o + 16) or c.w ~= readf64(sc, o + 24) then return 0 end
elseif not (ShiftedExact(readf64(sc, o), dx, c.x) and ShiftedExact(readf64(sc, o + 8), dy, c.y)
and ShiftedExact(readf64(sc, o + 16), dx, c.z) and ShiftedExact(readf64(sc, o + 24), dy, c.w)) then
-- An unchanged clip rect (e.g. the viewport) is fine if it clipped none of this command's primitives before
-- the move and still clips none after it
if c.x ~= readf64(sc, o) or c.y ~= readf64(sc, o + 8) or c.z ~= readf64(sc, o + 16) or c.w ~= readf64(sc, o + 24)
or buffer.readu8(sc, o + 53) == 0 then return 0 end
if buffer.readi32(sc, o + 54) + dx < floor(c.x) or buffer.readi32(sc, o + 58) + dy < floor(c.y)
or buffer.readi32(sc, o + 62) + dx > floor(c.z) or buffer.readi32(sc, o + 66) + dy > floor(c.w) then return 0 end
end
-- same layer kind as before (same rule as the collect pass)
if pcmd.ElemCount > 0 then
local kind = 1
if ccx0 and c.x >= ccx0 and c.y >= ccy0 and c.z <= ccx1 and c.w <= ccy1 then kind = 2; seen_content = true
elseif seen_content then kind = 3 end
if kind ~= buffer.readu8(sc, o + 52) then return 0 end
end
o += CMD_STRIDE
end
if dx == 0 and dy == 0 then
if set.SCC and (draw_list._ContentClipX0 ~= set.SCX0 or draw_list._ContentClipY0 ~= set.SCY0 or draw_list._ContentClipX1 ~= set.SCX1
or draw_list._ContentClipY1 ~= set.SCY1 or draw_list._ScrollOriginX ~= set.SSX or draw_list._ScrollOriginY ~= set.SSY) then return 0 end
for v = 0, nv - VTX_STRIDE, VTX_STRIDE do
if readf64(vb, v) ~= readf64(sv, v) or readf64(vb, v + 8) ~= readf64(sv, v + 8) or readf64(vb, v + 16) ~= readf64(sv, v + 16)
or readf64(vb, v + 24) ~= readf64(sv, v + 24) or readu32(vb, v + 32) ~= readu32(sv, v + 32) then return 0 end
end
return 1
end
if set.SCC and not (ShiftedExact(set.SCX0, dx, draw_list._ContentClipX0) and ShiftedExact(set.SCY0, dy, draw_list._ContentClipY0)
and ShiftedExact(set.SCX1, dx, draw_list._ContentClipX1) and ShiftedExact(set.SCY1, dy, draw_list._ContentClipY1)
and ShiftedExact(set.SSX, dx, draw_list._ScrollOriginX) and ShiftedExact(set.SSY, dy, draw_list._ScrollOriginY)) then return 0 end
for v = 0, nv - VTX_STRIDE, VTX_STRIDE do
if not ShiftedExact(readf64(sv, v), dx, readf64(vb, v)) or not ShiftedExact(readf64(sv, v + 8), dy, readf64(vb, v + 8))
or readf64(vb, v + 16) ~= readf64(sv, v + 16) or readf64(vb, v + 24) ~= readf64(sv, v + 24) or readu32(vb, v + 32) ~= readu32(sv, v + 32) then return 0 end
end
return 2, dx, dy
end
-- A window moved by whole pixels: its tiles are unchanged, only the layer frames move
local function ShiftLayer(layer, dx, dy, scale)
layer.X += dx; layer.Y += dy
local root = layer.Root
if root ~= layer.Frame then
layer.CX0 += dx; layer.CY0 += dy; layer.CX1 += dx; layer.CY1 += dy
root.Position = UDim2.fromOffset(layer.CX0 / scale, layer.CY0 / scale)
else
root.Position = UDim2.fromOffset(layer.X / scale, layer.Y / scale)
end end
buffer.writef64(b, o, ox); buffer.writef64(b, o + 8, oy)
buffer.writef64(b, o + 16, draw_list._ContentClipX0 or -1); buffer.writef64(b, o + 24, draw_list._ContentClipY0 or -1)
buffer.writef64(b, o + 32, draw_list._ContentClipX1 or -1); buffer.writef64(b, o + 40, draw_list._ContentClipY1 or -1)
buffer.writef64(b, o + 48, draw_list._ContentClipX0 and draw_list._ScrollOriginX or -1)
buffer.writef64(b, o + 56, draw_list._ContentClipX0 and draw_list._ScrollOriginY or -1)
o += 64
return readstring(draw_list.VtxBuffer.Buf, 0, draw_list.VtxBuffer.Size * VTX_STRIDE) .. readstring(b, 0, o)
end end
-- Overlays: user Instances (e.g. a Frame holding a ViewportFrame) placed inside a window, registered every frame with -- Overlays: user Instances (e.g. a Frame holding a ViewportFrame) placed inside a window, registered every frame with
@@ -690,9 +806,11 @@ local function PE() if profiling then profileend() end end
@native @native
local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_x0, clip_y0, clip_x1, clip_y1) local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_x0, clip_y0, clip_x1, clip_y1)
local scale = bd.Scale local scale = bd.Scale
local deferred = false
-- Pass 2: hash primitives into layer-relative tiles -- Pass 2: hash primitives into layer-relative tiles
PB("ImGui tile hash") PB("ImGui tile hash")
local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched
local tl1, tl2, tlf = layer.TL1, layer.TL2, layer.TLF -- per-tile list of the primitives' hashes (for partial redraws)
for k = 1, layer.NTouched do tn[touched[k]] = 0 end for k = 1, layer.NTouched do tn[touched[k]] = 0 end
local nt = 0 local nt = 0
for pi = 1, np do for pi = 1, np do
@@ -701,23 +819,55 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
-- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative) -- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative)
-- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved. -- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved.
local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0 local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0
local h1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1
local h2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
local vax, vay = lx0 + ox, ly0 + oy
local vbuf = pr_vb[p] local vbuf = pr_vb[p]
h1, h2 = HashVertex(h1, h2, vbuf, pr_a[p], vax, vay) local va, vd = pr_a[p], pr_d[p]
-- Flat rect (single colour, no gradient, constant uv): its pixels in a tile are exactly its clipped pixel rect
-- inside that tile filled with one colour, so hash that per tile instead of the vertices. Resizing a window
-- then only changes the tiles its edges cross, not every tile its background covers.
local flat_rect, rcol, ruv = false, 0, 0
if vd and not pr_g[p] then
local u0, v0 = readf64(vbuf, va + 16), readf64(vbuf, va + 24)
local vc = pr_c[p]
if not pr_t[p] or (u0 == readf64(vbuf, vc + 16) and v0 == readf64(vbuf, vc + 24)) then
flat_rect, rcol, ruv = true, readu32(vbuf, va + 32), u0 * 8192 + v0
end
end
local h1, h2 = 0, 0
local fsig = -1 -- flat rect: colour/uv/texture without the geometry (-1: not a flat rect)
if flat_rect then fsig = ((rcol * 8191 + ruv * 4096) % P1 * 64 + tex_id) % P1 end
if not flat_rect then
h1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1
h2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
local vax, vay = lx0 + ox, ly0 + oy
if vd then
-- Axis aligned quad: the collect pass checked that TR/BL (positions, uvs, colours) follow from TL/BR
-- (and the gradient kind), so TL + BR + kind determine its pixels
h1, h2 = HashVertex(h1, h2, vbuf, va, vax, vay)
h1, h2 = HashVertex(h1, h2, vbuf, pr_c[p], vax, vay)
local gk = pr_g[p]
if gk then h1 = (h1 * 48271 + gk) % P1; h2 = (h2 * 16807 + gk * 3) % P2 end
else
h1, h2 = HashVertex(h1, h2, vbuf, va, vax, vay)
h1, h2 = HashVertex(h1, h2, vbuf, pr_b[p], vax, vay) h1, h2 = HashVertex(h1, h2, vbuf, pr_b[p], vax, vay)
h1, h2 = HashVertex(h1, h2, vbuf, pr_c[p], vax, vay) h1, h2 = HashVertex(h1, h2, vbuf, pr_c[p], vax, vay)
local vd = pr_d[p] end
if vd then h1, h2 = HashVertex(h1, h2, vbuf, vd, vax, vay) end end
for ty = floor((pr_by0[p] - ly0) / TILE), floor((pr_by1[p] - 1 - ly0) / TILE) do for ty = floor((pr_by0[p] - ly0) / TILE), floor((pr_by1[p] - 1 - ly0) / TILE) do
for tx = floor((pr_bx0[p] - lx0) / TILE), floor((pr_bx1[p] - 1 - lx0) / TILE) do for tx = floor((pr_bx0[p] - lx0) / TILE), floor((pr_bx1[p] - 1 - lx0) / TILE) do
if flat_rect then
local tx0, ty0 = tx * TILE, ty * TILE
local ix0, iy0 = max(cx0 - tx0, 0), max(cy0 - ty0, 0)
local ix1, iy1 = min(cx1 - tx0, TILE), min(cy1 - ty0, TILE)
local r = ((ix0 * 131 + iy0) * 131 + ix1) * 131 + iy1
h1 = ((r * 48271 + rcol) % P1 * 48271 + ruv * 4096 + 7) % P1
h2 = ((r * 16807 + rcol + tex_id) % P2 * 16807 + ruv * 65521 + 11) % P2
end
local key = ty * KEY_W + tx local key = ty * KEY_W + tx
local k = tn[key] local k = tn[key]
if not k or k == 0 then if not k or k == 0 then
k = 0 k = 0
th1[key], th2[key] = 0, 0 th1[key], th2[key] = 0, 0
if not tl[key] then tl[key] = {} end if not tl[key] then tl[key] = {}; tl1[key] = {}; tl2[key] = {}; tlf[key] = {} end
nt = nt + 1 nt = nt + 1
touched[nt] = key touched[nt] = key
layer.TX[key], layer.TY[key] = tx, ty layer.TX[key], layer.TY[key] = tx, ty
@@ -725,6 +875,7 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
k = k + 1 k = k + 1
tn[key] = k tn[key] = k
tl[key][k] = p tl[key][k] = p
tl1[key][k], tl2[key][k], tlf[key][k] = h1, h2, fsig
th1[key] = (th1[key] * 48271 + h1) % P1 th1[key] = (th1[key] * 48271 + h1) % P1
th2[key] = (th2[key] * 16807 + h2 + k) % P2 th2[key] = (th2[key] * 16807 + h2 + k) % P2
end end
@@ -758,7 +909,7 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
PB("ImGui raster+upload") PB("ImGui raster+upload")
for key, tile in pairs(layer.Tiles) do for key, tile in pairs(layer.Tiles) do
if not tn[key] or tn[key] == 0 then if not tn[key] or tn[key] == 0 then
if tile.Visible then tile.Visible = false; tile.Label.Visible = false; tile.H1 = -1 end if tile.Visible then tile.Visible = false; tile.Label.Visible = false; tile.H1 = -1; tile.ON = -1 end
end end
end end
for kk = 1, nt do for kk = 1, nt do
@@ -766,14 +917,27 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
local count = tn[key] local count = tn[key]
local tx, ty = layer.TX[key], layer.TY[key] local tx, ty = layer.TX[key], layer.TY[key]
local tile = GetTile(bd, layer, key, tx, ty) local tile = GetTile(bd, layer, key, tx, ty)
if force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key] then local dirty = force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key]
if dirty and bd.RedrawStarted and (tile.Deferred or 0) < MAX_DEFER_FRAMES
and ((bd.RedrawBudget > 0 and os.clock() - bd.FrameT0 >= bd.RedrawBudget) or (bd.TileLimit and bd.FrameTiles >= bd.TileLimit)) then -- (TileLimit: test hook, tools/test/scripts/t_pixels.luau)
-- Over this frame's redraw budget: keep showing the old pixels, redraw on a following frame. The tile's
-- stored hashes/list still describe what it shows, so the next comparison is against the real content.
tile.Deferred = (tile.Deferred or 0) + 1
if force then tile.H1 = -1; tile.ON = -1 end -- full redraw later (force doesn't persist)
deferred = true
dirty = false
end
if dirty then
bd.RedrawStarted = true
bd.FrameTiles += 1
tile.Deferred = 0
tile.H1, tile.H2 = th1[key], th2[key] tile.H1, tile.H2 = th1[key], th2[key]
local x0, y0 = lx0 + tx * TILE, ly0 + ty * TILE local x0, y0 = lx0 + tx * TILE, ly0 + ty * TILE
local x1, y1 = x0 + TILE, y0 + TILE local x1, y1 = x0 + TILE, y0 + TILE
buf, buf_w = tile.Buffer, TILE buf, buf_w = tile.Buffer, TILE
local list = tl[key] local list, lh1, lh2 = tl[key], tl1[key], tl2[key]
-- Dirty region = old content bounds + new content bounds. Outside it the tile is already local oh1, oh2, ob0, ob1, ob2, ob3, on = tile.OH1, tile.OH2, tile.OB0, tile.OB1, tile.OB2, tile.OB3, tile.ON
-- transparent, so only this region is cleared and uploaded (edge tiles are often mostly empty). -- New content bounds (union of the primitives' bounds in this tile)
local nx0, ny0, nx1, ny1 = TILE, TILE, 0, 0 local nx0, ny0, nx1, ny1 = TILE, TILE, 0, 0
for k = 1, count do for k = 1, count do
local p = list[k] local p = list[k]
@@ -785,8 +949,63 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
if d > ny1 then ny1 = d end if d > ny1 then ny1 = d end
end end
nx0, ny0, nx1, ny1 = max(nx0, 0), max(ny0, 0), min(nx1, TILE), min(ny1, TILE) nx0, ny0, nx1, ny1 = max(nx0, 0), max(ny0, 0), min(nx1, TILE), min(ny1, TILE)
local rx0, ry0, rx1, ry1 = min(nx0, tile.BX0), min(ny0, tile.BY0), max(nx1, tile.BX1), max(ny1, tile.BY1) local rx0, ry0, rx1, ry1
if force or on < 0 then
-- Dirty region = old content bounds + new content bounds. Outside it the tile is already
-- transparent, so only this region is cleared and uploaded (edge tiles are often mostly empty).
rx0, ry0, rx1, ry1 = min(nx0, tile.BX0), min(ny0, tile.BY0), max(nx1, tile.BX1), max(ny1, tile.BY1)
else
-- Partial redraw: the primitives before the first and after the last changed one are the same as last
-- time. A pixel that no changed primitive (old or new) touches is covered by the same primitives in the
-- same order, so it is already correct: only the bounds of the changed ones are cleared and redrawn.
local i, lim = 1, min(on, count)
while i <= lim and oh1[i] == lh1[i] and oh2[i] == lh2[i] do i += 1 end
local j = 0
while j <= lim - i and oh1[on - j] == lh1[count - j] and oh2[on - j] == lh2[count - j] do j += 1 end
rx0, ry0, rx1, ry1 = TILE, TILE, 0, 0
local of, lf = tile.OF, tlf[key]
local paired = on - j - i == count - j - i
for k = i, count - j do
local p = list[k]
local a, b, c, d = max(pr_bx0[p] - x0, 0), max(pr_by0[p] - y0, 0), min(pr_bx1[p] - x0, TILE), min(pr_by1[p] - y0, TILE)
local oa, ob, oc, od = ob0[k], ob1[k], ob2[k], ob3[k]
if paired and lf[k] >= 0 and lf[k] == of[k] then
-- Same flat rect (colour/uv/texture) with new bounds: pixels inside both are unchanged, only the
-- strips between the old and new edges can differ (e.g. a window background growing on resize)
local uy0, uy1 = min(b, ob), max(d, od)
local ux0, ux1 = min(a, oa), max(c, oc)
if a ~= oa then ux0 = min(a, oa); local e = max(a, oa); rx0 = min(rx0, ux0); rx1 = max(rx1, e); ry0 = min(ry0, uy0); ry1 = max(ry1, uy1) end
if c ~= oc then local e = min(c, oc); rx0 = min(rx0, e); rx1 = max(rx1, max(c, oc)); ry0 = min(ry0, uy0); ry1 = max(ry1, uy1) end
if b ~= ob then ry0 = min(ry0, min(b, ob)); ry1 = max(ry1, max(b, ob)); rx0 = min(rx0, ux0); rx1 = max(rx1, ux1) end
if d ~= od then ry0 = min(ry0, min(d, od)); ry1 = max(ry1, max(d, od)); rx0 = min(rx0, ux0); rx1 = max(rx1, ux1) end
else
if a < rx0 then rx0 = a end
if b < ry0 then ry0 = b end
if c > rx1 then rx1 = c end
if d > ry1 then ry1 = d end
if k <= on - j then
if oa < rx0 then rx0 = oa end
if ob < ry0 then ry0 = ob end
if oc > rx1 then rx1 = oc end
if od > ry1 then ry1 = od end
end
end
end
for k = count - j + 1, on - j do -- old middle longer than the new one
if ob0[k] < rx0 then rx0 = ob0[k] end
if ob1[k] < ry0 then ry0 = ob1[k] end
if ob2[k] > rx1 then rx1 = ob2[k] end
if ob3[k] > ry1 then ry1 = ob3[k] end
end
end
tile.BX0, tile.BY0, tile.BX1, tile.BY1 = nx0, ny0, nx1, ny1 tile.BX0, tile.BY0, tile.BX1, tile.BY1 = nx0, ny0, nx1, ny1
-- Remember this list for the next partial redraw
for k = 1, count do
local p = list[k]
oh1[k], oh2[k], tile.OF[k] = lh1[k], lh2[k], tlf[key][k]
ob0[k], ob1[k], ob2[k], ob3[k] = max(pr_bx0[p] - x0, 0), max(pr_by0[p] - y0, 0), min(pr_bx1[p] - x0, TILE), min(pr_by1[p] - y0, TILE)
end
tile.ON = count
local rw = rx1 - rx0 local rw = rx1 - rx0
if rw == TILE then if rw == TILE then
buffer.fill(buf, ry0 * TILE * 4, 0, (ry1 - ry0) * TILE * 4) buffer.fill(buf, ry0 * TILE * 4, 0, (ry1 - ry0) * TILE * 4)
@@ -796,8 +1015,10 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
local tox, toy = ox + x0, oy + y0 local tox, toy = ox + x0, oy + y0
for k = 1, count do for k = 1, count do
local p = list[k] local p = list[k]
local cx0, cy0 = max(pr_cx0[p], x0) - x0, max(pr_cy0[p], y0) - y0 -- clip to the dirty region (only those pixels are redrawn)
local cx1, cy1 = min(pr_cx1[p], x1) - x0, min(pr_cy1[p], y1) - y0 local cx0, cy0 = max(pr_cx0[p] - x0, rx0), max(pr_cy0[p] - y0, ry0)
local cx1, cy1 = min(pr_cx1[p] - x0, rx1), min(pr_cy1[p] - y0, ry1)
if cx0 >= cx1 or cy0 >= cy1 or pr_bx0[p] - x0 >= rx1 or pr_by0[p] - y0 >= ry1 or pr_bx1[p] - x0 <= rx0 or pr_by1[p] - y0 <= ry0 then continue end
local va, vc, vd, vbuf = pr_a[p], pr_c[p], pr_d[p], pr_vb[p] local va, vc, vd, vbuf = pr_a[p], pr_c[p], pr_d[p], pr_vb[p]
if vd then if vd then
local ax, ay, cx, cy = readf64(vbuf, va) - tox, readf64(vbuf, va + 8) - toy, readf64(vbuf, vc) - tox, readf64(vbuf, vc + 8) - toy local ax, ay, cx, cy = readf64(vbuf, va) - tox, readf64(vbuf, va + 8) - toy, readf64(vbuf, vc) - tox, readf64(vbuf, vc + 8) - toy
@@ -816,31 +1037,42 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
tile.Image:WritePixelsBuffer(Vector2.zero, TILE_SIZE, buf) tile.Image:WritePixelsBuffer(Vector2.zero, TILE_SIZE, buf)
elseif rw > 0 and ry1 > ry0 then elseif rw > 0 and ry1 > ry0 then
local rh = ry1 - ry0 local rh = ry1 - ry0
local sub = buffer.create(rw * rh * 4) local sk = rw * 1024 + rh -- partial upload buffers are reused (WritePixelsBuffer needs the exact size)
local sub = sub_bufs[sk]
if not sub then
if n_sub_bufs >= 256 then table.clear(sub_bufs); n_sub_bufs = 0 end
sub = buffer.create(rw * rh * 4); sub_bufs[sk] = sub; n_sub_bufs += 1
end
for y = 0, rh - 1 do buffer.copy(sub, y * rw * 4, buf, ((ry0 + y) * TILE + rx0) * 4, rw * 4) end for y = 0, rh - 1 do buffer.copy(sub, y * rw * 4, buf, ((ry0 + y) * TILE + rx0) * 4, rw * 4) end
tile.Image:WritePixelsBuffer(Vector2.new(rx0, ry0), Vector2.new(rw, rh), sub) tile.Image:WritePixelsBuffer(Vector2.new(rx0, ry0), Vector2.new(rw, rh), sub)
end end
PE() PE()
end end
if not tile.Visible then if not tile.Visible and (tile.Deferred or 0) == 0 then -- (a deferred tile that was hidden holds old pixels: stays hidden until drawn)
tile.Visible = true tile.Visible = true
tile.Label.Visible = true tile.Label.Visible = true
end end
end end
PE() PE()
return deferred
end end
@native @native
function ImGui_ImplRoblox_RenderDrawData(draw_data) function ImGui_ImplRoblox_RenderDrawData(draw_data)
local bd = ImGui_ImplRoblox_GetBackendData() local bd = ImGui_ImplRoblox_GetBackendData()
PB("ImGui RenderDrawData") PB("ImGui RenderDrawData")
bd.FrameT0 = os.clock()
bd.FrameTiles = 0
bd.RedrawStarted = false -- the first dirty tile of a frame is always redrawn (progress), the rest within the budget
bd.Stale = nil
PB("ImGui textures") PB("ImGui textures")
if draw_data.Textures ~= nil then if draw_data.Textures ~= nil then
for _, tex in draw_data.Textures:iter() do for _, tex in draw_data.Textures:iter() do
if tex.Status ~= ImTextureStatus.OK then if tex.Status ~= ImTextureStatus.OK then
-- No redraw needed for WantUpdates: it only writes texture portions which have never been used before
-- (new glyphs/rects), so no existing tile samples them. Created textures get a new (never reused) id.
ImGui_ImplRoblox_UpdateTexture(tex) ImGui_ImplRoblox_UpdateTexture(tex)
bd.ForceRedraw = true -- texture contents changed: tiles sampling it must be redrawn
end end
end end
end end
@@ -878,20 +1110,27 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
local n = 0 local n = 0
local z = 0 local z = 0
local any_deferred = false
for _, draw_list in draw_data.CmdLists:iter() do for _, draw_list in draw_data.CmdLists:iter() do
-- Unchanged draw list (same vertex bytes, indices, commands, origin and scroll): its layers are already -- Unchanged draw list (same vertices, indices, commands, origin and scroll): its layers are already correct,
-- correct, only restack them. Static windows then cost one byte compare instead of collect + hash. -- only restack them. Moved by whole pixels (a dragged window): only move its layers. Either way no collect + hash.
PB("ImGui unchanged check") PB("ImGui unchanged check")
local sig = DrawListSignature(draw_list, ox, oy)
local set = bd.Layers[draw_list] local set = bd.Layers[draw_list]
if sig and set and not force and set.Sig == sig then local same, sdx, sdy = 0, 0, 0
if not force then same, sdx, sdy = CompareDrawList(set, draw_list, ox, oy, sw, sh) end
if same ~= 0 then
for kind = 1, 3 do for kind = 1, 3 do
local layer = set[kind] local layer = set[kind]
if layer and layer.Visible then if layer and layer.Visible then
if same == 2 then ShiftLayer(layer, sdx, sdy, bd.Scale) end
z += 1 z += 1
if layer.Z ~= z then layer.Z = z; layer.Root.ZIndex = z end if layer.Z ~= z then layer.Z = z; layer.Root.ZIndex = z end
end end
end end
if same == 2 then
Snapshot(set, draw_list, ox, oy, sw, sh)
set.SEX0 += sdx; set.SEY0 += sdy; set.SEX1 += sdx; set.SEY1 += sdy
end
set.LastUsed = frame_no set.LastUsed = frame_no
z = PlaceOverlays(bd, draw_list, z, ox, oy) z = PlaceOverlays(bd, draw_list, z, ox, oy)
PE() PE()
@@ -909,13 +1148,30 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
content_x1, content_y1 = min(floor(ccx1 - ox), lim_x1), min(floor(ccy1 - oy), lim_y1) content_x1, content_y1 = min(floor(ccx1 - ox), lim_x1), min(floor(ccy1 - oy), lim_y1)
end end
local seen_content = false local seen_content = false
-- extents of everything clamped by the clip limits below (lim_free: nothing was), for the move fast path
local ex0, ey0, ex1, ey1 = math.huge, math.huge, -math.huge, -math.huge
local lim_free = true
if ccx0 then
local a, b, c, d = floor(ccx0 - ox), floor(ccy0 - oy), floor(ccx1 - ox), floor(ccy1 - oy)
if a < lim_x0 or b < lim_y0 or c > lim_x1 or d > lim_y1 then lim_free = false end
ex0, ey0, ex1, ey1 = min(ex0, a), min(ey0, b), max(ex1, c), max(ey1, d)
end
local ci = 0
for _, pcmd in draw_list.CmdBuffer:iter() do for _, pcmd in draw_list.CmdBuffer:iter() do
ci += 1
cm_free[ci], cm_kind[ci] = false, 0
if pcmd.UserCallback ~= nil then if pcmd.UserCallback ~= nil then
pcmd.UserCallback(draw_list, pcmd) pcmd.UserCallback(draw_list, pcmd)
elseif pcmd.ElemCount > 0 then elseif pcmd.ElemCount > 0 then
local clip = pcmd.ClipRect local clip = pcmd.ClipRect
local cx0, cy0 = max(floor(clip.x - ox), lim_x0), max(floor(clip.y - oy), lim_y0) local rcx0, rcy0, rcx1, rcy1 = floor(clip.x - ox), floor(clip.y - oy), floor(clip.z - ox), floor(clip.w - oy)
local cx1, cy1 = min(floor(clip.z - ox), lim_x1), min(floor(clip.w - oy), lim_y1) if rcx0 < lim_x0 or rcy0 < lim_y0 or rcx1 > lim_x1 or rcy1 > lim_y1 then lim_free = false end
if rcx0 < ex0 then ex0 = rcx0 end
if rcy0 < ey0 then ey0 = rcy0 end
if rcx1 > ex1 then ex1 = rcx1 end
if rcy1 > ey1 then ey1 = rcy1 end
local cx0, cy0 = max(rcx0, lim_x0), max(rcy0, lim_y0)
local cx1, cy1 = min(rcx1, lim_x1), min(rcy1, lim_y1)
local kind = 1 local kind = 1
if ccx0 and clip.x >= ccx0 and clip.y >= ccy0 and clip.z <= ccx1 and clip.w <= ccy1 then if ccx0 and clip.x >= ccx0 and clip.y >= ccy0 and clip.z <= ccx1 and clip.w <= ccy1 then
kind = 2; seen_content = true kind = 2; seen_content = true
@@ -926,6 +1182,8 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
elseif seen_content then elseif seen_content then
kind = 3 kind = 3
end end
cm_kind[ci] = kind
local cfree, cex0, cey0, cex1, cey1 = cx1 > cx0 and cy1 > cy0, math.huge, math.huge, -math.huge, -math.huge
if cx1 > cx0 and cy1 > cy0 then if cx1 > cx0 and cy1 > cy0 then
local tex_id = pcmd:GetTexID() local tex_id = pcmd:GetTexID()
local t = bd.Textures[tex_id] local t = bd.Textures[tex_id]
@@ -971,6 +1229,16 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
bx1, by1 = ceil(max(pax, pbx, pcx) - ox) + 1, ceil(max(pay, pby, pcy) - oy) + 1 bx1, by1 = ceil(max(pax, pbx, pcx) - ox) + 1, ceil(max(pay, pby, pcy) - oy) + 1
i = i + 3 i = i + 3
end end
if bx0 < cx0 or by0 < cy0 or bx1 > cx1 or by1 > cy1 then cfree = false end
if bx0 < cex0 then cex0 = bx0 end
if by0 < cey0 then cey0 = by0 end
if bx1 > cex1 then cex1 = bx1 end
if by1 > cey1 then cey1 = by1 end
if bx0 < ex0 then ex0 = bx0 end
if by0 < ey0 then ey0 = by0 end
if bx1 > ex1 then ex1 = bx1 end
if by1 > ey1 then ey1 = by1 end
if bx0 < lim_x0 or by0 < lim_y0 or bx1 > lim_x1 or by1 > lim_y1 then lim_free = false end
if bx0 < cx0 then bx0 = cx0 end if bx0 < cx0 then bx0 = cx0 end
if by0 < cy0 then by0 = cy0 end if by0 < cy0 then by0 = cy0 end
if bx1 > cx1 then bx1 = cx1 end if bx1 > cx1 then bx1 = cx1 end
@@ -984,12 +1252,16 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
end end
end end
end end
if cex0 > cex1 then cex0, cey0, cex1, cey1 = 0, 0, 0, 0 end -- (no primitives)
cm_free[ci] = cfree
cm_x0[ci], cm_y0[ci], cm_x1[ci], cm_y1[ci] = cex0, cey0, cex1, cey1
end end
end end
PE() PE()
-- Split into layer kinds and place each one -- Split into layer kinds and place each one
set = bd.Layers[draw_list] set = bd.Layers[draw_list]
local list_deferred = false
local nk1, nk2, nk3 = 0, 0, 0 local nk1, nk2, nk3 = 0, 0, 0
local o1x, o1y, o3x, o3y = math.huge, math.huge, math.huge, math.huge local o1x, o1y, o3x, o3y = math.huge, math.huge, math.huge, math.huge
for p = first, n do for p = first, n do
@@ -1008,19 +1280,28 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
end end
if nk1 > 0 then if nk1 > 0 then
z += 1 z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 1), plist1, nk1, o1x, o1y, ox, oy, force, z) if DrawLayer(bd, GetLayer(bd, draw_list, 1), plist1, nk1, o1x, o1y, ox, oy, force, z) then list_deferred = true end
elseif set and set[1] then HideLayer(set[1]) end elseif set and set[1] then HideLayer(set[1]) end
if nk2 > 0 then if nk2 > 0 then
z += 1 z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 2), plist2, nk2, floor(draw_list._ScrollOriginX - ox), floor(draw_list._ScrollOriginY - oy), ox, oy, force, z, if DrawLayer(bd, GetLayer(bd, draw_list, 2), plist2, nk2, floor(draw_list._ScrollOriginX - ox), floor(draw_list._ScrollOriginY - oy), ox, oy, force, z,
content_x0, content_y0, content_x1, content_y1) content_x0, content_y0, content_x1, content_y1) then list_deferred = true end
elseif set and set[2] then HideLayer(set[2]) end elseif set and set[2] then HideLayer(set[2]) end
if nk3 > 0 then if nk3 > 0 then
z += 1 z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 3), plist3, nk3, o3x, o3y, ox, oy, force, z) if DrawLayer(bd, GetLayer(bd, draw_list, 3), plist3, nk3, o3x, o3y, ox, oy, force, z) then list_deferred = true end
elseif set and set[3] then HideLayer(set[3]) end elseif set and set[3] then HideLayer(set[3]) end
set = bd.Layers[draw_list] set = bd.Layers[draw_list]
if set then set.LastUsed = frame_no; set.Sig = sig end -- (deferred tiles: not "unchanged" next frame even if the draw list is, so they get redrawn)
if set then
set.LastUsed = frame_no
if list_deferred then set.SnapOK = false else
Snapshot(set, draw_list, ox, oy, sw, sh)
set.SLimFree = lim_free
set.SEX0, set.SEY0, set.SEX1, set.SEY1 = ex0, ey0, ex1, ey1
end
end
if list_deferred then any_deferred = true end
z = PlaceOverlays(bd, draw_list, z, ox, oy) z = PlaceOverlays(bd, draw_list, z, ox, oy)
end end
@@ -1033,7 +1314,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
-- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips) -- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips)
for draw_list, set in pairs(bd.Layers) do for draw_list, set in pairs(bd.Layers) do
if set.LastUsed ~= frame_no then if set.LastUsed ~= frame_no then
set.Sig = nil -- hidden now: must go through the full path when shown again set.SnapOK = false -- hidden now: must go through the full path when shown again
for kind = 1, 3 do if set[kind] then HideLayer(set[kind]) end end for kind = 1, 3 do if set[kind] then HideLayer(set[kind]) end end
if frame_no - set.LastUsed > LAYER_KEEP_FRAMES then if frame_no - set.LastUsed > LAYER_KEEP_FRAMES then
DestroyLayerSet(set) DestroyLayerSet(set)
@@ -1045,6 +1326,8 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
-- drop references so old vertex tables can be collected -- drop references so old vertex tables can be collected
for k = n + 1, bd.LastPrimCount do pr_t[k], pr_vb[k] = nil, nil end for k = n + 1, bd.LastPrimCount do pr_t[k], pr_vb[k] = nil, nil end
bd.LastPrimCount = n bd.LastPrimCount = n
-- Tiles left for later: if the app doesn't submit a new frame, Heartbeat finishes them from this draw data
if any_deferred then bd.Stale = draw_data; bd.StaleHB = bd.HB end
PE() PE()
end end
@@ -1070,9 +1353,11 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
local bd = { local bd = {
Time = os.clock(), Gui = gui, Connections = {}, Time = os.clock(), Gui = gui, Connections = {},
Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1, Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1,
Layers = {}, Overlays = {}, OverlayHolders = {}, OverlayShown = {}, FrameNo = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 60, BusyRenderRate = 0, NextRender = 0, LastPrimCount = 0, ForceRedraw = true, Layers = {}, Overlays = {}, OverlayHolders = {}, OverlayShown = {}, FrameNo = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 60, BusyRenderRate = 0, RedrawBudget = 0, HB = 0, NextRender = 0, LastPrimCount = 0, ForceRedraw = true,
} }
io.BackendPlatformUserData = bd io.BackendPlatformUserData = bd
io.BackendPlatformName = "imgui_impl_roblox"
io.BackendRendererName = "imgui_impl_roblox"
io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset) io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset)
@@ -1137,7 +1422,10 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
if d and os.clock() + 0.002 >= bd.NextRender then if d and os.clock() + 0.002 >= bd.NextRender then
bd.Pending = nil bd.Pending = nil
ImGui_ImplRoblox_RenderDrawData(d) ImGui_ImplRoblox_RenderDrawData(d)
elseif bd.Stale and bd.HB > bd.StaleHB then -- deferred tiles of a frame not followed by a new one
ImGui_ImplRoblox_RenderDrawData(bd.Stale)
end end
bd.HB += 1
end) end)
connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end) connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end)
@@ -1189,6 +1477,8 @@ function ImGui_ImplRoblox_Shutdown()
bd.Gui:Destroy() bd.Gui:Destroy()
io.BackendPlatformUserData = nil io.BackendPlatformUserData = nil
io.BackendPlatformName = nil
io.BackendRendererName = nil
io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor( io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor(
ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasTextures,
ImGuiBackendFlags.RendererHasVtxOffset ImGuiBackendFlags.RendererHasVtxOffset
@@ -1200,6 +1490,7 @@ function ImGui_ImplRoblox_NewFrame()
local io = ImGui.GetIO() local io = ImGui.GetIO()
local bd = ImGui_ImplRoblox_GetBackendData() local bd = ImGui_ImplRoblox_GetBackendData()
if bd.Pending then bd.Pending = nil; table.clear(bd.Overlays) end -- skipped frame superseded (its draw lists get reset) if bd.Pending then bd.Pending = nil; table.clear(bd.Overlays) end -- skipped frame superseded (its draw lists get reset)
bd.Stale = nil
local screen = bd.Gui.AbsoluteSize local screen = bd.Gui.AbsoluteSize
local w, h = floor(screen.X * bd.Scale), floor(screen.Y * bd.Scale) local w, h = floor(screen.X * bd.Scale), floor(screen.Y * bd.Scale)
@@ -1306,7 +1597,15 @@ function ImGui_ImplRoblox_NewFrame()
PE() PE()
end end
--- Max tile redraws per second (default 60); 0 = every frame. imgui logic still runs every frame. --- Opt-in: max milliseconds per frame spent redrawing changed tiles (default 0 = no limit, every change is drawn the
--- same frame). With a budget (e.g. 1.5), tiles over it keep
--- their previous pixels and are redrawn on the following frames (each one at most a few frames late), so a big change
--- (resizing a large window, opening one, scrolling a long list) is spread over frames instead of one long frame.
function ImGui_ImplRoblox_SetRedrawBudget(ms)
ImGui_ImplRoblox_GetBackendData().RedrawBudget = (ms or 0) / 1000
end
--- Max tile redraws per second (default 60; 0 = every frame, limited by the redraw budget instead). imgui logic still runs every frame.
function ImGui_ImplRoblox_SetRenderRate(hz) function ImGui_ImplRoblox_SetRenderRate(hz)
ImGui_ImplRoblox_GetBackendData().RenderRate = hz or 0 ImGui_ImplRoblox_GetBackendData().RenderRate = hz or 0
end end
@@ -1336,6 +1635,7 @@ ImGui_ImplRoblox = {
AddFile = ImGui_ImplRoblox_AddFile, AddFile = ImGui_ImplRoblox_AddFile,
SetRenderScale = ImGui_ImplRoblox_SetRenderScale, SetRenderScale = ImGui_ImplRoblox_SetRenderScale,
SetRenderRate = ImGui_ImplRoblox_SetRenderRate, SetRenderRate = ImGui_ImplRoblox_SetRenderRate,
SetRedrawBudget = ImGui_ImplRoblox_SetRedrawBudget,
SetOverlay = ImGui_ImplRoblox_SetOverlay, SetOverlay = ImGui_ImplRoblox_SetOverlay,
SetTextProxy = ImGui_ImplRoblox_SetTextProxy, SetTextProxy = ImGui_ImplRoblox_SetTextProxy,
CreateTexture = ImGui_ImplRoblox_CreateTexture, CreateTexture = ImGui_ImplRoblox_CreateTexture,
+1 -1
View File
@@ -1 +1 @@
[[12, "imgui.lua"], [21288, "imgui_demo.lua"], [29706, "imgui_draw.lua"], [35476, "imgui_h.lua"], [38142, "imgui_impl_roblox.lua"], [39494, "imgui_impl_roblox_fonts.lua"], [45897, "imgui_internal.lua"], [49343, "imgui_tables.lua"], [54338, "imgui_widgets.lua"], [64949, "imstb_rectpack.lua"], [65613, "imstb_textedit.lua"], [67155, "imstb_truetype.lua"], [69943, "imstd_minstdio.lua"]] [[46, "imgui.lua"], [22675, "imgui_demo.lua"], [34533, "imgui_draw.lua"], [42860, "imgui_h.lua"], [45857, "imgui_impl_roblox.lua"], [47509, "imgui_impl_roblox_fonts.lua"], [53912, "imgui_internal.lua"], [57884, "imgui_tables.lua"], [62976, "imgui_widgets.lua"], [75210, "imstb_rectpack.lua"], [75874, "imstb_textedit.lua"], [77416, "imstb_truetype.lua"], [80204, "imstd_minstdio.lua"]]
+20898 -10507
View File
File diff suppressed because it is too large Load Diff
+77 -3
View File
@@ -6,6 +6,7 @@ export type ImVec4 = { x: number, y: number, z: number, w: number, [any]: any }
export type ImGuiAPI = { export type ImGuiAPI = {
AcceptDragDropPayload: (type: string, flags: number?) -> any, AcceptDragDropPayload: (type: string, flags: number?) -> any,
ActivateItemByID: (...any) -> ...any, ActivateItemByID: (...any) -> ...any,
AddContextHook: (...any) -> ...any,
AddDrawListToDrawDataEx: (...any) -> ...any, AddDrawListToDrawDataEx: (...any) -> ...any,
AddSettingsHandler: (...any) -> ...any, AddSettingsHandler: (...any) -> ...any,
AddUpdateViewport: (...any) -> ...any, AddUpdateViewport: (...any) -> ...any,
@@ -13,6 +14,7 @@ export type ImGuiAPI = {
ArrowButton: (str_id: string, dir: number) -> boolean, ArrowButton: (str_id: string, dir: number) -> boolean,
ArrowButtonEx: (...any) -> ...any, ArrowButtonEx: (...any) -> ...any,
Begin: (name: string, open: boolean?, flags: number?) -> (boolean, boolean?), Begin: (name: string, open: boolean?, flags: number?) -> (boolean, boolean?),
BeginBoxSelect: (...any) -> ...any,
BeginChild: (...any) -> ...any, BeginChild: (...any) -> ...any,
BeginChildEx: (...any) -> ...any, BeginChildEx: (...any) -> ...any,
BeginColumns: (...any) -> ...any, BeginColumns: (...any) -> ...any,
@@ -36,6 +38,7 @@ export type ImGuiAPI = {
BeginMenu: (label: string, enabled: boolean?) -> boolean, BeginMenu: (label: string, enabled: boolean?) -> boolean,
BeginMenuBar: () -> boolean, BeginMenuBar: () -> boolean,
BeginMenuEx: (...any) -> ...any, BeginMenuEx: (...any) -> ...any,
BeginMultiSelect: (flags: number, selection_size: number?, items_count: number?) -> any,
BeginPopup: (str_id: string, flags: number?) -> boolean, BeginPopup: (str_id: string, flags: number?) -> boolean,
BeginPopupContextItem: (str_id: string?, popup_flags: number?) -> boolean, BeginPopupContextItem: (str_id: string?, popup_flags: number?) -> boolean,
BeginPopupContextVoid: (str_id: string?, popup_flags: number?) -> boolean, BeginPopupContextVoid: (str_id: string?, popup_flags: number?) -> boolean,
@@ -70,6 +73,7 @@ export type ImGuiAPI = {
CalcTypematicRepeatAmount: (...any) -> ...any, CalcTypematicRepeatAmount: (...any) -> ...any,
CalcWindowNextAutoFitSize: (...any) -> ...any, CalcWindowNextAutoFitSize: (...any) -> ...any,
CalcWrapWidthForPos: (...any) -> ...any, CalcWrapWidthForPos: (...any) -> ...any,
CallContextHooks: (...any) -> ...any,
Checkbox: (label: string, v: boolean?) -> (boolean, boolean?), Checkbox: (label: string, v: boolean?) -> (boolean, boolean?),
CheckboxFlags: (...any) -> ...any, CheckboxFlags: (...any) -> ...any,
CleanupIniSettings: (...any) -> ...any, CleanupIniSettings: (...any) -> ...any,
@@ -109,26 +113,47 @@ export type ImGuiAPI = {
DataTypeGetInfo: (...any) -> ...any, DataTypeGetInfo: (...any) -> ...any,
DataTypeIsZero: (...any) -> ...any, DataTypeIsZero: (...any) -> ...any,
DebugAllocHook: (...any) -> ...any, DebugAllocHook: (...any) -> ...any,
DebugBreakButton: (...any) -> ...any,
DebugBreakButtonTooltip: (...any) -> ...any,
DebugBreakClearData: (...any) -> ...any,
DebugDrawCursorPos: (...any) -> ...any,
DebugDrawItemRect: (...any) -> ...any,
DebugDrawLineExtents: (...any) -> ...any,
DebugFlashStyleColor: (idx: number) -> (),
DebugHookIdInfo: (...any) -> ...any, DebugHookIdInfo: (...any) -> ...any,
DebugLocateItem: (...any) -> ...any,
DebugLocateItemOnHover: (...any) -> ...any,
DebugLocateItemResolveWithLastItem: (...any) -> ...any,
DebugLog: (fmt: string, ...any) -> (), DebugLog: (fmt: string, ...any) -> (),
DebugLogV: (fmt: string, ...any) -> (),
DebugNodeColumns: (...any) -> ...any, DebugNodeColumns: (...any) -> ...any,
DebugNodeDockNode: (...any) -> ...any, DebugNodeDockNode: (...any) -> ...any,
DebugNodeDrawCmdShowMeshAndBoundingBox: (...any) -> ...any,
DebugNodeDrawList: (...any) -> ...any, DebugNodeDrawList: (...any) -> ...any,
DebugNodeFont: (...any) -> ...any, DebugNodeFont: (...any) -> ...any,
DebugNodeFontGlyph: (...any) -> ...any, DebugNodeFontGlyph: (...any) -> ...any,
DebugNodeFontGlyphsForSrcMask: (...any) -> ...any, DebugNodeFontGlyphsForSrcMask: (...any) -> ...any,
DebugNodeInputTextState: (...any) -> ...any,
DebugNodeMultiSelectState: (...any) -> ...any,
DebugNodePlatformMonitor: (...any) -> ...any,
DebugNodeStorage: (...any) -> ...any, DebugNodeStorage: (...any) -> ...any,
DebugNodeTabBar: (...any) -> ...any, DebugNodeTabBar: (...any) -> ...any,
DebugNodeTable: (...any) -> ...any, DebugNodeTable: (...any) -> ...any,
DebugNodeTableSettings: (...any) -> ...any, DebugNodeTableSettings: (...any) -> ...any,
DebugNodeTexture: (...any) -> ...any, DebugNodeTexture: (...any) -> ...any,
DebugNodeTypingSelectState: (...any) -> ...any,
DebugNodeViewport: (...any) -> ...any, DebugNodeViewport: (...any) -> ...any,
DebugNodeWindow: (...any) -> ...any, DebugNodeWindow: (...any) -> ...any,
DebugNodeWindowSettings: (...any) -> ...any, DebugNodeWindowSettings: (...any) -> ...any,
DebugNodeWindowsList: (...any) -> ...any, DebugNodeWindowsList: (...any) -> ...any,
DebugNodeWindowsListByBeginStackParent: (...any) -> ...any, DebugNodeWindowsListByBeginStackParent: (...any) -> ...any,
DebugRenderKeyboardPreview: (...any) -> ...any,
DebugRenderViewportThumbnail: (...any) -> ...any, DebugRenderViewportThumbnail: (...any) -> ...any,
DebugStartItemPicker: () -> (),
DebugTextEncoding: (str: string) -> (), DebugTextEncoding: (str: string) -> (),
DebugTextUnformattedWithLocateItem: (...any) -> ...any,
DebugTextureIDToU64: (...any) -> ...any,
DemoMarker: (...any) -> ...any,
DestroyContext: (ctx: any) -> ...any, DestroyContext: (ctx: any) -> ...any,
DestroyPlatformWindow: (...any) -> ...any, DestroyPlatformWindow: (...any) -> ...any,
DestroyPlatformWindows: () -> (), DestroyPlatformWindows: () -> (),
@@ -220,6 +245,7 @@ export type ImGuiAPI = {
DragScalarN: (label: string, data_type: number, v: any, components: number, v_speed: number?, v_min: any, v_max: any, format: string?, flags: number?) -> ...any, DragScalarN: (label: string, data_type: number, v: any, components: number, v_speed: number?, v_min: any, v_max: any, format: string?, flags: number?) -> ...any,
Dummy: (size: ImVec2) -> (), Dummy: (size: ImVec2) -> (),
End: () -> (), End: () -> (),
EndBoxSelect: (...any) -> ...any,
EndChild: () -> (), EndChild: () -> (),
EndColumns: (...any) -> ...any, EndColumns: (...any) -> ...any,
EndCombo: () -> (), EndCombo: () -> (),
@@ -235,12 +261,15 @@ export type ImGuiAPI = {
EndMainMenuBar: () -> (), EndMainMenuBar: () -> (),
EndMenu: () -> (), EndMenu: () -> (),
EndMenuBar: () -> (), EndMenuBar: () -> (),
EndMultiSelect: () -> any,
EndPopup: () -> (), EndPopup: () -> (),
EndTabBar: () -> (), EndTabBar: () -> (),
EndTabItem: () -> (), EndTabItem: () -> (),
EndTable: () -> (), EndTable: () -> (),
EndTooltip: () -> (), EndTooltip: () -> (),
ErrorCheckEndFrameFinalizeErrorTooltip: (...any) -> ...any, ErrorCheckEndFrameFinalizeErrorTooltip: (...any) -> ...any,
ErrorCheckEndFrameSanityChecks: (...any) -> ...any,
ErrorCheckNewFrameSanityChecks: (...any) -> ...any,
ErrorCheckUsingSetCursorPosToExtendParentBoundaries: (...any) -> ...any, ErrorCheckUsingSetCursorPosToExtendParentBoundaries: (...any) -> ...any,
ErrorLog: (...any) -> ...any, ErrorLog: (...any) -> ...any,
ErrorRecoveryStoreState: (...any) -> ...any, ErrorRecoveryStoreState: (...any) -> ...any,
@@ -259,6 +288,7 @@ export type ImGuiAPI = {
FindRenderedTextEnd: (...any) -> ...any, FindRenderedTextEnd: (...any) -> ...any,
FindSettingsHandler: (...any) -> ...any, FindSettingsHandler: (...any) -> ...any,
FindViewportByID: (viewport_id: number) -> any, FindViewportByID: (viewport_id: number) -> any,
FindViewportByPlatformHandle: (platform_handle: any) -> ...any,
FindWindowByID: (...any) -> ...any, FindWindowByID: (...any) -> ...any,
FindWindowByName: (...any) -> ...any, FindWindowByName: (...any) -> ...any,
FindWindowDisplayIndex: (...any) -> ...any, FindWindowDisplayIndex: (...any) -> ...any,
@@ -273,7 +303,9 @@ export type ImGuiAPI = {
GcAwakeTransientWindowBuffers: (...any) -> ...any, GcAwakeTransientWindowBuffers: (...any) -> ...any,
GcCompactTransientMiscBuffers: (...any) -> ...any, GcCompactTransientMiscBuffers: (...any) -> ...any,
GcCompactTransientWindowBuffers: (...any) -> ...any, GcCompactTransientWindowBuffers: (...any) -> ...any,
GetActiveID: (...any) -> ...any,
GetBackgroundDrawList: (viewport: any) -> ...any, GetBackgroundDrawList: (viewport: any) -> ...any,
GetBoxSelectState: (...any) -> ...any,
GetClipboardText: () -> any, GetClipboardText: () -> any,
GetColorU32: (...any) -> ...any, GetColorU32: (...any) -> ...any,
GetColumnIndex: () -> number, GetColumnIndex: () -> number,
@@ -286,6 +318,9 @@ export type ImGuiAPI = {
GetContentRegionAvail: () -> ImVec2, GetContentRegionAvail: () -> ImVec2,
GetContentRegionMax: (...any) -> ...any, GetContentRegionMax: (...any) -> ...any,
GetCurrentContext: () -> any, GetCurrentContext: () -> any,
GetCurrentFocusScope: (...any) -> ...any,
GetCurrentTabBar: (...any) -> ...any,
GetCurrentTable: (...any) -> ...any,
GetCurrentWindow: (...any) -> ...any, GetCurrentWindow: (...any) -> ...any,
GetCurrentWindowRead: (...any) -> ...any, GetCurrentWindowRead: (...any) -> ...any,
GetCursorPos: () -> ImVec2, GetCursorPos: () -> ImVec2,
@@ -297,7 +332,9 @@ export type ImGuiAPI = {
GetDragDropPayload: () -> any, GetDragDropPayload: () -> any,
GetDrawData: () -> any, GetDrawData: () -> any,
GetDrawListSharedData: () -> any, GetDrawListSharedData: () -> any,
GetFocusID: (...any) -> ...any,
GetFont: () -> any, GetFont: () -> any,
GetFontBaked: () -> any,
GetFontSize: () -> number, GetFontSize: () -> number,
GetFontTexUvWhitePixel: () -> ImVec2, GetFontTexUvWhitePixel: () -> ImVec2,
GetForegroundDrawList: (viewport: any) -> ...any, GetForegroundDrawList: (viewport: any) -> ...any,
@@ -315,6 +352,7 @@ export type ImGuiAPI = {
GetItemRectMax: () -> ImVec2, GetItemRectMax: () -> ImVec2,
GetItemRectMin: () -> ImVec2, GetItemRectMin: () -> ImVec2,
GetItemRectSize: () -> ImVec2, GetItemRectSize: () -> ImVec2,
GetItemStatusFlags: (...any) -> ...any,
GetKeyChordName: (...any) -> ...any, GetKeyChordName: (...any) -> ...any,
GetKeyData: (...any) -> ...any, GetKeyData: (...any) -> ...any,
GetKeyMagnitude2d: (...any) -> ...any, GetKeyMagnitude2d: (...any) -> ...any,
@@ -331,7 +369,9 @@ export type ImGuiAPI = {
GetMouseDragDelta: (button: number?, lock_threshold: number?) -> ImVec2, GetMouseDragDelta: (button: number?, lock_threshold: number?) -> ImVec2,
GetMousePos: () -> ImVec2, GetMousePos: () -> ImVec2,
GetMousePosOnOpeningCurrentPopup: () -> ImVec2, GetMousePosOnOpeningCurrentPopup: () -> ImVec2,
GetMultiSelectState: (...any) -> ...any,
GetNavTweakPressedAmount: (...any) -> ...any, GetNavTweakPressedAmount: (...any) -> ...any,
GetPixelDensity: (...any) -> ...any,
GetPlatformIO: () -> any, GetPlatformIO: () -> any,
GetPopupAllowedExtentRect: (...any) -> ...any, GetPopupAllowedExtentRect: (...any) -> ...any,
GetRoundedFontSize: (...any) -> ...any, GetRoundedFontSize: (...any) -> ...any,
@@ -353,6 +393,7 @@ export type ImGuiAPI = {
GetTopMostPopupModal: (...any) -> ...any, GetTopMostPopupModal: (...any) -> ...any,
GetTreeNodeToLabelSpacing: () -> number, GetTreeNodeToLabelSpacing: () -> number,
GetTypematicRepeatRate: (...any) -> ...any, GetTypematicRepeatRate: (...any) -> ...any,
GetTypingSelectRequest: (...any) -> ...any,
GetVersion: () -> any, GetVersion: () -> any,
GetViewportPlatformMonitor: (...any) -> ...any, GetViewportPlatformMonitor: (...any) -> ...any,
GetWindowAlwaysWantOwnTabBar: (...any) -> ...any, GetWindowAlwaysWantOwnTabBar: (...any) -> ...any,
@@ -360,10 +401,13 @@ export type ImGuiAPI = {
GetWindowContentRegionMax: (...any) -> ...any, GetWindowContentRegionMax: (...any) -> ...any,
GetWindowContentRegionMin: (...any) -> ...any, GetWindowContentRegionMin: (...any) -> ...any,
GetWindowDockID: () -> number, GetWindowDockID: () -> number,
GetWindowDockNode: (...any) -> ...any,
GetWindowDpiScale: () -> number, GetWindowDpiScale: () -> number,
GetWindowDrawList: () -> any, GetWindowDrawList: () -> any,
GetWindowHeight: () -> number, GetWindowHeight: () -> number,
GetWindowPos: () -> ImVec2, GetWindowPos: () -> ImVec2,
GetWindowResizeBorderID: (...any) -> ...any,
GetWindowResizeCornerID: (...any) -> ...any,
GetWindowScrollbarID: (...any) -> ...any, GetWindowScrollbarID: (...any) -> ...any,
GetWindowScrollbarRect: (...any) -> ...any, GetWindowScrollbarRect: (...any) -> ...any,
GetWindowSize: () -> ImVec2, GetWindowSize: () -> ImVec2,
@@ -405,6 +449,7 @@ export type ImGuiAPI = {
IsInNavFocusRoute: (...any) -> ...any, IsInNavFocusRoute: (...any) -> ...any,
IsItemActivated: () -> boolean, IsItemActivated: () -> boolean,
IsItemActive: () -> boolean, IsItemActive: () -> boolean,
IsItemActiveAsInputText: (...any) -> ...any,
IsItemClicked: (mouse_button: number?) -> boolean, IsItemClicked: (mouse_button: number?) -> boolean,
IsItemDeactivated: () -> boolean, IsItemDeactivated: () -> boolean,
IsItemDeactivatedAfterEdit: () -> boolean, IsItemDeactivatedAfterEdit: () -> boolean,
@@ -421,6 +466,7 @@ export type ImGuiAPI = {
IsKeyReleased: (...any) -> ...any, IsKeyReleased: (...any) -> ...any,
IsKeyboardKey: (...any) -> ...any, IsKeyboardKey: (...any) -> ...any,
IsLRModKey: (...any) -> ...any, IsLRModKey: (...any) -> ...any,
IsLegacyKey: (...any) -> ...any,
IsMouseClicked: (button: number, is_repeat: boolean?) -> boolean, IsMouseClicked: (button: number, is_repeat: boolean?) -> boolean,
IsMouseClickedEx: (...any) -> ...any, IsMouseClickedEx: (...any) -> ...any,
IsMouseDoubleClicked: (...any) -> ...any, IsMouseDoubleClicked: (...any) -> ...any,
@@ -479,6 +525,10 @@ export type ImGuiAPI = {
MenuItem: (...any) -> ...any, MenuItem: (...any) -> ...any,
MenuItemEx: (...any) -> ...any, MenuItemEx: (...any) -> ...any,
MouseButtonToKey: (...any) -> ...any, MouseButtonToKey: (...any) -> ...any,
MultiSelectAddSetAll: (...any) -> ...any,
MultiSelectAddSetRange: (...any) -> ...any,
MultiSelectItemFooter: (...any) -> ...any,
MultiSelectItemHeader: (...any) -> ...any,
NavApplyItemToResult: (...any) -> ...any, NavApplyItemToResult: (...any) -> ...any,
NavBiasScoringRect: (...any) -> ...any, NavBiasScoringRect: (...any) -> ...any,
NavCalcPreferredRefPos: (...any) -> ...any, NavCalcPreferredRefPos: (...any) -> ...any,
@@ -556,6 +606,8 @@ export type ImGuiAPI = {
RadioButton: (...any) -> ...any, RadioButton: (...any) -> ...any,
RadioButtonEx: (...any) -> ...any, RadioButtonEx: (...any) -> ...any,
RegisterFontAtlas: (...any) -> ...any, RegisterFontAtlas: (...any) -> ...any,
RegisterUserTexture: (...any) -> ...any,
RemoveContextHook: (...any) -> ...any,
RemoveSettingsHandler: (...any) -> ...any, RemoveSettingsHandler: (...any) -> ...any,
Render: () -> (), Render: () -> (),
RenderArrow: (...any) -> ...any, RenderArrow: (...any) -> ...any,
@@ -591,6 +643,7 @@ export type ImGuiAPI = {
ScaleRatioFromValueT: (...any) -> ...any, ScaleRatioFromValueT: (...any) -> ...any,
ScaleValueFromRatioT: (...any) -> ...any, ScaleValueFromRatioT: (...any) -> ...any,
ScaleWindowsInViewport: (...any) -> ...any, ScaleWindowsInViewport: (...any) -> ...any,
ScrollToBringRectIntoView: (...any) -> ...any,
ScrollToItem: (...any) -> ...any, ScrollToItem: (...any) -> ...any,
ScrollToRect: (...any) -> ...any, ScrollToRect: (...any) -> ...any,
ScrollToRectEx: (...any) -> ...any, ScrollToRectEx: (...any) -> ...any,
@@ -608,6 +661,7 @@ export type ImGuiAPI = {
SetColorEditOptions: (...any) -> ...any, SetColorEditOptions: (...any) -> ...any,
SetColumnOffset: (column_index: number, offset: number) -> (), SetColumnOffset: (column_index: number, offset: number) -> (),
SetColumnWidth: (column_index: number, width: number) -> (), SetColumnWidth: (column_index: number, width: number) -> (),
SetContextName: (...any) -> ...any,
SetCurrentContext: (ctx: any) -> ...any, SetCurrentContext: (ctx: any) -> ...any,
SetCurrentFont: (...any) -> ...any, SetCurrentFont: (...any) -> ...any,
SetCurrentViewport: (...any) -> ...any, SetCurrentViewport: (...any) -> ...any,
@@ -621,6 +675,7 @@ export type ImGuiAPI = {
SetItemDefaultFocus: () -> (), SetItemDefaultFocus: () -> (),
SetItemKeyOwner: (...any) -> ...any, SetItemKeyOwner: (...any) -> ...any,
SetItemTooltip: (fmt: string, ...any) -> (), SetItemTooltip: (fmt: string, ...any) -> (),
SetItemTooltipV: (fmt: string, ...any) -> (),
SetKeyOwner: (...any) -> ...any, SetKeyOwner: (...any) -> ...any,
SetKeyOwnersForKeyChord: (...any) -> ...any, SetKeyOwnersForKeyChord: (...any) -> ...any,
SetKeyboardFocusHere: (offset: number?) -> (), SetKeyboardFocusHere: (offset: number?) -> (),
@@ -639,6 +694,7 @@ export type ImGuiAPI = {
SetNextItemColorMarker: (...any) -> ...any, SetNextItemColorMarker: (...any) -> ...any,
SetNextItemOpen: (is_open: boolean, cond: number?) -> (), SetNextItemOpen: (is_open: boolean, cond: number?) -> (),
SetNextItemRefVal: (...any) -> ...any, SetNextItemRefVal: (...any) -> ...any,
SetNextItemSelectionUserData: (selection_user_data: any) -> (),
SetNextItemShortcut: (key_chord: any, flags: number?) -> (), SetNextItemShortcut: (key_chord: any, flags: number?) -> (),
SetNextItemStorageID: (storage_id: number) -> (), SetNextItemStorageID: (storage_id: number) -> (),
SetNextItemWidth: (item_width: number) -> (), SetNextItemWidth: (item_width: number) -> (),
@@ -657,6 +713,7 @@ export type ImGuiAPI = {
SetNextWindowSize: (size: ImVec2, cond: number?) -> (), SetNextWindowSize: (size: ImVec2, cond: number?) -> (),
SetNextWindowSizeConstraints: (size_min: ImVec2, size_max: ImVec2, custom_callback: any, custom_callback_user_data: any) -> ...any, SetNextWindowSizeConstraints: (size_min: ImVec2, size_max: ImVec2, custom_callback: any, custom_callback_user_data: any) -> ...any,
SetNextWindowViewport: (id: number) -> (), SetNextWindowViewport: (id: number) -> (),
SetPixelDensity: (...any) -> ...any,
SetScrollFromPosX: (...any) -> ...any, SetScrollFromPosX: (...any) -> ...any,
SetScrollFromPosY: (...any) -> ...any, SetScrollFromPosY: (...any) -> ...any,
SetScrollHereX: (center_x_ratio: number?) -> (), SetScrollHereX: (center_x_ratio: number?) -> (),
@@ -667,6 +724,7 @@ export type ImGuiAPI = {
SetStateStorage: (tree: any) -> ...any, SetStateStorage: (tree: any) -> ...any,
SetTabItemClosed: (label: string) -> (), SetTabItemClosed: (label: string) -> (),
SetTooltip: (fmt: string, ...any) -> (), SetTooltip: (fmt: string, ...any) -> (),
SetTooltipV: (fmt: string, ...any) -> (),
SetWindowClipRectBeforeSetChannel: (...any) -> ...any, SetWindowClipRectBeforeSetChannel: (...any) -> ...any,
SetWindowCollapsed: (...any) -> ...any, SetWindowCollapsed: (...any) -> ...any,
SetWindowDock: (...any) -> ...any, SetWindowDock: (...any) -> ...any,
@@ -674,6 +732,7 @@ export type ImGuiAPI = {
SetWindowFontScale: (...any) -> ...any, SetWindowFontScale: (...any) -> ...any,
SetWindowHiddenAndSkipItemsForCurrentFrame: (...any) -> ...any, SetWindowHiddenAndSkipItemsForCurrentFrame: (...any) -> ...any,
SetWindowHitTestHole: (...any) -> ...any, SetWindowHitTestHole: (...any) -> ...any,
SetWindowParentWindowForFocusRoute: (...any) -> ...any,
SetWindowPos: (...any) -> ...any, SetWindowPos: (...any) -> ...any,
SetWindowSize: (...any) -> ...any, SetWindowSize: (...any) -> ...any,
SetWindowViewport: (...any) -> ...any, SetWindowViewport: (...any) -> ...any,
@@ -822,6 +881,7 @@ export type ImGuiAPI = {
TempInputScalar: (...any) -> ...any, TempInputScalar: (...any) -> ...any,
TempInputText: (...any) -> ...any, TempInputText: (...any) -> ...any,
TestKeyOwner: (...any) -> ...any, TestKeyOwner: (...any) -> ...any,
TestShortcutRouting: (...any) -> ...any,
Text: (fmt: string, ...any) -> (), Text: (fmt: string, ...any) -> (),
TextAligned: (...any) -> ...any, TextAligned: (...any) -> ...any,
TextBufferToString: (buf: {number}) -> string, TextBufferToString: (buf: {number}) -> string,
@@ -848,11 +908,17 @@ export type ImGuiAPI = {
TreePop: () -> (), TreePop: () -> (),
TreePush: (...any) -> ...any, TreePush: (...any) -> ...any,
TreePushOverrideID: (...any) -> ...any, TreePushOverrideID: (...any) -> ...any,
TypingSelectFindBestLeadingMatch: (...any) -> ...any,
TypingSelectFindMatch: (...any) -> ...any,
TypingSelectFindNextSingleCharMatch: (...any) -> ...any,
Unindent: (indent_w: number?) -> (), Unindent: (indent_w: number?) -> (),
UnregisterFontAtlas: (...any) -> ...any, UnregisterFontAtlas: (...any) -> ...any,
UnregisterUserTexture: (...any) -> ...any,
UpdateAliasKey: (...any) -> ...any, UpdateAliasKey: (...any) -> ...any,
UpdateCurrentFontSize: (...any) -> ...any, UpdateCurrentFontSize: (...any) -> ...any,
UpdateDebugToolFlashStyleColor: (...any) -> ...any,
UpdateDebugToolItemPathQuery: (...any) -> ...any, UpdateDebugToolItemPathQuery: (...any) -> ...any,
UpdateDebugToolItemPicker: (...any) -> ...any,
UpdateFontsEndFrame: (...any) -> ...any, UpdateFontsEndFrame: (...any) -> ...any,
UpdateFontsNewFrame: (...any) -> ...any, UpdateFontsNewFrame: (...any) -> ...any,
UpdateHoveredWindowAndCaptureFlags: (...any) -> ...any, UpdateHoveredWindowAndCaptureFlags: (...any) -> ...any,
@@ -880,20 +946,22 @@ export type ImGuiAPI = {
VSliderScalar: (label: string, size: ImVec2, data_type: number, data: any, min: any, max: any, format: string?, flags: number?) -> ...any, VSliderScalar: (label: string, size: ImVec2, data_type: number, data: any, min: any, max: any, format: string?, flags: number?) -> ...any,
Value: (...any) -> ...any, Value: (...any) -> ...any,
WindowPosAbsToRel: (...any) -> ...any, WindowPosAbsToRel: (...any) -> ...any,
WindowPosRelToAbs: (...any) -> ...any,
WindowRectAbsToRel: (...any) -> ...any, WindowRectAbsToRel: (...any) -> ...any,
WindowRectRelToAbs: (...any) -> ...any, WindowRectRelToAbs: (...any) -> ...any,
WindowSelectViewport: (...any) -> ...any, WindowSelectViewport: (...any) -> ...any,
WindowSettingsAddSettingsHandler: (...any) -> ...any, WindowSettingsAddSettingsHandler: (...any) -> ...any,
WindowSyncOwnedViewport: (...any) -> ...any, WindowSyncOwnedViewport: (...any) -> ...any,
_ByteArrayToString: (...any) -> ...any, _ByteArrayToString: (...any) -> ...any,
_CalcItemSizeXY: (...any) -> ...any,
_GetTypeFunc: (...any) -> ...any, _GetTypeFunc: (...any) -> ...any,
_SetCurrentContext_Internal: (...any) -> ...any, _SetCurrentContext_Internal: (...any) -> ...any,
_SetCurrentContext_Tables: (...any) -> ...any, _SetCurrentContext_Tables: (...any) -> ...any,
_SetCurrentContext_Widgets: (...any) -> ...any, _SetCurrentContext_Widgets: (...any) -> ...any,
[string]: any, [string]: any,
} }
export type ImDrawFlags = { None: number, RoundCornersTopLeft: number, RoundCornersTopRight: number, RoundCornersBottomLeft: number, RoundCornersBottomRight: number, RoundCornersNone: number, Closed: number, RoundCornersTop: number, RoundCornersBottom: number, RoundCornersLeft: number, RoundCornersRight: number, RoundCornersAll: number, RoundCornersDefault_: number, RoundCornersMask_: number, InvalidMask_: number, [string]: number } export type ImDrawFlags = { None: number, RoundTopLeft: number, RoundTopRight: number, RoundBottomLeft: number, RoundBottomRight: number, RoundNone: number, Closed: number, JoinMiter: number, CapSquare: number, AAFill: number, AALines: number, AALineEnds: number, StrokeInside: number, StrokeCenter: number, StrokeCenterBiased: number, StrokeOutside: number, StrokeLegacy: number, TextNoPixelSnap: number, UseTexForRoundCorners: number, UseVtxOffset: number, AllowTexForRoundCorners_: number, RoundAll: number, RoundTop: number, RoundBottom: number, RoundLeft: number, RoundRight: number, RoundMask_: number, AllowInPushScope_: number, AllowInFrameScope_: number, StrokeMask_: number, InvalidMask_: number, RoundCornersTopLeft: number, RoundCornersTopRight: number, RoundCornersBottomLeft: number, RoundCornersBottomRight: number, RoundCornersNone: number, RoundCornersAll: number, RoundCornersTop: number, RoundCornersBottom: number, RoundCornersLeft: number, RoundCornersRight: number, [string]: number }
export type ImDrawListFlags = { None: number, AntiAliasedLines: number, AntiAliasedLinesUseTex: number, AntiAliasedFill: number, AllowVtxOffset: number, TextNoPixelSnap: number, [string]: number } export type ImDrawListFlags = { None: number, AntiAliasedFill: number, AntiAliasedLines: number, AntiAliasedLinesUseTex: number, AllowVtxOffset: number, TextNoPixelSnap: number, [string]: number }
export type ImFontFlags = { None: number, NoLoadError: number, NoLoadGlyphs: number, LockBakedSizes: number, ImplicitRefSize: number, [string]: number } export type ImFontFlags = { None: number, NoLoadError: number, NoLoadGlyphs: number, LockBakedSizes: number, ImplicitRefSize: number, [string]: number }
export type ImGuiBackendFlags = { None: number, HasGamepad: number, HasMouseCursors: number, HasSetMousePos: number, RendererHasVtxOffset: number, RendererHasTextures: number, RendererHasViewports: number, PlatformHasViewports: number, HasMouseHoveredViewport: number, HasParentViewport: number, [string]: number } export type ImGuiBackendFlags = { None: number, HasGamepad: number, HasMouseCursors: number, HasSetMousePos: number, RendererHasVtxOffset: number, RendererHasTextures: number, RendererHasViewports: number, PlatformHasViewports: number, HasMouseHoveredViewport: number, HasParentViewport: number, [string]: number }
export type ImGuiButtonFlags = { None: number, MouseButtonLeft: number, MouseButtonRight: number, MouseButtonMiddle: number, EnableNav: number, PressedOnClick: number, PressedOnClickRelease: number, PressedOnClickReleaseAnywhere: number, PressedOnRelease: number, PressedOnDoubleClick: number, PressedOnDragDropHold: number, FlattenChildren: number, AllowOverlap: number, AlignTextBaseLine: number, NoKeyModsAllowed: number, NoHoldingActiveId: number, NoNavFocus: number, NoHoveredOnFocus: number, NoSetKeyOwner: number, NoTestKeyOwner: number, NoFocus: number, MouseButtonMask_: number, PressedOnMask_: number, PressedOnDefault_: number, NoKeyModifiers: number, [string]: number } export type ImGuiButtonFlags = { None: number, MouseButtonLeft: number, MouseButtonRight: number, MouseButtonMiddle: number, EnableNav: number, PressedOnClick: number, PressedOnClickRelease: number, PressedOnClickReleaseAnywhere: number, PressedOnRelease: number, PressedOnDoubleClick: number, PressedOnDragDropHold: number, FlattenChildren: number, AllowOverlap: number, AlignTextBaseLine: number, NoKeyModsAllowed: number, NoHoldingActiveId: number, NoNavFocus: number, NoHoveredOnFocus: number, NoSetKeyOwner: number, NoTestKeyOwner: number, NoFocus: number, MouseButtonMask_: number, PressedOnMask_: number, PressedOnDefault_: number, NoKeyModifiers: number, [string]: number }
@@ -911,12 +979,15 @@ export type ImGuiInputTextFlags = { None: number, CharsDecimal: number, CharsHex
export type ImGuiItemFlags = { None: number, NoTabStop: number, NoNav: number, NoNavDefaultFocus: number, ButtonRepeat: number, AutoClosePopups: number, AllowDuplicateId: number, Disabled: number, LiveEditOnInputText: number, LiveEditOnInputScalar: number, LiveEditOnInput: number, ReadOnly: number, MixedValue: number, NoWindowHoverableCheck: number, AllowOverlap: number, NoNavDisableMouseHover: number, NoMarkEdited: number, NoFocus: number, Inputable: number, HasSelectionUserData: number, IsMultiSelect: number, Default_: number, [string]: number } export type ImGuiItemFlags = { None: number, NoTabStop: number, NoNav: number, NoNavDefaultFocus: number, ButtonRepeat: number, AutoClosePopups: number, AllowDuplicateId: number, Disabled: number, LiveEditOnInputText: number, LiveEditOnInputScalar: number, LiveEditOnInput: number, ReadOnly: number, MixedValue: number, NoWindowHoverableCheck: number, AllowOverlap: number, NoNavDisableMouseHover: number, NoMarkEdited: number, NoFocus: number, Inputable: number, HasSelectionUserData: number, IsMultiSelect: number, Default_: number, [string]: number }
export type ImGuiItemStatusFlags = { None: number, HoveredRect: number, HasDisplayRect: number, Edited: number, ToggledSelection: number, ToggledOpen: number, HasDeactivated: number, Deactivated: number, HoveredWindow: number, Visible: number, HasClipRect: number, HasShortcut: number, EditedInternal: number, [string]: number } export type ImGuiItemStatusFlags = { None: number, HoveredRect: number, HasDisplayRect: number, Edited: number, ToggledSelection: number, ToggledOpen: number, HasDeactivated: number, Deactivated: number, HoveredWindow: number, Visible: number, HasClipRect: number, HasShortcut: number, EditedInternal: number, [string]: number }
export type ImGuiKey = { None: number, NamedKey_BEGIN: number, Tab: number, LeftArrow: number, RightArrow: number, UpArrow: number, DownArrow: number, PageUp: number, PageDown: number, Home: number, End: number, Insert: number, Delete: number, Backspace: number, Space: number, Enter: number, Escape: number, LeftCtrl: number, RightCtrl: number, Menu: number, K0: number, A: number, K: number, U: number, F1: number, F7: number, F13: number, F19: number, Apostrophe: number, CapsLock: number, Keypad0: number, KeypadDecimal: number, AppBack: number, AppForward: number, Oem102: number, GamepadStart: number, GamepadFaceLeft: number, GamepadDpadLeft: number, GamepadL1: number, GamepadL2: number, GamepadL3: number, GamepadLStickLeft: number, GamepadRStickLeft: number, MouseLeft: number, ReservedForModCtrl: number, NamedKey_END: number, NamedKey_COUNT: number, NavKeyboardTweakSlow: number, NavKeyboardTweakFast: number, NavGamepadTweakSlow: number, NavGamepadTweakFast: number, NavGamepadMenu: number, NavGamepadContextMenu: number, [string]: number } export type ImGuiKey = { None: number, NamedKey_BEGIN: number, Tab: number, LeftArrow: number, RightArrow: number, UpArrow: number, DownArrow: number, PageUp: number, PageDown: number, Home: number, End: number, Insert: number, Delete: number, Backspace: number, Space: number, Enter: number, Escape: number, LeftCtrl: number, RightCtrl: number, Menu: number, K0: number, A: number, K: number, U: number, F1: number, F7: number, F13: number, F19: number, Apostrophe: number, CapsLock: number, Keypad0: number, KeypadDecimal: number, AppBack: number, AppForward: number, Oem102: number, GamepadStart: number, GamepadFaceLeft: number, GamepadDpadLeft: number, GamepadL1: number, GamepadL2: number, GamepadL3: number, GamepadLStickLeft: number, GamepadRStickLeft: number, MouseLeft: number, ReservedForModCtrl: number, NamedKey_END: number, NamedKey_COUNT: number, NavKeyboardTweakSlow: number, NavKeyboardTweakFast: number, NavGamepadTweakSlow: number, NavGamepadTweakFast: number, NavGamepadMenu: number, NavGamepadContextMenu: number, [string]: number }
export type ImGuiListClipperFlags = { None: number, NoSetTableRowCounters: number, [string]: number }
export type ImGuiMouseSource = { Mouse: number, TouchScreen: number, Pen: number, COUNT: number, [string]: number } export type ImGuiMouseSource = { Mouse: number, TouchScreen: number, Pen: number, COUNT: number, [string]: number }
export type ImGuiMultiSelectFlags = { None: number, SingleSelect: number, NoSelectAll: number, NoRangeSelect: number, NoAutoSelect: number, NoAutoClear: number, NoAutoClearOnReselect: number, BoxSelect1d: number, BoxSelect2d: number, BoxSelectNoScroll: number, ClearOnEscape: number, ClearOnClickVoid: number, ScopeWindow: number, ScopeRect: number, SelectOnAuto: number, SelectOnClickAlways: number, SelectOnClickRelease: number, NavWrapX: number, NoSelectOnRightClick: number, CheckboxMode_: number, SelectOnMask_: number, SelectOnClick: number, [string]: number }
export type ImGuiNextItemDataFlags = { None: number, HasWidth: number, HasOpen: number, HasShortcut: number, HasRefVal: number, HasStorageID: number, HasColorMarker: number, [string]: number } export type ImGuiNextItemDataFlags = { None: number, HasWidth: number, HasOpen: number, HasShortcut: number, HasRefVal: number, HasStorageID: number, HasColorMarker: number, [string]: number }
export type ImGuiPopupFlags = { None: number, MouseButtonLeft: number, MouseButtonRight: number, MouseButtonMiddle: number, NoReopen: number, NoOpenOverExistingPopup: number, NoOpenOverItems: number, AnyPopupId: number, AnyPopupLevel: number, AnyPopup: number, MouseButtonShift_: number, MouseButtonMask_: number, InvalidMask_: number, [string]: number } export type ImGuiPopupFlags = { None: number, MouseButtonLeft: number, MouseButtonRight: number, MouseButtonMiddle: number, NoReopen: number, NoOpenOverExistingPopup: number, NoOpenOverItems: number, AnyPopupId: number, AnyPopupLevel: number, AnyPopup: number, MouseButtonShift_: number, MouseButtonMask_: number, InvalidMask_: number, [string]: number }
export type ImGuiSelectableFlags = { None: number, NoAutoClosePopups: number, SpanAllColumns: number, AllowDoubleClick: number, Disabled: number, AllowOverlap: number, Highlight: number, SelectOnNav: number, NoHoldingActiveID: number, SelectOnClick: number, SelectOnRelease: number, SpanAvailWidth: number, SetNavIdOnHover: number, NoPadWithHalfSpacing: number, NoSetKeyOwner: number, [string]: number } export type ImGuiSelectableFlags = { None: number, NoAutoClosePopups: number, SpanAllColumns: number, AllowDoubleClick: number, Disabled: number, AllowOverlap: number, Highlight: number, SelectOnNav: number, NoHoldingActiveID: number, SelectOnClick: number, SelectOnRelease: number, SpanAvailWidth: number, SetNavIdOnHover: number, NoPadWithHalfSpacing: number, NoSetKeyOwner: number, [string]: number }
export type ImGuiSelectionRequestType = { None: number, SetAll: number, SetRange: number, [string]: number }
export type ImGuiSliderFlags = { None: number, Logarithmic: number, NoRoundToFormat: number, NoInput: number, WrapAround: number, ClampOnInput: number, ClampZeroRange: number, NoSpeedTweaks: number, ColorMarkers: number, InvalidMask_: number, AlwaysClamp: number, Vertical: number, ReadOnly: number, [string]: number } export type ImGuiSliderFlags = { None: number, Logarithmic: number, NoRoundToFormat: number, NoInput: number, WrapAround: number, ClampOnInput: number, ClampZeroRange: number, NoSpeedTweaks: number, ColorMarkers: number, InvalidMask_: number, AlwaysClamp: number, Vertical: number, ReadOnly: number, [string]: number }
export type ImGuiStyleVar = { Alpha: number, DisabledAlpha: number, WindowPadding: number, WindowRounding: number, WindowBorderSize: number, WindowMinSize: number, WindowTitleAlign: number, ChildRounding: number, ChildBorderSize: number, PopupRounding: number, PopupBorderSize: number, FramePadding: number, FrameRounding: number, FrameBorderSize: number, ItemSpacing: number, ItemInnerSpacing: number, IndentSpacing: number, CellPadding: number, ScrollbarSize: number, ScrollbarRounding: number, ScrollbarPadding: number, GrabMinSize: number, GrabRounding: number, ImageRounding: number, ImageBorderSize: number, TabRounding: number, TabBorderSize: number, TabMinWidthBase: number, TabMinWidthShrink: number, TabBarBorderSize: number, TabBarOverlineSize: number, TableAngledHeadersAngle: number, TableAngledHeadersTextAlign: number, TreeLinesSize: number, TreeLinesRounding: number, MenuItemRounding: number, SelectableRounding: number, DragDropTargetRounding: number, ButtonTextAlign: number, SelectableTextAlign: number, SeparatorTextBorderSize: number, SeparatorTextAlign: number, SeparatorTextPadding: number, COUNT: number, [string]: number } export type ImGuiStyleVar = { Alpha: number, DisabledAlpha: number, WindowPadding: number, WindowRounding: number, WindowBorderSize: number, WindowMinSize: number, WindowTitleAlign: number, ChildRounding: number, ChildBorderSize: number, PopupRounding: number, PopupBorderSize: number, FramePadding: number, FrameRounding: number, FrameBorderSize: number, ItemSpacing: number, ItemInnerSpacing: number, IndentSpacing: number, CellPadding: number, ScrollbarSize: number, ScrollbarRounding: number, ScrollbarPadding: number, GrabMinSize: number, GrabRounding: number, ImageRounding: number, ImageBorderSize: number, TabRounding: number, TabBorderSize: number, TabMinWidthBase: number, TabMinWidthShrink: number, TabBarBorderSize: number, TabBarOverlineSize: number, TableAngledHeadersAngle: number, TableAngledHeadersTextAlign: number, TreeLinesSize: number, TreeLinesRounding: number, MenuItemRounding: number, SelectableRounding: number, DragDropTargetRounding: number, ButtonTextAlign: number, SelectableTextAlign: number, SeparatorSize: number, SeparatorTextBorderSize: number, SeparatorTextAlign: number, SeparatorTextPadding: number, DockingSeparatorSize: number, COUNT: number, [string]: number }
export type ImGuiTreeNodeFlags = { None: number, Selected: number, Framed: number, AllowOverlap: number, NoTreePushOnOpen: number, NoAutoOpenOnLog: number, DefaultOpen: number, OpenOnDoubleClick: number, OpenOnArrow: number, Leaf: number, Bullet: number, FramePadding: number, SpanAvailWidth: number, SpanFullWidth: number, SpanLabelWidth: number, SpanAllColumns: number, LabelSpanAllColumns: number, NavLeftJumpsToParent: number, DrawLinesNone: number, DrawLinesFull: number, DrawLinesToNodes: number, NoNavFocus: number, ClipLabelForTrailingButton: number, UpsideDownArrow: number, CollapsingHeader: number, OpenOnMask_: number, DrawLinesMask_: number, [string]: number } export type ImGuiTreeNodeFlags = { None: number, Selected: number, Framed: number, AllowOverlap: number, NoTreePushOnOpen: number, NoAutoOpenOnLog: number, DefaultOpen: number, OpenOnDoubleClick: number, OpenOnArrow: number, Leaf: number, Bullet: number, FramePadding: number, SpanAvailWidth: number, SpanFullWidth: number, SpanLabelWidth: number, SpanAllColumns: number, LabelSpanAllColumns: number, NavLeftJumpsToParent: number, DrawLinesNone: number, DrawLinesFull: number, DrawLinesToNodes: number, NoNavFocus: number, ClipLabelForTrailingButton: number, UpsideDownArrow: number, CollapsingHeader: number, OpenOnMask_: number, DrawLinesMask_: number, [string]: number }
export type ImGuiViewportFlags = { None: number, IsPlatformWindow: number, IsPlatformMonitor: number, OwnedByApp: number, NoDecoration: number, NoTaskBarIcon: number, NoFocusOnAppearing: number, NoFocusOnClick: number, NoInputs: number, NoRendererClear: number, NoAutoMerge: number, TopMost: number, CanHostOtherWindows: number, IsMinimized: number, IsFocused: number, [string]: number } export type ImGuiViewportFlags = { None: number, IsPlatformWindow: number, IsPlatformMonitor: number, OwnedByApp: number, NoDecoration: number, NoTaskBarIcon: number, NoFocusOnAppearing: number, NoFocusOnClick: number, NoInputs: number, NoRendererClear: number, NoAutoMerge: number, TopMost: number, CanHostOtherWindows: number, IsMinimized: number, IsFocused: number, [string]: number }
export type ImGuiWindowFlags = { None: number, NoTitleBar: number, NoResize: number, NoMove: number, NoScrollbar: number, NoScrollWithMouse: number, NoCollapse: number, AlwaysAutoResize: number, NoBackground: number, NoSavedSettings: number, NoMouseInputs: number, MenuBar: number, HorizontalScrollbar: number, NoFocusOnAppearing: number, NoBringToFrontOnFocus: number, AlwaysVerticalScrollbar: number, AlwaysHorizontalScrollbar: number, NoNavInputs: number, NoNavFocus: number, UnsavedDocument: number, NoDocking: number, DockNodeHost: number, ChildWindow: number, Tooltip: number, Popup: number, Modal: number, ChildMenu: number, NoNav: number, NoDecoration: number, NoInputs: number, [string]: number } export type ImGuiWindowFlags = { None: number, NoTitleBar: number, NoResize: number, NoMove: number, NoScrollbar: number, NoScrollWithMouse: number, NoCollapse: number, AlwaysAutoResize: number, NoBackground: number, NoSavedSettings: number, NoMouseInputs: number, MenuBar: number, HorizontalScrollbar: number, NoFocusOnAppearing: number, NoBringToFrontOnFocus: number, AlwaysVerticalScrollbar: number, AlwaysHorizontalScrollbar: number, NoNavInputs: number, NoNavFocus: number, UnsavedDocument: number, NoDocking: number, DockNodeHost: number, ChildWindow: number, Tooltip: number, Popup: number, Modal: number, ChildMenu: number, NoNav: number, NoDecoration: number, NoInputs: number, [string]: number }
@@ -943,10 +1014,13 @@ export type ImGuiModule = {
ImGuiItemFlags: ImGuiItemFlags, ImGuiItemFlags: ImGuiItemFlags,
ImGuiItemStatusFlags: ImGuiItemStatusFlags, ImGuiItemStatusFlags: ImGuiItemStatusFlags,
ImGuiKey: ImGuiKey, ImGuiKey: ImGuiKey,
ImGuiListClipperFlags: ImGuiListClipperFlags,
ImGuiMouseSource: ImGuiMouseSource, ImGuiMouseSource: ImGuiMouseSource,
ImGuiMultiSelectFlags: ImGuiMultiSelectFlags,
ImGuiNextItemDataFlags: ImGuiNextItemDataFlags, ImGuiNextItemDataFlags: ImGuiNextItemDataFlags,
ImGuiPopupFlags: ImGuiPopupFlags, ImGuiPopupFlags: ImGuiPopupFlags,
ImGuiSelectableFlags: ImGuiSelectableFlags, ImGuiSelectableFlags: ImGuiSelectableFlags,
ImGuiSelectionRequestType: ImGuiSelectionRequestType,
ImGuiSliderFlags: ImGuiSliderFlags, ImGuiSliderFlags: ImGuiSliderFlags,
ImGuiStyleVar: ImGuiStyleVar, ImGuiStyleVar: ImGuiStyleVar,
ImGuiTreeNodeFlags: ImGuiTreeNodeFlags, ImGuiTreeNodeFlags: ImGuiTreeNodeFlags,
+1 -1
View File
@@ -1 +1 @@
[[149, "imgui.lua"], [21425, "imgui_demo.lua"], [29843, "imgui_draw.lua"], [35613, "imgui_h.lua"], [38279, "imgui_impl_love.lua"], [38819, "imgui_internal.lua"], [42265, "imgui_tables.lua"], [47260, "imgui_widgets.lua"], [57871, "imstb_rectpack.lua"], [58535, "imstb_textedit.lua"], [60077, "imstb_truetype.lua"], [62865, "imstd_minstdio.lua"]] [[183, "imgui.lua"], [22812, "imgui_demo.lua"], [34670, "imgui_draw.lua"], [42997, "imgui_h.lua"], [45994, "imgui_impl_love.lua"], [46538, "imgui_internal.lua"], [50510, "imgui_tables.lua"], [55602, "imgui_widgets.lua"], [67836, "imstb_rectpack.lua"], [68500, "imstb_textedit.lua"], [70042, "imstb_truetype.lua"], [72830, "imstd_minstdio.lua"]]
+20530 -10436
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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`). - **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`. - 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. - 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.
+2622
View File
File diff suppressed because it is too large Load Diff
+19 -38
View File
@@ -1,52 +1,33 @@
Dear ImGui for Roblox - not yet ported / known gaps Dear ImGui for Roblox - not yet ported / known gaps
==================================================== ====================================================
Upstream reference: tools/test/upstream/ (ocornut/imgui docking branch, 1.93 WIP). Upstream reference: tools/test/upstream/ (ocornut/imgui docking branch, 1.93 WIP).
Re-check with: python3 tools/test/missing.py [imgui|widgets|tables|draw] and python3 tools/test/undefined.py --no-demo Re-check with: python3 tools/compare_upstream.py (writes docs/PORT_STATUS.txt: [ ] = not ported, [=] = replaced by a
(missing.py also lists names the port defines differently, e.g. the *V printf variants and DragFloat2..4 / DragInt2..4, Lua equivalent, "missing:" = enum values / struct fields) and python3 tools/test/undefined.py --no-demo
which exist as ImGui.DragFloat2 = DragFloatN(2) etc. Those are NOT gaps.)
FEATURES NOT PORTED NOT PORTED / PARTIAL
------------------- --------------------
- Multi-select / box-select: BeginMultiSelect, EndMultiSelect, MultiSelectItemHeader/Footer, MultiSelectAddSetAll, - ImFont::AddRemapCodepointToGlyphIndex / ImGui_ImplStbTrueType_FontSrcGetGlyphIndexFromCodepoint (the port's font
MultiSelectAddSetRange, SetNextItemSelectionUserData, BeginBoxSelect/EndBoxSelect, DebugNodeMultiSelectState. loader still checks glyphs by codepoint; 1.93 split this into codepoint -> glyph index -> glyph).
(Selectable/TreeNode call sites are stubbed with TODOs; the demo "Selection State & Multi-Select" section is not ported.) - ImGui_GetDefaultCompressedFontDataProggyClean/ProggyForever: AddFontFromMemoryCompressedTTF works, but the default
- Typing-select (type to jump in lists): GetTypingSelectRequest, TypingSelectFind*, DebugNodeTypingSelectState. fonts are loaded from fonts/ instead of embedded compressed data.
- Examples > Assets Browser (needs multi-select + box-select). - ImFontAtlasDebugLogTextureRequests, ImHashSkipUncontributingPrefix.
- Metrics > Tools > Item Picker (UpdateDebugToolItemPicker). - DebugLogSkippedErrors / DebugDrawIdConflicts* (error-recovery and ID-conflict debug tools).
- DebugFlashStyleColor / UpdateDebugToolFlashStyleColor (Style Editor "?" flash button).
- DebugNodeInputTextState (Metrics > Internal state > InputText). LUA LIMITATIONS
- BringWindowToDisplayBehind: stub, does nothing. ---------------
- ColorEdit4 / ColorPicker4: drag & drop target (dropping a color onto a color widget). - ImGuiDataType.S64/U64: values are Luau doubles, exact only within +-2^53 (LLONG_MAX/ULLONG_MAX round up).
- ImFontAtlas:AddFontFromMemoryCompressedTTF: stub (binary_to_compressed_c fonts can't be loaded). - ImDrawList::AddCallback(userdata_size > 0): the "copy" is a shallow copy of a table userdata (no raw bytes).
- ImGuiSizeCallbackData: SetNextWindowSizeConstraints custom callbacks only partially supported
(Examples > Constrained Resize).
- LogSetNextTextDecoration (Selectable "[ ]" decoration in log/clipboard output).
- stb_truetype: cmap high-byte mapping (some CJK fonts assert).
- Context hooks removal (RemoveContextHook), SetContextName.
- RegisterUserTexture / UnregisterUserTexture.
- ErrorCheckNewFrameSanityChecks / ErrorCheckEndFrameSanityChecks (fewer usage-error asserts than upstream).
- GetAllocatorFunctions (n/a in Luau).
WON'T DO (platform) WON'T DO (platform)
------------------- -------------------
- Multi-viewports / platform windows: Roblox has one window. Viewport API exists for the main viewport only. - Multi-viewports / platform windows: Roblox has one window. The viewport API covers the main viewport only.
- Gamepad navigation via a real gamepad backend (keyboard navigation works). - Gamepad navigation via a real gamepad backend (keyboard navigation works).
- IME / non-ASCII keyboard text input (backend sends printable ASCII only). - IME / non-ASCII keyboard text input (backend sends printable ASCII only).
NOT VERIFIED LINE BY LINE AGAINST UPSTREAM
------------------------------------------
- Demo: Layout & Scrolling, Popups & Modal windows, Inputs & Focus.
KNOWN ISSUES KNOWN ISSUES
------------ ------------
- Tables > "Columns flags": disabling every column asserts in TableUpdateLayout (upstream asserts too). - Tables > "Columns flags": disabling every column asserts in TableUpdateLayout (upstream asserts too).
- Style Editor > Colors uses a ListClipper (port-only change, upstream submits every row). - stb_truetype: cmap format 2 (high-byte mapping) asserts (same "@TODO" as upstream stb_truetype).
- ImTriangulator differs from upstream (imgui_draw.lua, [Luau]): CCW input is reversed first and only strictly
PERFORMANCE IDEAS (not done) counter-clockwise corners are reflex, so CCW and duplicate-point polygons triangulate differently from upstream.
----------------------------
- Cheaper visible ColorEdit4 rows; cheaper Begin() for child windows.
PLANNED EXTENSIONS
------------------
- ImGuizmo, ImNodes, ImGuiColorTextEdit, ImPlot, ImPlot3D (as separate extensions/, built with a --with flag).
+1 -1
View File
@@ -17,7 +17,7 @@ setfenv(1, setmetatable({}, {
local MainScale = 1.0 local MainScale = 1.0
local RenderScale = 1.0 local RenderScale = 1.0
local RenderRate = 60 local RenderRate = 60 -- max tile redraws per second (0 = every frame, limited by the redraw budget instead)
local ThumbnailSize = 420 local ThumbnailSize = 420
local FontSize = 13.0 local FontSize = 13.0
local FontNames = { "Cousine-Regular", "DroidSans", "Karla-Regular", "ProggyTiny", "Roboto-Medium" } local FontNames = { "Cousine-Regular", "DroidSans", "Karla-Regular", "ProggyTiny", "Roboto-Medium" }
-53
View File
@@ -1,53 +0,0 @@
-- One UI that runs unchanged on Roblox and LÖVE: only the Dear ImGui API is used, and the code is plain Lua 5.1
-- (no Luau-only syntax), so both Luau and LuaJIT run it. The platform launchers are roblox.client.luau and main.lua.
-- local App = require(app)(G) -- G = the table returned by the ImGui bundle
-- every frame, between ImGui.NewFrame() and ImGui.Render(): App.Frame()
return function(G)
local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4
local App = {
Platform = "?", -- set by the launcher ("Roblox" / "LÖVE")
ClearColor = ImVec4(0.45, 0.55, 0.60, 1.00), -- launchers may use it as the background colour
}
local show_demo, show_table = false, true
local counter, speed = 0, 1.0
local name = ImGui.NewTextBuffer("Player", 64)
local samples, t = {}, 0.0
local items = { { "Sword", 1, 120 }, { "Potion", 5, 15 }, { "Arrow", 64, 1 }, { "Shield", 1, 80 } }
function App.Frame()
local io = ImGui.GetIO()
t = t + io.DeltaTime * speed
for i = 1, 90 do samples[i] = math.sin(i / 90 * math.pi * 4 + t * 3) end
ImGui.SetNextWindowPos(ImVec2(20, 20), G.ImGuiCond.FirstUseEver)
ImGui.SetNextWindowSize(ImVec2(420, 360), G.ImGuiCond.FirstUseEver)
ImGui.Begin("Shared Example")
ImGui.Text("Running on %s (%.1f FPS)", App.Platform, io.Framerate)
ImGui.Separator()
ImGui.InputText("name", name, 64)
if ImGui.Button("Click me") then counter = counter + 1 end
ImGui.SameLine(); ImGui.Text("%s clicked %d times", ImGui.TextBufferToString(name), counter)
speed = ImGui.SliderFloat("speed", speed, 0.0, 4.0)
ImGui.ColorEdit3("background", App.ClearColor)
ImGui.PlotLines("wave", samples, nil, #samples, 0, nil, -1.0, 1.0, ImVec2(0, 60))
local _
_, show_table = ImGui.Checkbox("Inventory table", show_table)
ImGui.SameLine(); _, show_demo = ImGui.Checkbox("Demo window", show_demo)
if show_table and ImGui.BeginTable("inv", 3, bit32.bor(G.ImGuiTableFlags.Borders, G.ImGuiTableFlags.RowBg, G.ImGuiTableFlags.Sortable)) then
ImGui.TableSetupColumn("Item"); ImGui.TableSetupColumn("Count"); ImGui.TableSetupColumn("Value")
ImGui.TableHeadersRow()
for _, it in ipairs(items) do
ImGui.TableNextRow()
ImGui.TableNextColumn(); ImGui.Text(it[1])
ImGui.TableNextColumn(); ImGui.Text("%d", it[2])
ImGui.TableNextColumn(); ImGui.Text("%d", it[3])
end
ImGui.EndTable()
end
ImGui.End()
if show_demo and ImGui.ShowDemoWindow then show_demo = ImGui.ShowDemoWindow(show_demo) end
end
return App
end
-5
View File
@@ -1,5 +0,0 @@
function love.conf(t)
t.window.title = "Dear ImGui - shared example (LÖVE)"
t.window.width, t.window.height = 1280, 800
t.window.resizable = true
end
@@ -1,15 +0,0 @@
{
"name": "imgui-shared-example",
"tree": {
"$className": "DataModel",
"ReplicatedStorage": {
"ImGui": { "$path": "../../build/ImGuiTyped.luau", "Impl": { "$path": "../../build/ImGui.luau" } },
"SharedApp": { "$path": "app.lua" }
},
"StarterPlayer": {
"StarterPlayerScripts": {
"SharedExample": { "$path": "roblox.client.luau" }
}
}
}
}
-43
View File
@@ -1,43 +0,0 @@
-- LÖVE launcher for app.lua: python3 tools/bundle.py --target love then love examples/example_shared
local ROOT = love.filesystem.getSource() .. "/../../" -- repo root (build/, fonts/)
local function load_bundle()
local f = assert(io.open(ROOT .. "build/imgui_love.lua", "rb"), "run: python3 tools/bundle.py --target love")
local src = f:read("*a"); f:close()
return assert(loadstring(src, "@imgui_love.lua"))()
end
local G, ImGui, App
function love.load()
G = load_bundle()
ImGui = G.ImGui
ImGui.CreateContext()
local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.NavEnableKeyboard, G.ImGuiConfigFlags.DockingEnable)
io.IniFilename = nil
G.ImGui_ImplLove.Init({ root = ROOT })
App = love.filesystem.load("app.lua")()(G)
App.Platform = "LÖVE"
end
function love.update(dt)
G.ImGui_ImplLove.NewFrame()
ImGui.NewFrame()
App.Frame()
ImGui.Render()
end
function love.draw()
local c = App.ClearColor
love.graphics.clear(c.x, c.y, c.z, 1)
G.ImGui_ImplLove.RenderDrawData(ImGui.GetDrawData())
end
function love.mousemoved(x, y) G.ImGui_ImplLove.MouseMoved(x, y) end
function love.mousepressed(x, y, b) G.ImGui_ImplLove.MousePressed(x, y, b) end
function love.mousereleased(x, y, b) G.ImGui_ImplLove.MouseReleased(x, y, b) end
function love.wheelmoved(x, y) G.ImGui_ImplLove.WheelMoved(x, y) end
function love.keypressed(k) G.ImGui_ImplLove.KeyPressed(k) end
function love.keyreleased(k) G.ImGui_ImplLove.KeyReleased(k) end
function love.textinput(t) G.ImGui_ImplLove.TextInput(t) end
function love.focus(f) G.ImGui_ImplLove.Focus(f) end
@@ -1,21 +0,0 @@
-- Roblox launcher for app.lua (sync with examples/example_shared/default.project.json).
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
local ImGui = G.ImGui
ImGui.CreateContext()
local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.NavEnableKeyboard, G.ImGuiConfigFlags.DockingEnable)
G.ImGui_ImplRoblox.Init()
local App = require(ReplicatedStorage:WaitForChild("SharedApp"))(G)
App.Platform = "Roblox"
RunService.Stepped:Connect(function()
G.ImGui_ImplRoblox.NewFrame()
ImGui.NewFrame()
App.Frame()
ImGui.Render()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
end)
+1637 -284
View File
File diff suppressed because it is too large Load Diff
+8318 -4878
View File
File diff suppressed because it is too large Load Diff
+3297 -740
View File
File diff suppressed because it is too large Load Diff
+400 -69
View File
@@ -30,7 +30,7 @@
--- @meta --- @meta
local type = ImGui._GetTypeFunc() local type = type -- [Luau] builtin (FASTCALL); ImGui._GetTypeFunc() kept for compatibility
local rawget = rawget; local rawset = rawset local rawget = rawget; local rawset = rawset
-- [LuaBitOp](https://bitop.luajit.org/semantics.html) -- [LuaBitOp](https://bitop.luajit.org/semantics.html)
@@ -75,6 +75,14 @@ function ImU32(val) return bitAnd(val, 0xFFFFFFFF) end
--- @return ImS32 --- @return ImS32
function ImS32(val) return bitAnd(val, 0xFFFFFFFF) end function ImS32(val) return bitAnd(val, 0xFFFFFFFF) end
-- [Luau] 64-bit integers are stored in doubles (exact only within +-2^53): the cast just truncates toward zero like a C double->int64 cast.
--- @param val number
--- @return ImS64
function ImS64(val) if val >= 0 then return math.floor(val) end return -math.floor(-val) end
--- @param val number
--- @return ImU64
function ImU64(val) if val >= 0 then return math.floor(val) end return -math.floor(-val) end
--- @alias float number --- @alias float number
--- @class double : number --- @class double : number
@@ -155,15 +163,17 @@ ImGuiDataType =
U16 = 3, -- unsigned short U16 = 3, -- unsigned short
S32 = 4, -- int S32 = 4, -- int
U32 = 5, -- unsigned int U32 = 5, -- unsigned int
Float = 6, -- float S64 = 6, -- long long / __int64 -- [Luau] stored in a double: exact only within +-2^53
Double = 7, -- double U64 = 7, -- unsigned long long / unsigned __int64 -- [Luau] stored in a double: exact only within 0..2^53
Bool = 8, -- bool (provided for user convenience, not supported by scalar widgets) Float = 8, -- float
String = 9, -- string (provided for user convenience, not supported by scalar widgets) Double = 9, -- double
COUNT = 10 Bool = 10, -- bool (provided for user convenience, not supported by scalar widgets)
String = 11, -- string (provided for user convenience, not supported by scalar widgets)
COUNT = 12
} }
-- Extend ImGuiDataType_ -- Extend ImGuiDataType_
ImGuiDataType.Pointer = 11 -- [internal] ImGuiDataType.Pointer = ImGuiDataType.COUNT -- [internal]
ImGuiDataType.ID = 12 -- [internal] ImGuiDataType.ID = ImGuiDataType.COUNT + 1 -- [internal]
ImTextureID_Invalid = -1 ImTextureID_Invalid = -1
@@ -194,7 +204,8 @@ ImFontAtlasFlags =
None = 0, None = 0,
NoPowerOfTwoHeight = bitLShift(1, 0), -- Don't round the height to next power of two NoPowerOfTwoHeight = bitLShift(1, 0), -- Don't round the height to next power of two
NoMouseCursors = bitLShift(1, 1), -- Don't build software mouse cursors into the atlas (save a little texture memory) NoMouseCursors = bitLShift(1, 1), -- Don't build software mouse cursors into the atlas (save a little texture memory)
NoBakedLines = bitLShift(1, 2) -- Don't build anti-aliased line textures into the atlas (save a little texture memory). SINCE 1.93.0 THIS PREVENTS ANTI-ALIASED LINES FROM WORKING AND WILL DISABLE THEM. NoBakedLines = bitLShift(1, 2), -- Don't build anti-aliased line textures into the atlas (save a little texture memory). SINCE 1.93.0 THIS PREVENTS ANTI-ALIASED LINES FROM WORKING AND WILL DISABLE THEM.
NoBakedRoundCorners= bitLShift(1, 3), -- Don't build round corners into the atlas.
} }
-- A cardinal direction -- A cardinal direction
@@ -498,8 +509,8 @@ end
--- @field UserCallbackData any --- @field UserCallbackData any
--- @field UserCallbackDataSize int --- @field UserCallbackDataSize int
--- @field UserCallbackDataOffset int --- @field UserCallbackDataOffset int
local IM_DRAWCMD = {} MT.ImDrawCmd = {}
IM_DRAWCMD.__index = IM_DRAWCMD MT.ImDrawCmd.__index = MT.ImDrawCmd
--- @return ImDrawCmd --- @return ImDrawCmd
--- @nodiscard --- @nodiscard
@@ -514,11 +525,11 @@ function ImDrawCmd()
UserCallbackData = nil, -- 4-8 -- Callback user data (when UserCallback != nil). If called AddCallback() with size == 0, this is a copy of the AddCallback() argument. If called AddCallback() with size > 0, this is pointing to a buffer where data is stored. UserCallbackData = nil, -- 4-8 -- Callback user data (when UserCallback != nil). If called AddCallback() with size == 0, this is a copy of the AddCallback() argument. If called AddCallback() with size > 0, this is pointing to a buffer where data is stored.
UserCallbackDataSize = 0, -- 4 -- Size of callback user data when using storage, otherwise 0. UserCallbackDataSize = 0, -- 4 -- Size of callback user data when using storage, otherwise 0.
UserCallbackDataOffset = 0 -- 4 -- [Internal] Offset of callback user data when using storage, otherwise -1. UserCallbackDataOffset = 0 -- 4 -- [Internal] Offset of callback user data when using storage, otherwise -1.
}, IM_DRAWCMD) }, MT.ImDrawCmd)
end end
--- @return ImTextureID --- @return ImTextureID
function IM_DRAWCMD:GetTexID() function MT.ImDrawCmd:GetTexID()
local tex_id = (self.TexRef._TexData) and self.TexRef._TexData.TexID or self.TexRef._TexID local tex_id = (self.TexRef._TexData) and self.TexRef._TexData.TexID or self.TexRef._TexID
if self.TexRef._TexData ~= nil then if self.TexRef._TexData ~= nil then
IM_ASSERT(tex_id ~= ImTextureID_Invalid, "ImDrawCmd is referring to ImTextureData that wasn't uploaded to graphics system. Backend must call ImTextureData::SetTexID() after handling ImTextureStatus_WantCreate request!") IM_ASSERT(tex_id ~= ImTextureID_Invalid, "ImDrawCmd is referring to ImTextureData that wasn't uploaded to graphics system. Backend must call ImTextureData::SetTexID() after handling ImTextureStatus_WantCreate request!")
@@ -605,7 +616,7 @@ function ImDrawListSplitter() return setmetatable({ _Current = 0, _Count = 0, _C
--- @field CmdBuffer ImVector<ImDrawCmd> --- @field CmdBuffer ImVector<ImDrawCmd>
--- @field IdxBuffer ImVector<ImDrawIdx> --- @field IdxBuffer ImVector<ImDrawIdx>
--- @field VtxBuffer ImVector<ImDrawVert> --- @field VtxBuffer ImVector<ImDrawVert>
--- @field Flags ImDrawListFlags --- @field Flags ImDrawFlags # Current flags for drawing primitives. Alter with PushDrawFlag()/PopDrawFlag()
--- @field _VtxCurrentIdx unsigned_int # 1-based, generally == (VtxBuffer.Size + 1) --- @field _VtxCurrentIdx unsigned_int # 1-based, generally == (VtxBuffer.Size + 1)
--- @field _Data ImDrawListSharedData # Pointes to shared draw data --- @field _Data ImDrawListSharedData # Pointes to shared draw data
--- @field _VtxWritePtr unsigned_int # 1-based, points to the current writing vertex in VtxBuffer (see ImDrawVtxBuffer) --- @field _VtxWritePtr unsigned_int # 1-based, points to the current writing vertex in VtxBuffer (see ImDrawVtxBuffer)
@@ -616,7 +627,10 @@ function ImDrawListSplitter() return setmetatable({ _Current = 0, _Count = 0, _C
--- @field _ClipRectStack ImVector<ImVec4> --- @field _ClipRectStack ImVector<ImVec4>
--- @field _TextureStack ImVector<ImTextureRef> --- @field _TextureStack ImVector<ImTextureRef>
--- @field _CallbacksDataBuf any --- @field _CallbacksDataBuf any
--- @field _DrawFlagsStack ImVector<ImDrawFlags>
--- @field _FringeScale float --- @field _FringeScale float
--- @field _InvFringeScale float
--- @field _FringeScaleIsInteger bool
--- @field _OwnerName string --- @field _OwnerName string
MT.ImDrawList = {} MT.ImDrawList = {}
MT.ImDrawList.__index = MT.ImDrawList MT.ImDrawList.__index = MT.ImDrawList
@@ -669,9 +683,11 @@ function ImDrawList(data)
_Splitter = ImDrawListSplitter(), -- [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!) _Splitter = ImDrawListSplitter(), -- [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!)
_ClipRectStack = ImVector(), -- [Internal] _ClipRectStack = ImVector(), -- [Internal]
_TextureStack = ImVector(), -- [Internal] _TextureStack = ImVector(), -- [Internal]
_CallbacksDataBuf = nil, -- [Internal] _CallbacksDataBuf = ImVector(), -- [Internal] [Lua] copies of AddCallback() userdata (userdata_size > 0)
_DrawFlagsStack = ImVector(), -- [Internal] ImSmallStack_<ImDrawFlags,3>: capacity checked by PushDrawFlag()
_FringeScale = 0, -- [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content _FringeScale = 1.0, -- [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content -- [Luau] 1.0 instead of memset 0 so a list used before _SetPixelDensity() still works
_InvFringeScale = 1.0, -- [internal] 1.0 / _FringeScale // FIXME: Consider renaming to _PixelDensity.
_FringeScaleIsInteger = true, -- [Internal] true if 1/_FringeScale is a whole number, used to select fast path for rendering
_OwnerName = nil -- Pointer to owner window's name for debugging _OwnerName = nil -- Pointer to owner window's name for debugging
}, MT.ImDrawList) }, MT.ImDrawList)
@@ -750,6 +766,7 @@ function ImTextureData()
this.UniqueID = 0 -- w - -- [DEBUG] Sequential index to facilitate identifying a texture when debugging/printing. Unique per atlas. this.UniqueID = 0 -- w - -- [DEBUG] Sequential index to facilitate identifying a texture when debugging/printing. Unique per atlas.
this.Status = ImTextureStatus.Destroyed -- rw rw -- ImTextureStatus.OK/_WantCreate/_WantUpdates/_WantDestroy. Always use SetStatus() to modify! this.Status = ImTextureStatus.Destroyed -- rw rw -- ImTextureStatus.OK/_WantCreate/_WantUpdates/_WantDestroy. Always use SetStatus() to modify!
this.BackendUserData = nil -- - rw -- Convenience storage for backend. Some backends may have enough with TexID. this.BackendUserData = nil -- - rw -- Convenience storage for backend. Some backends may have enough with TexID.
this.QueueUserData = nil -- r - -- Convenience storage for a staged/multi-threaded rendering texture queue (e.g. imgui_threaded_rendering.h. See #8597). When ~= nil, core assumes the texture is referenced by the queue.
this.TexID = ImTextureID_Invalid -- r w -- Backend-specific texture identifier. Always use SetTexID() to modify! The identifier will stored in ImDrawCmd::GetTexID() and passed to backend's RenderDrawData function. this.TexID = ImTextureID_Invalid -- r w -- Backend-specific texture identifier. Always use SetTexID() to modify! The identifier will stored in ImDrawCmd::GetTexID() and passed to backend's RenderDrawData function.
this.Format = 0 -- w r -- ImTextureFormat.RGBA32 (default) or ImTextureFormat.Alpha8 this.Format = 0 -- w r -- ImTextureFormat.RGBA32 (default) or ImTextureFormat.Alpha8
this.Width = 0 -- w r -- Texture width this.Width = 0 -- w r -- Texture width
@@ -769,11 +786,14 @@ end
--- @param x int --- @param x int
--- @param y int --- @param y int
function MT.ImTextureData:GetPixels() IM_ASSERT(self.Pixels ~= nil); return self.Pixels end
function MT.ImTextureData:GetPixelsAt(x, y) function MT.ImTextureData:GetPixelsAt(x, y)
return self.Pixels, (x + y * self.Width) * self.BytesPerPixel + 1 return self.Pixels, (x + y * self.Width) * self.BytesPerPixel + 1
end end
function MT.ImTextureData:GetSizeInBytes() return self.Width * self.Height * self.BytesPerPixel end
function MT.ImTextureData:GetPitch() return self.Width * self.BytesPerPixel end function MT.ImTextureData:GetPitch() return self.Width * self.BytesPerPixel end
function MT.ImTextureData:GetTexRef() local tex_ref = ImTextureRef(); tex_ref._TexData = self; tex_ref._TexID = ImTextureID_Invalid; return tex_ref end
function MT.ImTextureData:GetTexID() return self.TexID end function MT.ImTextureData:GetTexID() return self.TexID end
--- @param tex_id ImTextureID --- @param tex_id ImTextureID
@@ -793,8 +813,8 @@ function MT.ImTextureData:SetStatus(status) self.Status = status if (status == I
--- @class ImTextureRef --- @class ImTextureRef
--- @field _TexData? ImTextureData --- @field _TexData? ImTextureData
--- @field _TexID ImTextureID --- @field _TexID ImTextureID
local IM_TEXTURE_REF = {} MT.ImTextureRef = {}
IM_TEXTURE_REF.__index = IM_TEXTURE_REF MT.ImTextureRef.__index = MT.ImTextureRef
--- @return ImTextureRef --- @return ImTextureRef
--- @nodiscard --- @nodiscard
@@ -803,7 +823,14 @@ function ImTextureRef(tex_id)
-- Members (either are set, never both!) -- Members (either are set, never both!)
_TexData = nil, -- A texture, generally owned by a ImFontAtlas. Will convert to ImTextureID during render loop, after texture has been uploaded. _TexData = nil, -- A texture, generally owned by a ImFontAtlas. Will convert to ImTextureID during render loop, after texture has been uploaded.
_TexID = tex_id or ImTextureID_Invalid -- _OR_ Low-level backend texture identifier, if already uploaded or created by user/app. Generally provided to e.g. ImGui.Image() calls. _TexID = tex_id or ImTextureID_Invalid -- _OR_ Low-level backend texture identifier, if already uploaded or created by user/app. Generally provided to e.g. ImGui.Image() calls.
}, IM_TEXTURE_REF) }, MT.ImTextureRef)
end
-- This is provided for consistency (but we don't actually use this)
--- @return ImTextureID
function MT.ImTextureRef:GetTexID()
IM_ASSERT(not (self._TexData ~= nil and self._TexID ~= ImTextureID_Invalid))
if self._TexData then return self._TexData.TexID else return self._TexID end
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@@ -1132,6 +1159,34 @@ function ImFontGlyph()
} }
end end
-- Helper to build glyph ranges from text/string data. Feed your application strings/characters to it then call BuildRanges().
-- This is essentially a tightly packed of vector of 64k booleans = 8KB storage.
--- @class ImFontGlyphRangesBuilder
--- @field UsedChars ImVector<ImU32> # Store 1-bit per Unicode code point (0=unused, 1=used)
MT.ImFontGlyphRangesBuilder = {}
MT.ImFontGlyphRangesBuilder.__index = MT.ImFontGlyphRangesBuilder
--- @return ImFontGlyphRangesBuilder
--- @nodiscard
function ImFontGlyphRangesBuilder()
local this = setmetatable({ UsedChars = ImVector() }, MT.ImFontGlyphRangesBuilder)
this:Clear()
return this
end
function MT.ImFontGlyphRangesBuilder:Clear()
local size_in_bytes = (IM_UNICODE_CODEPOINT_MAX + 1) / 8
self.UsedChars:resize(size_in_bytes / 4)
local data = self.UsedChars.Data
for i = 1, self.UsedChars.Size do data[i] = 0 end
end
-- Get bit n in the array
function MT.ImFontGlyphRangesBuilder:GetBit(n) local off = bit32.rshift(n, 5); local mask = bit32.lshift(1, bit32.band(n, 31)); return bit32.band(self.UsedChars.Data[off + 1], mask) ~= 0 end
-- Set bit n in the array
function MT.ImFontGlyphRangesBuilder:SetBit(n) local off = bit32.rshift(n, 5); local mask = bit32.lshift(1, bit32.band(n, 31)); self.UsedChars.Data[off + 1] = bit32.bor(self.UsedChars.Data[off + 1], mask) end
-- Add character
function MT.ImFontGlyphRangesBuilder:AddChar(c) self:SetBit(c) end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- [SECTION] ImGuiIO -- [SECTION] ImGuiIO
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@@ -1190,6 +1245,8 @@ function ImGuiIO()
-- Other state maintained from data above + IO function calls -- Other state maintained from data above + IO function calls
KeyMods = nil, -- Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags). Read-only, updated by NewFrame() KeyMods = nil, -- Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags). Read-only, updated by NewFrame()
BackendPlatformName = nil, -- = NULL
BackendRendererName = nil, -- = NULL
BackendFlags = ImGuiBackendFlags.None, -- = 0 -- See ImGuiBackendFlags_ enum. Set by backend (imgui_impl_xxx files or custom backend) to communicate features supported by the backend. BackendFlags = ImGuiBackendFlags.None, -- = 0 -- See ImGuiBackendFlags_ enum. Set by backend (imgui_impl_xxx files or custom backend) to communicate features supported by the backend.
ConfigFlags = ImGuiConfigFlags.None, -- = 0 -- See ImGuiConfigFlags_ enum. Set by user/application. Keyboard/Gamepad navigation options, etc. ConfigFlags = ImGuiConfigFlags.None, -- = 0 -- See ImGuiConfigFlags_ enum. Set by user/application. Keyboard/Gamepad navigation options, etc.
DisplaySize = ImVec2(-1.0, -1.0), -- <unset> -- Main display size, in pixels (== GetMainViewport()->Size). May change every frame. DisplaySize = ImVec2(-1.0, -1.0), -- <unset> -- Main display size, in pixels (== GetMainViewport()->Size). May change every frame.
@@ -1198,6 +1255,9 @@ function ImGuiIO()
DisplayFramebufferScale = ImVec2(1.0, 1.0), -- = (1, 1) -- Main display density. For retina display where window coordinates are different from framebuffer coordinates. This will affect font density + will end up in ImDrawData::FramebufferScale. DisplayFramebufferScale = ImVec2(1.0, 1.0), -- = (1, 1) -- Main display density. For retina display where window coordinates are different from framebuffer coordinates. This will affect font density + will end up in ImDrawData::FramebufferScale.
-- Font system
FontAllowUserScaling = false, -- = false -- Allow user scaling text of individual window with Ctrl+Wheel.
-- Main Input State -- Main Input State
-- (this block used to be written by backend, since 1.87 it is best to NOT write to those directly, call the AddXXX functions above instead) -- (this block used to be written by backend, since 1.87 it is best to NOT write to those directly, call the AddXXX functions above instead)
-- (reading from those variables is fair game, as they are extremely unlikely to be moving anywhere) -- (reading from those variables is fair game, as they are extremely unlikely to be moving anywhere)
@@ -1208,12 +1268,14 @@ function ImGuiIO()
MouseDelta = ImVec2(), -- Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. MouseDelta = ImVec2(), -- Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta.
MouseDown = {[0] = false, [1] = false, [2] = false}, -- Mouse buttons: 0=left, 1=right, 2=middle + extras (ImGuiMouseButton.COUNT == 5). Dear ImGui mostly uses left and right buttons. Other buttons allow us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. MouseDown = {[0] = false, [1] = false, [2] = false, [3] = false, [4] = false}, -- Mouse buttons: 0=left, 1=right, 2=middle + extras (ImGuiMouseButton.COUNT == 5). Dear ImGui mostly uses left and right buttons. Other buttons allow us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API.
MouseWheel = 0, -- Mouse wheel Vertical: 1 unit scrolls about 5 lines text. >0 scrolls Up, <0 scrolls Down. Hold Shift to turn vertical scroll into horizontal scroll. MouseWheel = 0, -- Mouse wheel Vertical: 1 unit scrolls about 5 lines text. >0 scrolls Up, <0 scrolls Down. Hold Shift to turn vertical scroll into horizontal scroll.
MouseWheelH = 0, -- Mouse wheel Horizontal. >0 scrolls Left, <0 scrolls Right. Most users don't have a mouse with a horizontal wheel, may not be filled by all backends. MouseWheelH = 0, -- Mouse wheel Horizontal. >0 scrolls Left, <0 scrolls Right. Most users don't have a mouse with a horizontal wheel, may not be filled by all backends.
MouseCtrlLeftAsRightClick = false, -- (OSX) Set to true when the current click was a Ctrl+Click that spawned a simulated right click MouseCtrlLeftAsRightClick = false, -- (OSX) Set to true when the current click was a Ctrl+Click that spawned a simulated right click
MouseSource = ImGuiMouseSource.Mouse, -- Mouse actual input peripheral (Mouse/TouchScreen/Pen).
PenPressure = 0.0, -- Touch/Pen pressure (0.0f to 1.0f, should be >0.0f only when MouseDown[0] == true). Helper storage currently unused by Dear ImGui.
MouseWheelRequestAxisSwap = false, -- On a non-Mac system, holding Shift requests WheelY to perform the equivalent of a WheelX event. On a Mac system this is already enforced by the system. MouseWheelRequestAxisSwap = false, -- On a non-Mac system, holding Shift requests WheelY to perform the equivalent of a WheelX event. On a Mac system this is already enforced by the system.
@@ -1229,6 +1291,25 @@ function ImGuiIO()
ConfigDebugIniSettings = false, -- = false -- Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower) ConfigDebugIniSettings = false, -- = false -- Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower)
ConfigWindowsCopyContentsWithCtrlC = false, -- = false -- [EXPERIMENTAL] Ctrl+C copy the contents of focused window into the clipboard. Experimental because: (1) has known issues with nested Begin/End pairs (2) text output quality varies (3) text output is in submission order rather than spatial order. ConfigWindowsCopyContentsWithCtrlC = false, -- = false -- [EXPERIMENTAL] Ctrl+C copy the contents of focused window into the clipboard. Experimental because: (1) has known issues with nested Begin/End pairs (2) text output quality varies (3) text output is in submission order rather than spatial order.
ConfigWindowsMoveFromTitleBarOnly = false, -- = false -- Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. ConfigWindowsMoveFromTitleBarOnly = false, -- = false -- Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar.
ConfigScrollbarScrollByPage = true, -- = true -- Enable scrolling page by page when clicking outside the scrollbar grab. When disabled, always scroll to clicked location. When enabled, Shift+Click scrolls to clicked location.
ConfigInputTextCursorBlink = true, -- = true -- Enable blinking cursor (optional as some users consider it to be distracting).
ConfigInputTextEnterKeepActive = false, -- = false -- [BETA] Pressing Enter will reactivate item and select all text (single-line only). Ctrl+Enter or Shift+Enter will deactivate normally.
ConfigDragClickToInputText = false, -- = false -- [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard.
-- Keyboard/Gamepad Navigation options
ConfigNavSwapGamepadButtons = false, -- = false -- Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout.
ConfigNavMoveSetMousePos = false, -- = false -- Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult. Will update io.MousePos and set io.WantSetMousePos=true.
ConfigNavCaptureKeyboard = true, -- = true -- Sets io.WantCaptureKeyboard when io.NavActive is set.
ConfigNavEscapeClearFocusItem = true, -- = true -- Pressing Escape can clear focused item + navigation id/highlight. Set to false if you want to always keep highlight on.
ConfigNavEscapeClearFocusWindow = false, -- = false -- Pressing Escape can clear focused window as well (super set of io.ConfigNavEscapeClearFocusItem).
-- DPI/Scaling options
ConfigDpiScaleFonts = false, -- = false -- [EXPERIMENTAL] Automatically overwrite style.FontScaleDpi when Monitor DPI changes. This will scale fonts but _NOT_ scale sizes/padding for now.
ConfigDpiScaleViewports = false, -- = false -- [EXPERIMENTAL] Scale Dear ImGui and Platform Windows when Monitor DPI changes.
-- Ini Settings options
ConfigIniSettingsSaveLastUsedDate = true, -- = true -- Enable loading/saving last used day (YYYYMMDD) in some .ini struct, making things easier to audit and allowing custom tools to cleanup old data.
ConfigIniSettingsAutoDiscardMonths = 0, -- = 0 -- [BETA] Set number of months after which unused .ini entries are discarded on load. Require platform_io.Platform_SessionDate to be set. For systems supporting the feature, .ini entries without a LastUsed field will always be discarded! Please report if you are using this.
-- Options to configure Error Handling and how we handle recoverable errors [EXPERIMENTAL] -- Options to configure Error Handling and how we handle recoverable errors [EXPERIMENTAL]
-- - Error recovery is provided as a way to facilitate: -- - Error recovery is provided as a way to facilitate:
-- - Recovery after a programming error (native code or scripting language - the latter tends to facilitate iterating on code while running). -- - Recovery after a programming error (native code or scripting language - the latter tends to facilitate iterating on code while running).
@@ -1253,6 +1334,16 @@ function ImGuiIO()
-- - See FAQ https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system -- - See FAQ https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system
ConfigDebugHighlightIdConflicts = false, -- Highlight and show an error message popup when multiple items have conflicting identifiers. ConfigDebugHighlightIdConflicts = false, -- Highlight and show an error message popup when multiple items have conflicting identifiers.
ConfigDebugHighlightIdConflictsShowItemPicker = true, -- =true -- Show "Item Picker" button in aforementioned popup. ConfigDebugHighlightIdConflictsShowItemPicker = true, -- =true -- Show "Item Picker" button in aforementioned popup.
ConfigDebugIsDebuggerPresent = false, -- = false -- Enable various tools calling IM_DEBUG_BREAK().
-- Tools to test correct Begin/End and BeginChild/EndChild behaviors.
ConfigDebugBeginReturnValueOnce = false, -- = false -- First-time calls to Begin()/BeginChild() will return false. NEEDS TO BE SET AT APPLICATION BOOT TIME if you don't want to miss windows.
ConfigDebugBeginReturnValueLoop = false, -- = false -- Some calls to Begin()/BeginChild() will return false. Will cycle through window depths then repeat. Suggested use: add "io.ConfigDebugBeginReturnValue = io.KeyShift" in your main loop then occasionally press SHIFT. Windows should be flickering while running.
-- Option to deactivate io.AddFocusEvent(false) handling.
ConfigDebugIgnoreFocusLoss = false, -- = false -- Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys()/io.ClearInputMouse() in input processing.
-- Option to compare < 1.93.0 rendering details vs 1.93.0 rendering details. (#9504)
-- - Equivalent to calling PushDrawFlag(ImDrawList_StrokeLegacy,true) on every draw list instances.
-- - Suggested use, e.g. io.ConfigDebugDrawListDefaultsToStrokeLegacy = io.KeyShift; // Map to SHIFT modifier
ConfigDebugDrawListDefaultsToStrokeLegacy = false, -- false -- Default all ImDrawList to ImDrawList_StrokeLegacy mode, mimicking pre-1.93.0 rendering.
IniFilename = "imgui.ini", -- Roblox: no disk access, only used as a flag (nil disables .ini handling) IniFilename = "imgui.ini", -- Roblox: no disk access, only used as a flag (nil disables .ini handling)
IniSavingRate = 5.0, -- = 5.0f -- Minimum time between saving positions/sizes to .ini file, in seconds. IniSavingRate = 5.0, -- = 5.0f -- Minimum time between saving positions/sizes to .ini file, in seconds.
@@ -1279,23 +1370,23 @@ function ImGuiIO()
-- (you can visualize and interact with all options in 'Demo->Configuration') -- (you can visualize and interact with all options in 'Demo->Configuration')
MouseDrawCursor = false, -- = false -- Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by backend implementations. MouseDrawCursor = false, -- = false -- Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by backend implementations.
MouseClicked = {[0] = false, [1] = false, [2] = false}, -- Mouse button went from !Down to Down (same as MouseClickedCount[x] != 0) MouseClicked = {[0] = false, [1] = false, [2] = false, [3] = false, [4] = false}, -- Mouse button went from !Down to Down (same as MouseClickedCount[x] != 0)
MouseReleased = {[0] = false, [1] = false, [2] = false}, -- Mouse button went from Down to !Down MouseReleased = {[0] = false, [1] = false, [2] = false, [3] = false, [4] = false}, -- Mouse button went from Down to !Down
MouseClickedCount = {[0] = 0, [1] = 0, [2] = 0}, -- == 0 (not clicked), == 1 (same as MouseClicked[]), == 2 (double-clicked), == 3 (triple-clicked) etc. when going from !Down to Down MouseClickedCount = {[0] = 0, [1] = 0, [2] = 0, [3] = 0, [4] = 0}, -- == 0 (not clicked), == 1 (same as MouseClicked[]), == 2 (double-clicked), == 3 (triple-clicked) etc. when going from !Down to Down
MouseClickedLastCount = {[0] = 0, [1] = 0, [2] = 0}, -- Count successive number of clicks. Stays valid after mouse release. Reset after another click is done. MouseClickedLastCount = {[0] = 0, [1] = 0, [2] = 0, [3] = 0, [4] = 0}, -- Count successive number of clicks. Stays valid after mouse release. Reset after another click is done.
MouseDownDuration = {[0] = -1, [1] = -1, [2] = -1}, -- Duration the mouse button has been down (0.0f == just clicked) MouseDownDuration = {[0] = -1, [1] = -1, [2] = -1, [3] = -1, [4] = -1}, -- Duration the mouse button has been down (0.0f == just clicked)
MouseDownDurationPrev = {[0] = -1, [1] = -1, [2] = -1}, -- Previous time the mouse button has been down MouseDownDurationPrev = {[0] = -1, [1] = -1, [2] = -1, [3] = -1, [4] = -1}, -- Previous time the mouse button has been down
MouseDragMaxDistanceAbs = {[0] = ImVec2(), [1] = ImVec2(), [2] = ImVec2()}, -- Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point MouseDragMaxDistanceAbs = {[0] = ImVec2(), [1] = ImVec2(), [2] = ImVec2(), [3] = ImVec2(), [4] = ImVec2()}, -- Maximum distance, absolute, on each axis, of how much mouse has traveled from the clicking point
MouseDragMaxDistanceSqr = {[0] = 0, [1] = 0, [2] = 0}, -- Squared maximum distance of how much mouse has traveled from the clicking point (used for moving thresholds) MouseDragMaxDistanceSqr = {[0] = 0, [1] = 0, [2] = 0, [3] = 0, [4] = 0}, -- Squared maximum distance of how much mouse has traveled from the clicking point (used for moving thresholds)
MouseDownOwned = {[0] = nil, [1] = nil, [2] = nil}, -- Track if button was clicked inside a dear imgui window or over void blocked by a popup. We don't request mouse capture from the application if click started outside ImGui bounds. MouseDownOwned = {[0] = nil, [1] = nil, [2] = nil, [3] = nil, [4] = nil}, -- Track if button was clicked inside a dear imgui window or over void blocked by a popup. We don't request mouse capture from the application if click started outside ImGui bounds.
MouseDownOwnedUnlessPopupClose = {[0] = nil, [1] = nil, [2] = nil}, -- Track if button was clicked inside a dear imgui window. MouseDownOwnedUnlessPopupClose = {[0] = nil, [1] = nil, [2] = nil, [3] = nil, [4] = nil}, -- Track if button was clicked inside a dear imgui window.
MouseClickedTime = {[0] = 0, [1] = 0, [2] = 0}, -- Time of last click (used to figure out double-click) MouseClickedTime = {[0] = 0, [1] = 0, [2] = 0, [3] = 0, [4] = 0}, -- Time of last click (used to figure out double-click)
MouseReleasedTime = {[0] = 0, [1] = 0, [2] = 0}, -- Time of last released (rarely used! but useful to handle delayed single-click when trying to disambiguate them from double-click). MouseReleasedTime = {[0] = 0, [1] = 0, [2] = 0, [3] = 0, [4] = 0}, -- Time of last released (rarely used! but useful to handle delayed single-click when trying to disambiguate them from double-click).
MouseClickedPos = {[0] = ImVec2(), [1] = ImVec2(), [2] = ImVec2()}, -- Position at time of clicking MouseClickedPos = {[0] = ImVec2(), [1] = ImVec2(), [2] = ImVec2(), [3] = ImVec2(), [4] = ImVec2()}, -- Position at time of clicking
MouseDoubleClicked = {[0] = false, [1] = false, [2] = false}, -- Has mouse button been double-clicked? (same as MouseClickedCount[x] == 2) MouseDoubleClicked = {[0] = false, [1] = false, [2] = false, [3] = false, [4] = false}, -- Has mouse button been double-clicked? (same as MouseClickedCount[x] == 2)
-- Inputs Behaviors -- Inputs Behaviors
-- (other variables, ones which are expected to be tweaked within UI code, are exposed in ImGuiStyle) -- (other variables, ones which are expected to be tweaked within UI code, are exposed in ImGuiStyle)
@@ -1463,6 +1554,7 @@ function ImGuiViewport()
PlatformHandle = nil, -- void* to hold higher-level, platform window handle (e.g. HWND for Win32 backend, Uint32 WindowID for SDL, GLFWWindow* for GLFW), for FindViewportByPlatformHandle(). PlatformHandle = nil, -- void* to hold higher-level, platform window handle (e.g. HWND for Win32 backend, Uint32 WindowID for SDL, GLFWWindow* for GLFW), for FindViewportByPlatformHandle().
PlatformHandleRaw = nil, -- void* to hold lower-level, platform-native window handle (always HWND on Win32 platform, unused for other platforms). PlatformHandleRaw = nil, -- void* to hold lower-level, platform-native window handle (always HWND on Win32 platform, unused for other platforms).
PlatformIconData = nil, -- [EXPERIMENTAL] Pass opaque data for Platform backend to handle.
PlatformWindowCreated = false -- Platform window has been created (Platform_CreateWindow() has been called). This is false during the first frame where a viewport is being created. PlatformWindowCreated = false -- Platform window has been created (Platform_CreateWindow() has been called). This is false during the first frame where a viewport is being created.
}, MT.ImGuiViewport) }, MT.ImGuiViewport)
end end
@@ -1494,12 +1586,24 @@ function ImGuiPlatformIO()
-- (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) -- (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures)
Platform_GetClipboardTextFn = nil, -- Should return nil on failure (e.g. clipboard data is not text). Platform_GetClipboardTextFn = nil, -- Should return nil on failure (e.g. clipboard data is not text).
Platform_SetClipboardTextFn = nil, Platform_SetClipboardTextFn = nil,
Platform_ClipboardUserData = nil,
-- Optional: Open link/folder/file in OS Shell -- Optional: Open link/folder/file in OS Shell
-- (default to use ShellExecuteW() on Windows, system() on Linux/Mac. expected to return false on failure, but some platforms may always return true) -- (default to use ShellExecuteW() on Windows, system() on Linux/Mac. expected to return false on failure, but some platforms may always return true)
Platform_OpenInShellFn = nil, Platform_OpenInShellFn = nil,
Platform_OpenInShellUserData = nil, Platform_OpenInShellUserData = nil,
-- Optional: Notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME on Windows)
Platform_SetImeDataFn = nil, -- fun(ctx, viewport, data)
Platform_ImeUserData = nil,
-- Optional: Platform locale
-- [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), generally pulled from *localeconv()->decimal_point
Platform_LocaleDecimalPoint = 46, -- '.'
-- Optional: Session date
Platform_SessionDate = 0, -- Integer storing YYYYMMDD e.g. 20261231 corresponding to the beginning of application session.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Input - Interface with Renderer Backend -- Input - Interface with Renderer Backend
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@@ -1511,6 +1615,12 @@ function ImGuiPlatformIO()
-- Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure. -- Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure.
Renderer_RenderState = nil, Renderer_RenderState = nil,
-- Optional: Standard draw callbacks (set by backends that support them)
DrawCallback_ResetRenderState = nil, -- Request to reset the graphics/render state.
DrawCallback_SetSamplerLinear = nil, -- Request backend to set texture sampling to Linear.
DrawCallback_SetSamplerNearest = nil, -- Request backend to set texture sampling to Nearest/Point.
DrawCallback_SetSamplerFromTex = nil, -- Request backend to use sampler associated to texture - only available in some backends: OpenGL2/3 and SDLRenderer3.
-- (Optional) Monitor list -- (Optional) Monitor list
-- - Updated by: app/backend. Update every frame to dynamically support changing monitor or DPI configuration. -- - Updated by: app/backend. Update every frame to dynamically support changing monitor or DPI configuration.
-- - Used by: dear imgui to query DPI info, clamp popups/tooltips within same monitor and not have them straddle monitors. -- - Used by: dear imgui to query DPI info, clamp popups/tooltips within same monitor and not have them straddle monitors.
@@ -1522,10 +1632,6 @@ function ImGuiPlatformIO()
-- (in the future we will attempt to organize this feature to remove the need for a "main viewport") -- (in the future we will attempt to organize this feature to remove the need for a "main viewport")
Viewports = ImVector(), -- Main viewports, followed by all secondary viewports. Viewports = ImVector(), -- Main viewports, followed by all secondary viewports.
-- Optional: Platform locale
-- [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), generally pulled from *localeconv()->decimal_point
Platform_LocaleDecimalPoint = 46, -- '.'
Platform_OnChangedViewport = nil, -- (Optional) [BETA] FIXME-DPI: DPI handling: Called during Begin() every time the viewport we are outputting into changes, so backend has a chance to swap fonts to adjust style. Platform_OnChangedViewport = nil, -- (Optional) [BETA] FIXME-DPI: DPI handling: Called during Begin() every time the viewport we are outputting into changes, so backend has a chance to swap fonts to adjust style.
} }
@@ -1687,35 +1793,90 @@ ImGuiNextItemDataFlags = {
--- @enum ImDrawFlags --- @enum ImDrawFlags
ImDrawFlags = { ImDrawFlags = {
None = 0, None = 0,
-- - Rounding default to ImDrawFlags.RoundCornersAll when 'rounding > 0'. --ImDrawFlags_Closed = 1, // -> FLAG MOVED BELOW. Prior to 1.92.8 (May 2026): ImDrawFlags_Closed was guaranteed to be == (1<<0) == 1, for legacy compatibility reason. Hardcoded use of 1 or true should be replaced with 'ImDrawFlags_Closed'.
-- - So you only need to use the ImDrawFlags.RoundCornersXXX flags if you want a special configuration (e.g. a rectangle with one rounded corner).
-- About usage of flags:
-- - "Prim" column: 'OK' = flag can be used to configure an individual AddXXX() call.
-- - "Scope" column: 'OK' = initialized by ImGui based on Style options (e.g. whether anti-aliased is enabled) + may be modified using ImDrawList::PushDrawFlag().
-- OK(0)/OK(1) indicates whether this flag is set in the default ImGui Style settings.
-- - Rounding default to ImDrawFlags_RoundAll when 'rounding > 0'.
-- - So you only need to use the ImDrawFlags_RoundXXX flags if you want a special configuration (e.g. a rectangle with one rounded corner).
-- Rounding for AddRectXXX(), PathRect() ------ -- Prim/Scope? -- Rounding for AddRectXXX(), PathRect() ------ -- Prim/Scope?
RoundCornersTopLeft = bitLShift(1, 4), -- OK -- -- Round top-left corner only (when 'rounding > 0.0f', we default to all corners). RoundTopLeft = bitLShift(1, 4), -- OK -- -- Round top-left corner only (when 'rounding > 0.0f', we default to all corners).
RoundCornersTopRight = bitLShift(1, 5), -- OK -- -- Round top-right corner only (when 'rounding > 0.0f', we default to all corners). RoundTopRight = bitLShift(1, 5), -- OK -- -- Round top-right corner only (when 'rounding > 0.0f', we default to all corners).
RoundCornersBottomLeft = bitLShift(1, 6), -- OK -- -- Round bottom-left corner only (when 'rounding > 0.0f', we default to all corners). RoundBottomLeft = bitLShift(1, 6), -- OK -- -- Round bottom-left corner only (when 'rounding > 0.0f', we default to all corners).
RoundCornersBottomRight = bitLShift(1, 7), -- OK -- -- Round bottom-right corner only (when 'rounding > 0.0f', we default to all corners). RoundBottomRight = bitLShift(1, 7), -- OK -- -- Round bottom-right corner only (when 'rounding > 0.0f', we default to all corners).
RoundCornersNone = bitLShift(1, 8), -- OK -- -- Disable rounding even when 'rounding > 0.0f'. This value is NOT zero, it is NOT an implicit flag! RoundNone = bitLShift(1, 8), -- OK -- -- Disable rounding even when 'rounding > 0.0f'. This value is NOT zero, it is NOT an implicit flag!
-- Stroke Options ----------------------------- -- Prim/Scope? -- Stroke Options ----------------------------- -- Prim/Scope?
Closed = bitLShift(1, 9) -- OK -- -- PathStroke(), AddPolyline(): specify that shape should be closed. Closed = bitLShift(1, 9), -- OK -- -- PathStroke(), AddPolyline(): specify that shape should be closed.
JoinMiter = bitLShift(1, 10), -- OK -- -- PathStroke(), AddPolyline(): use miter joins/corners only. This assumes that the input polyline does not have corners sharper than 90 degrees. Slightly faster.
CapSquare = bitLShift(1, 11), -- OK -- -- PathStroke(), AddPolyline(): use square cap line ends.
-- About Stroke Ends:
-- - Rendering is optimized for fast pixel-perfect UI, so line ends are not anti-aliased by default. It's cheaper (we can emit less vertices).
-- - For more free-form drawings, light graphs and markers, or when using thick strokes: you can turn them on using the ImDrawFlags_AALineEnds flag.
-- - See 'Demo->Examples->Custom Rendering' to interactively toy with those flags.
-- - 'OK*' indicates using this at the AddXXX() call site is unlikely: it would only makes sense if (1) the option is disabled in style/scope and (2) you want to forcefully enable it for a single primitives. Possible but unlikely! Only supported for functions taking flags inputs.
-- Stroke/Fill Anti-aliasing ------------------ -- Prim/Scope?
AAFill = bitLShift(1, 12), -- OK* OK(1) -- Enable anti-aliasing for Filled shapes.
AALines = bitLShift(1, 13), -- OK* OK(1) -- Enable anti-aliasing for Strokes (lines, borders).
AALineEnds = bitLShift(1, 14), -- OK OK(0) -- Enable anti-aliasing for Strokes Ends. Requires AALines to also be enabled. Useful on thick strokes or for precise continuity of multiple lines. A little more costly.
--ImDrawFlags_NoAA = 1 << 15, // OK -- // Disable anti-aliasing for a given primitive.
--ImDrawFlags_NoAALineEnds = 1 << 16, // OK -- // Disable anti-aliasing ends for a given primitive.
-- About Stroke Position:
-- - Read https://github.com/ocornut/imgui/wiki/Draw-List (this guide includes a precise list of differences between 1.92.9 and 1.93.0)
-- - Read https://github.com/ocornut/imgui/wiki/Pixel-Perfect-Rendering
-- Stroke Position relative to shape outline -- -- Prim/Scope?
StrokeInside = bitLShift(1, 17), -- OK -- -- Draw stroke inside of the shape outline (default for closed shapes and AddLineH, AddLineV functions)
StrokeCenter = bitLShift(2, 17), -- OK -- -- Draw stroke at the center of the shape outline (default for paths, bezier, and AddLine functions)
StrokeCenterBiased = bitLShift(3, 17), -- OK -- -- Draw stroke at the center of the shape outline, so that half thickness rounded down will be outside, and the rest inside the shape outline. Useful for axis-aligned shapes: AddLineH, AddLineV, AddRect. Does not animate well!
StrokeOutside = bitLShift(4, 17), -- OK -- -- Draw stroke outside of the shape outline
StrokeLegacy = bitLShift(7, 17), -- OK OK(0) -- Use legacy positioning + enable JoinMiter + disable AALineEnds. Must be all bits set.
-- Other Options ------------------------------ -- Prim/Scope?
TextNoPixelSnap = bitLShift(1, 20), -- OK OK(0) -- Disable automatically snapping AddText() calls to pixel boundaries.
UseTexForRoundCorners = bitLShift(1, 21), -- -- OK(1) -- Enable using textures instead of strokes to draw rounded corners/circles where possible (faster). Used by default unless 'ImFontAtlasFlags_NoBakedRoundCorners' is enabled in the font atlas.
UseVtxOffset = bitLShift(1, 22), -- -- OK(1) -- Can emit 'VtxOffset > 0' to allow large meshes with 16-bit ImDrawIdx. Used by default when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled by the backend.
AllowTexForRoundCorners_= bitLShift(1, 23), -- -- OK(1) -- [Internal]
} }
ImDrawFlags.RoundAll = bitOr(ImDrawFlags.RoundTopLeft, ImDrawFlags.RoundTopRight, ImDrawFlags.RoundBottomLeft, ImDrawFlags.RoundBottomRight) -- (Default!!)
ImDrawFlags.RoundTop = bitOr(ImDrawFlags.RoundTopLeft, ImDrawFlags.RoundTopRight)
ImDrawFlags.RoundBottom = bitOr(ImDrawFlags.RoundBottomLeft, ImDrawFlags.RoundBottomRight)
ImDrawFlags.RoundLeft = bitOr(ImDrawFlags.RoundBottomLeft, ImDrawFlags.RoundTopLeft)
ImDrawFlags.RoundRight = bitOr(ImDrawFlags.RoundBottomRight, ImDrawFlags.RoundTopRight)
ImDrawFlags.RoundCornersTop = bitOr(ImDrawFlags.RoundCornersTopLeft, ImDrawFlags.RoundCornersTopRight) -- [Internal]
ImDrawFlags.RoundCornersBottom = bitOr(ImDrawFlags.RoundCornersBottomLeft, ImDrawFlags.RoundCornersBottomRight) ImDrawFlags.RoundMask_ = bitOr(ImDrawFlags.RoundAll, ImDrawFlags.RoundNone) -- [Internal]
ImDrawFlags.RoundCornersLeft = bitOr(ImDrawFlags.RoundCornersBottomLeft, ImDrawFlags.RoundCornersTopLeft) ImDrawFlags.AllowInPushScope_ = bitOr(ImDrawFlags.AAFill, ImDrawFlags.AALines, ImDrawFlags.AALineEnds, ImDrawFlags.StrokeLegacy, ImDrawFlags.TextNoPixelSnap, ImDrawFlags.UseTexForRoundCorners, ImDrawFlags.UseVtxOffset, ImDrawFlags.RoundMask_) -- [Internal] Values allowed in PushDrawFlag() scope.
ImDrawFlags.RoundCornersRight = bitOr(ImDrawFlags.RoundCornersBottomRight, ImDrawFlags.RoundCornersTopRight) ImDrawFlags.AllowInFrameScope_ = bitOr(ImDrawFlags.AllowInPushScope_, ImDrawFlags.AllowTexForRoundCorners_)
ImDrawFlags.RoundCornersAll = bitOr(ImDrawFlags.RoundCornersTopLeft, ImDrawFlags.RoundCornersTopRight, ImDrawFlags.RoundCornersBottomLeft, ImDrawFlags.RoundCornersBottomRight) -- (Default!!) ImDrawFlags.StrokeMask_ = bitLShift(0x07, 17) -- [Internal]
ImDrawFlags.RoundCornersDefault_ = ImDrawFlags.RoundCornersAll ImDrawFlags.InvalidMask_ = 0x8000000F -- [Internal] == ~0x7FFFFFF0 == 0x8000000F. Reserved to detect misuses.
ImDrawFlags.RoundCornersMask_ = bitOr(ImDrawFlags.RoundCornersAll, ImDrawFlags.RoundCornersNone) -- [Internal]
ImDrawFlags.InvalidMask_ = 0x8000000F -- [Internal] == 0x8000000F. Reserved to detect misuses.
-- Obsolete names
-- RENAMED in 1.93.0 (Oct 2026): ImDrawFlags_RoundCornersXXXX -> ImDrawFlags_RoundXXXX.
ImDrawFlags.RoundCornersTopLeft = ImDrawFlags.RoundTopLeft
ImDrawFlags.RoundCornersTopRight = ImDrawFlags.RoundTopRight
ImDrawFlags.RoundCornersBottomLeft = ImDrawFlags.RoundBottomLeft
ImDrawFlags.RoundCornersBottomRight = ImDrawFlags.RoundBottomRight
ImDrawFlags.RoundCornersNone = ImDrawFlags.RoundNone
ImDrawFlags.RoundCornersAll = ImDrawFlags.RoundAll
ImDrawFlags.RoundCornersTop = ImDrawFlags.RoundTop
ImDrawFlags.RoundCornersBottom = ImDrawFlags.RoundBottom
ImDrawFlags.RoundCornersLeft = ImDrawFlags.RoundLeft
ImDrawFlags.RoundCornersRight = ImDrawFlags.RoundRight
-- -- RENAMED in 1.93.0: Merged ImDrawListFlags into ImDrawFlags.
--- @enum ImDrawListFlags --- @enum ImDrawListFlags
ImDrawListFlags = { ImDrawListFlags = {
None = 0, None = ImDrawFlags.None,
AntiAliasedLines = bitLShift(1, 0), AntiAliasedFill = ImDrawFlags.AAFill,
AntiAliasedLinesUseTex = bitLShift(1, 1), AntiAliasedLines = ImDrawFlags.AALines,
AntiAliasedFill = bitLShift(1, 2), AntiAliasedLinesUseTex = ImDrawFlags.AALines, -- No effect, anti-aliased rendering always uses textures from 1.93+
AllowVtxOffset = bitLShift(1, 3), AllowVtxOffset = ImDrawFlags.UseVtxOffset,
TextNoPixelSnap = bitLShift(1, 4) TextNoPixelSnap = ImDrawFlags.TextNoPixelSnap,
} }
-- Font flags -- Font flags
@@ -1857,10 +2018,12 @@ ImGuiStyleVar = {
DragDropTargetRounding = 37, -- float DragDropTargetRounding DragDropTargetRounding = 37, -- float DragDropTargetRounding
ButtonTextAlign = 38, -- ImVec2 ButtonTextAlign ButtonTextAlign = 38, -- ImVec2 ButtonTextAlign
SelectableTextAlign = 39, -- ImVec2 SelectableTextAlign SelectableTextAlign = 39, -- ImVec2 SelectableTextAlign
SeparatorTextBorderSize = 40, -- float SeparatorTextBorderSize SeparatorSize = 40, -- float SeparatorSize
SeparatorTextAlign = 41, -- ImVec2 SeparatorTextAlign SeparatorTextBorderSize = 41, -- float SeparatorTextBorderSize
SeparatorTextPadding = 42, -- ImVec2 SeparatorTextPadding SeparatorTextAlign = 42, -- ImVec2 SeparatorTextAlign
COUNT = 43 SeparatorTextPadding = 43, -- ImVec2 SeparatorTextPadding
DockingSeparatorSize = 44, -- float DockingSeparatorSize
COUNT = 45
} }
-- Flags for ImGui.IsItemHovered(), ImGui.IsWindowHovered() -- Flags for ImGui.IsItemHovered(), ImGui.IsWindowHovered()
@@ -1949,8 +2112,9 @@ ImGuiKey = {
RightCtrl = 531, RightShift = 532, RightAlt = 533, RightSuper = 534, RightCtrl = 531, RightShift = 532, RightAlt = 533, RightSuper = 534,
Menu = 535, Menu = 535,
-- 1 ~ 9 -- 0 ~ 9 (upstream ImGuiKey_0..ImGuiKey_9: K0..K9 in this port, ImGuiKey["0"]..ImGuiKey["9"] are aliases)
K0 = 536, K1 = 537, K2 = 538, K3 = 539, K4 = 540, K5 = 541, K6 = 542, K7 = 543, K8 = 544, K9 = 545, K0 = 536, K1 = 537, K2 = 538, K3 = 539, K4 = 540, K5 = 541, K6 = 542, K7 = 543, K8 = 544, K9 = 545,
["0"] = 536, ["1"] = 537, ["2"] = 538, ["3"] = 539, ["4"] = 540, ["5"] = 541, ["6"] = 542, ["7"] = 543, ["8"] = 544, ["9"] = 545,
A = 546, B = 547, C = 548, D = 549, E = 550, F = 551, G = 552, H = 553, I = 554, J = 555, A = 546, B = 547, C = 548, D = 549, E = 550, F = 551, G = 552, H = 553, I = 554, J = 555,
K = 556, L = 557, M = 558, N = 559, O = 560, P = 561, Q = 562, R = 563, S = 564, T = 565, K = 556, L = 557, M = 558, N = 559, O = 560, P = 561, Q = 562, R = 563, S = 564, T = 565,
@@ -2119,6 +2283,10 @@ ImGuiDragDropFlags = {
ImGuiDragDropFlags.AcceptPeekOnly = bitOr(ImGuiDragDropFlags.AcceptBeforeDelivery, ImGuiDragDropFlags.AcceptNoDrawDefaultRect) -- For peeking ahead and inspecting the payload before delivery. ImGuiDragDropFlags.AcceptPeekOnly = bitOr(ImGuiDragDropFlags.AcceptBeforeDelivery, ImGuiDragDropFlags.AcceptNoDrawDefaultRect) -- For peeking ahead and inspecting the payload before delivery.
-- Standard Drag and Drop payload types. You can define you own payload types using short strings. Types starting with '_' are defined by Dear ImGui.
IMGUI_PAYLOAD_TYPE_COLOR_3F = "_COL3F" -- float[3]: Standard type for colors, without alpha. User code may use this type. [Port] payload.Data = { r, g, b } (1-based)
IMGUI_PAYLOAD_TYPE_COLOR_4F = "_COL4F" -- float[4]: Standard type for colors. User code may use this type. [Port] payload.Data = { r, g, b, a } (1-based)
-- Helpers macros to generate 32-bit encoded colors -- Helpers macros to generate 32-bit encoded colors
IM_COL32_R_SHIFT = 0 IM_COL32_R_SHIFT = 0
IM_COL32_G_SHIFT = 8 IM_COL32_G_SHIFT = 8
@@ -2130,7 +2298,8 @@ IM_COL32_A_MASK = 0xFF000000
--- @param G ImU32 --- @param G ImU32
--- @param B ImU32 --- @param B ImU32
--- @param A ImU32 --- @param A ImU32
IM_COL32 = function(R, G, B, A) return (bitOr(bitLShift(A, IM_COL32_A_SHIFT), bitLShift(B, IM_COL32_B_SHIFT), bitLShift(G, IM_COL32_G_SHIFT), bitLShift(R, IM_COL32_R_SHIFT))) end --@hoist
function IM_COL32(R, G, B, A) return (bitOr(bitLShift(A, IM_COL32_A_SHIFT), bitLShift(B, IM_COL32_B_SHIFT), bitLShift(G, IM_COL32_G_SHIFT), bitLShift(R, IM_COL32_R_SHIFT))) end
IM_COL32_WHITE = IM_COL32(255, 255, 255, 255) IM_COL32_WHITE = IM_COL32(255, 255, 255, 255)
IM_COL32_BLACK = IM_COL32(0, 0, 0, 255) IM_COL32_BLACK = IM_COL32(0, 0, 0, 255)
IM_COL32_BLACK_TRANS = IM_COL32(0, 0, 0, 0) IM_COL32_BLACK_TRANS = IM_COL32(0, 0, 0, 0)
@@ -2172,7 +2341,7 @@ ImGuiComboFlags = {
} }
ImGuiComboFlags.HeightMask_ = bitOr(ImGuiComboFlags.HeightSmall, ImGuiComboFlags.HeightRegular, ImGuiComboFlags.HeightLarge, ImGuiComboFlags.HeightLargest) ImGuiComboFlags.HeightMask_ = bitOr(ImGuiComboFlags.HeightSmall, ImGuiComboFlags.HeightRegular, ImGuiComboFlags.HeightLarge, ImGuiComboFlags.HeightLargest)
ImGuiComboFlags.CustomPreview = bitLShift(1, 20) ImGuiComboFlags.CustomPreview = bitLShift(1, 20) -- [Luau] obsolete: removed in 1.93.0 (BeginComboPreview() no longer needs a flag), kept as an ignored value
-- Flags for ImGui.Selectable() -- Flags for ImGui.Selectable()
--- @enum ImGuiSelectableFlags --- @enum ImGuiSelectableFlags
@@ -2294,6 +2463,7 @@ function ImGuiWindowClass()
TabItemFlagsOverrideSet = 0, -- [EXPERIMENTAL] TabItem flags to set when a window of this class gets submitted into a dock node tab bar. May use with ImGuiTabItemFlags.Leading or ImGuiTabItemFlags.Trailing. TabItemFlagsOverrideSet = 0, -- [EXPERIMENTAL] TabItem flags to set when a window of this class gets submitted into a dock node tab bar. May use with ImGuiTabItemFlags.Leading or ImGuiTabItemFlags.Trailing.
DockNodeFlagsOverrideSet = 0, -- [EXPERIMENTAL] Dock node flags to set when a window of this class is hosted by a dock node (it doesn't have to be selected!) DockNodeFlagsOverrideSet = 0, -- [EXPERIMENTAL] Dock node flags to set when a window of this class is hosted by a dock node (it doesn't have to be selected!)
DockingAlwaysTabBar = false, -- Set to true to enforce single floating windows of this class always having their own docking node (equivalent of setting the global io.ConfigDockingAlwaysTabBar) DockingAlwaysTabBar = false, -- Set to true to enforce single floating windows of this class always having their own docking node (equivalent of setting the global io.ConfigDockingAlwaysTabBar)
PlatformIconData = nil, -- void* to hold custom data structure for the OS / platform to specify an icon. Currently unused for exposed to allow experiments.
DockingAllowUnclassed = true -- Set to true to allow windows of this class to be docked/merged with an unclassed window. -- FIXME-DOCK: Move to DockNodeFlags override? DockingAllowUnclassed = true -- Set to true to allow windows of this class to be docked/merged with an unclassed window. -- FIXME-DOCK: Move to DockNodeFlags override?
} }
end end
@@ -2417,7 +2587,8 @@ function MT.ImGuiInputTextCallbackData:InsertChars(pos, new_text)
self.SelectionStart = self.CursorPos; self.SelectionEnd = self.CursorPos self.SelectionStart = self.CursorPos; self.SelectionEnd = self.CursorPos
end end
function MT.ImGuiInputTextCallbackData:SelectAll() self.SelectionStart = 0; self.SelectionEnd = self.BufTextLen end function MT.ImGuiInputTextCallbackData:SelectAll() self.SelectionStart = 0; self.CursorPos = self.BufTextLen; self.SelectionEnd = self.BufTextLen end
function MT.ImGuiInputTextCallbackData:SetSelection(s, e) IM_ASSERT(s >= 0 and s <= self.BufTextLen); IM_ASSERT(e >= 0 and e <= self.BufTextLen); self.SelectionStart = s; self.CursorPos = e; self.SelectionEnd = e end
function MT.ImGuiInputTextCallbackData:ClearSelection() self.SelectionStart = self.BufTextLen; self.SelectionEnd = self.BufTextLen end function MT.ImGuiInputTextCallbackData:ClearSelection() self.SelectionStart = self.BufTextLen; self.SelectionEnd = self.BufTextLen end
function MT.ImGuiInputTextCallbackData:HasSelection() return self.SelectionStart ~= self.SelectionEnd end function MT.ImGuiInputTextCallbackData:HasSelection() return self.SelectionStart ~= self.SelectionEnd end
@@ -2616,6 +2787,17 @@ ImGuiTableBgTarget = { None = 0, RowBg0 = 1, RowBg1 = 2, CellBg = 3 }
--- @enum ImGuiSortDirection --- @enum ImGuiSortDirection
ImGuiSortDirection = { None = 0, Ascending = 1, Descending = 2 } ImGuiSortDirection = { None = 0, Ascending = 1, Descending = 2 }
-- Resizing callback data to apply custom constraint. As enabled by SetNextWindowSizeConstraints(). Callback is called during the next Begin().
-- NB: For basic min/max size constraint on each axis you don't need to use the callback! The SetNextWindowSizeConstraints() parameters are enough.
--- @class ImGuiSizeCallbackData
--- @field UserData any Read-only. What user passed to SetNextWindowSizeConstraints(). Generally store an integer or float in here.
--- @field Pos ImVec2 Read-only. Window position, for reference.
--- @field CurrentSize ImVec2 Read-only. Current window size.
--- @field DesiredSize ImVec2 Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing.
--- @return ImGuiSizeCallbackData
function ImGuiSizeCallbackData()
return { UserData = nil, Pos = ImVec2(), CurrentSize = ImVec2(), DesiredSize = ImVec2() }
end
-- Data payload for Drag and Drop operations: AcceptDragDropPayload(), GetDragDropPayload() -- Data payload for Drag and Drop operations: AcceptDragDropPayload(), GetDragDropPayload()
@@ -2639,6 +2821,13 @@ function ImGuiPayload_IsDelivery(p) return p.Delivery end
-- [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) -- [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor)
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Flags for ImGuiListClipper (currently not fully exposed in function calls: a future refactor will likely add this to ImGuiListClipper::Begin function equivalent)
--- @enum ImGuiListClipperFlags
ImGuiListClipperFlags = {
None = 0,
NoSetTableRowCounters = bitLShift(1, 0), -- [Internal] Disabled modifying table row counters. Avoid assumption that 1 clipper item == 1 table row.
}
-- Helper: Growable text buffer for logging/accumulating text -- Helper: Growable text buffer for logging/accumulating text
-- (this could be called 'ImGuiTextBuilder' / 'ImGuiStringBuilder') -- (this could be called 'ImGuiTextBuilder' / 'ImGuiStringBuilder')
--- @class ImGuiTextBuffer # string builder: Buf is a list of string chunks --- @class ImGuiTextBuffer # string builder: Buf is a list of string chunks
@@ -2652,6 +2841,148 @@ function ImGuiTextBufferMT:appendf(fmt, ...) self.Buf[#self.Buf + 1] = string.fo
function ImGuiTextBufferMT:c_str() return table.concat(self.Buf) end function ImGuiTextBufferMT:c_str() return table.concat(self.Buf) end
function ImGuiTextBufferMT:size() return #self:c_str() end function ImGuiTextBufferMT:size() return #self:c_str() end
-- Helpers: ImColor() implicitly converts colors to either ImU32 (packed 4x1 byte) or ImVec4 (4x1 float)
-- Prefer using IM_COL32() macros. Avoid storing ImColor! Store either u32 of ImVec4. This is not a full-featured color class.
-- **None of the ImGui API are using ImColor directly but you can use it as a convenience to pass colors in either ImU32 or ImVec4 formats. Explicitly cast to ImU32 or ImVec4 if needed.
-- [Luau] no implicit conversions: use :U32() / .Value (ImVec4). Overloads: ImColor(), ImColor(r, g, b, a) floats,
-- ImColor(ImVec4), ImColor(ImU32). The C++ int (0-255) overload can't be told apart from floats: use ImColor(IM_COL32(r, g, b, a)).
--- @class ImColor
local ImColorMT = {}
ImColorMT.__index = ImColorMT
ImColor = setmetatable({}, { __call = function(_, r, g, b, a)
local self = setmetatable({ Value = ImVec4() }, ImColorMT)
if type(r) == "table" then
ImVec4_Copy(self.Value, r)
elseif r ~= nil and g == nil then
local s = 1.0 / 255.0
self.Value.x = bit32.band(bit32.rshift(r, IM_COL32_R_SHIFT), 0xFF) * s; self.Value.y = bit32.band(bit32.rshift(r, IM_COL32_G_SHIFT), 0xFF) * s
self.Value.z = bit32.band(bit32.rshift(r, IM_COL32_B_SHIFT), 0xFF) * s; self.Value.w = bit32.band(bit32.rshift(r, IM_COL32_A_SHIFT), 0xFF) * s
elseif r ~= nil then
self.Value.x = r; self.Value.y = g; self.Value.z = b; self.Value.w = a or 1.0
end
return self
end })
function ImColorMT:U32() return ImGui.ColorConvertFloat4ToU32(self.Value) end
-- FIXME-OBSOLETE: May need to obsolete/cleanup those helpers.
function ImColorMT:SetHSV(h, s, v, a) self.Value.x, self.Value.y, self.Value.z = ImGui.ColorConvertHSVtoRGB(h, s, v); self.Value.w = a or 1.0 end
function ImColor.HSV(h, s, v, a) local r, g, b = ImGui.ColorConvertHSVtoRGB(h, s, v); return ImColor(r, g, b, a or 1.0) end
-------------------------------------------------------------------------------
-- [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiSelectionRequestType, ImGuiSelectionRequest, ImGuiMultiSelectIO, ImGuiSelectionBasicStorage)
-------------------------------------------------------------------------------
-- Multi-selection system
-- Documentation at: https://github.com/ocornut/imgui/wiki/Multi-Select
-- - Refer to 'Demo->Widgets->Selection State & Multi-Select' for demos using this.
-- - This system implements standard multi-selection idioms (Ctrl+Mouse/Keyboard, Shift+Mouse/Keyboard, etc)
-- with support for clipper (skipping non-visible items), box-select and many other details.
-- - Selectable(), Checkbox() are supported but custom widgets may use it as well.
-- - TreeNode() is technically supported but... using this correctly is more complicated: you need some sort of linear/random access to your tree,
-- which is suited to advanced trees setups also implementing filters and clipper. We will work toward simplifying and demoing it.
-- - In the spirit of Dear ImGui design, your code owns actual selection data.
-- This is designed to allow all kinds of selection storage you may use in your application e.g. set/map/hash.
-- About ImGuiSelectionBasicStorage:
-- - This is an optional helper to store a selection state and apply selection requests.
-- - It is used by our demos and provided as a convenience to quickly implement multi-selection.
-- Usage:
-- - Identify submitted items with SetNextItemSelectionUserData(), most likely using an index into your current data-set.
-- - Store and maintain actual selection data using persistent object identifiers.
-- - Usage flow:
-- BEGIN - (1) Call BeginMultiSelect() and retrieve the ImGuiMultiSelectIO result.
-- - (2) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 6.
-- - (3) [If using clipper] You need to make sure RangeSrcItem is always submitted. Calculate its index and pass to clipper:IncludeItemByIndex(). If storing indices in ImGuiSelectionUserData, a simple clipper:IncludeItemByIndex(ms_io.RangeSrcItem) call will work.
-- LOOP - (4) Submit your items with SetNextItemSelectionUserData() + Selectable()/TreeNode() calls.
-- END - (5) Call EndMultiSelect() and retrieve the ImGuiMultiSelectIO result.
-- - (6) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 2.
-- If you submit all items (no clipper), Step 2 and 3 are optional and will be handled by each item themselves. It is fine to always honor those steps.
-- About ImGuiSelectionUserData:
-- - This can store an application-defined identifier (e.g. index or pointer) submitted via SetNextItemSelectionUserData().
-- - In return we store them into RangeSrcItem/RangeFirstItem/RangeLastItem and other fields in ImGuiMultiSelectIO.
-- - Most applications will store an object INDEX, hence the chosen name and type. Storing an index is natural, because
-- SetRange requests will give you two end-points and you will need to iterate/interpolate between them to update your selection.
-- - However it is perfectly possible to store a POINTER or another IDENTIFIER inside ImGuiSelectionUserData.
-- Our system never assume that you identify items by indices, it never attempts to interpolate between two values.
-- - If you enable ImGuiMultiSelectFlags.NoRangeSelect then it is guaranteed that you will never have to interpolate
-- between two ImGuiSelectionUserData, which may be a convenient way to use part of the feature with less code work.
-- - As most users will want to store an index, for convenience and to reduce confusion we use ImS64 instead of void*,
-- being syntactically easier to downcast. Feel free to reinterpret_cast and store a pointer inside.
-- [Port] ImGuiSelectionUserData is any Lua value compared with '==' (an integer index, or e.g. a table reference instead of a pointer).
-- -1 (ImGuiSelectionUserData_Invalid) is reserved. ImGuiSelectionBasicStorage/ImGuiSelectionExternalStorage require integer indices.
-- Flags for BeginMultiSelect()
--- @enum ImGuiMultiSelectFlags
ImGuiMultiSelectFlags = {
None = 0,
SingleSelect = bitLShift(1, 0), -- Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho!
NoSelectAll = bitLShift(1, 1), -- Disable Ctrl+A shortcut to select all.
NoRangeSelect = bitLShift(1, 2), -- Disable Shift+selection mouse/keyboard support (useful for unordered 2D selection). With BoxSelect is also ensure contiguous SetRange requests are not combined into one. This allows not handling interpolation in SetRange requests.
NoAutoSelect = bitLShift(1, 3), -- Disable selecting items when navigating (useful for e.g. supporting range-select in a list of checkboxes).
NoAutoClear = bitLShift(1, 4), -- Disable clearing selection when navigating or selecting another one (generally used with ImGuiMultiSelectFlags.NoAutoSelect. useful for e.g. supporting range-select in a list of checkboxes).
NoAutoClearOnReselect = bitLShift(1, 5), -- Disable clearing selection when clicking/selecting an already selected item.
BoxSelect1d = bitLShift(1, 6), -- Enable box-selection with same width and same x pos items (e.g. full row Selectable()). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space.
BoxSelect2d = bitLShift(1, 7), -- Enable box-selection with varying width or varying x pos items support (e.g. different width labels, or 2D layout/grid). This is slower: alters clipping logic so that e.g. horizontal movements will update selection of normally clipped items.
BoxSelectNoScroll = bitLShift(1, 8), -- Disable scrolling when box-selecting and moving mouse near edges of scope.
ClearOnEscape = bitLShift(1, 9), -- Clear selection when pressing Escape while scope is focused.
ClearOnClickVoid = bitLShift(1, 10), -- Clear selection when clicking on empty location within scope.
ScopeWindow = bitLShift(1, 11), -- Scope for _BoxSelect and _ClearOnClickVoid is whole window (Default). Use if BeginMultiSelect() covers a whole window or used a single time in same window.
ScopeRect = bitLShift(1, 12), -- Scope for _BoxSelect and _ClearOnClickVoid is rectangle encompassing BeginMultiSelect()/EndMultiSelect(). Use if BeginMultiSelect() is called multiple times in same window.
SelectOnAuto = bitLShift(1, 13), -- Apply selection on mouse down when clicking on unselected item, on mouse up when clicking on selected item. (Default)
SelectOnClickAlways = bitLShift(1, 14), -- Apply selection on mouse down when clicking on any items. Prevents Drag and Drop from being used on multiple-selection, but allows e.g. BoxSelect to always reselect even when clicking inside an existing selection. (Excel style behavior)
SelectOnClickRelease = bitLShift(1, 15), -- Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection.
--RangeSelect2d = bitLShift(1, 15), -- Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does.
NavWrapX = bitLShift(1, 16), -- [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one.
NoSelectOnRightClick = bitLShift(1, 17), -- Disable default right-click processing, which selects item on mouse down, and is designed for context-menus.
CheckboxMode_ = bitLShift(1, 20), -- [Internal]
}
ImGuiMultiSelectFlags.SelectOnMask_ = bit32.bor(ImGuiMultiSelectFlags.SelectOnAuto, ImGuiMultiSelectFlags.SelectOnClickAlways, ImGuiMultiSelectFlags.SelectOnClickRelease)
ImGuiMultiSelectFlags.SelectOnClick = ImGuiMultiSelectFlags.SelectOnAuto -- RENAMED in 1.92.6
-- Main IO structure returned by BeginMultiSelect()/EndMultiSelect().
-- This mainly contains a list of selection requests.
-- - Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen.
-- - Some fields are only useful if your list is dynamic and allows deletion (getting post-deletion focus/state right is shown in the demo)
-- - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code.
--- @class ImGuiMultiSelectIO
--- @field Requests ImVector<ImGuiSelectionRequest> # ms:w, app:r / ms:w app:r // Requests to apply to your selection data. (1-based .Data)
--- @field RangeSrcItem ImGuiSelectionUserData # ms:w app:r / // (If using clipper) Begin: Source item (often the first selected item) must never be clipped: use clipper:IncludeItemByIndex() to ensure it is submitted.
--- @field NavIdItem ImGuiSelectionUserData # ms:w, app:r / // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items).
--- @field NavIdSelected bool # ms:w, app:r / app:r // (If using deletion) Last known selection state for NavId (if part of submitted items).
--- @field RangeSrcReset bool # app:w / ms:r // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection).
--- @field ItemsCount int # ms:w, app:r / app:r // 'int items_count' parameter to BeginMultiSelect() is copied here for convenience, allowing simpler calls to your ApplyRequests handler. Not used internally.
-- Selection request type
--- @enum ImGuiSelectionRequestType
ImGuiSelectionRequestType = {
None = 0,
SetAll = 1, -- Request app to clear selection (if Selected==false) or select all items (if Selected==true). We cannot set RangeFirstItem/RangeLastItem as its contents is entirely up to user (not necessarily an index)
SetRange = 2, -- Request app to select/unselect [RangeFirstItem..RangeLastItem] items (inclusive) based on value of Selected. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false.
}
-- Selection request item
--- @class ImGuiSelectionRequest
--- @field Type ImGuiSelectionRequestType # ms:w, app:r / ms:w, app:r // Request type. You'll most often receive 1 Clear + 1 SetRange with a single-item range.
--- @field Selected bool # ms:w, app:r / ms:w, app:r // Parameter for SetAll/SetRange requests (true = select, false = unselect)
--- @field RangeDirection int # / ms:w app:r // Parameter for SetRange request: +1 when RangeFirstItem comes before RangeLastItem, -1 otherwise. Useful if you want to preserve selection order on a backward Shift+Click.
--- @field RangeFirstItem ImGuiSelectionUserData # / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom).
--- @field RangeLastItem ImGuiSelectionUserData # / ms:w, app:r // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top). Inclusive!
function ImGuiSelectionRequest()
return { Type = ImGuiSelectionRequestType.None, Selected = false, RangeDirection = 0, RangeFirstItem = -1, RangeLastItem = -1 }
end
-- [Port] ImVector<ImGuiSelectionRequest> copy function: push_back() copies fields into the preallocated element (C++ copies by value),
-- so callers can push a reused scratch request without allocating.
function ImGuiSelectionRequest_Copy(t, k, v)
local dst = t[k]
dst.Type = v.Type; dst.Selected = v.Selected; dst.RangeDirection = v.RangeDirection; dst.RangeFirstItem = v.RangeFirstItem; dst.RangeLastItem = v.RangeLastItem
end
--- @return ImGuiMultiSelectIO
function ImGuiMultiSelectIO()
return { Requests = ImVector(ImGuiSelectionRequest, ImGuiSelectionRequest_Copy), RangeSrcItem = -1, NavIdItem = -1, NavIdSelected = false, RangeSrcReset = false, ItemsCount = 0 }
end
-- ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage: see [SECTION] Widgets: Multi-Select helpers in imgui_widgets.lua
-- (constructors and methods are defined there, like upstream's out-of-line definitions in imgui_widgets.cpp).
--- @class ImGuiSettingsHandler --- @class ImGuiSettingsHandler
--- Fns take (ctx, handler, ...) like upstream. ReadOpenFn(ctx, handler, name) returns entry; ReadLineFn(ctx, handler, entry, line); --- Fns take (ctx, handler, ...) like upstream. ReadOpenFn(ctx, handler, name) returns entry; ReadLineFn(ctx, handler, entry, line);
--- WriteAllFn(ctx, handler, buf: ImGuiTextBuffer) --- WriteAllFn(ctx, handler, buf: ImGuiTextBuffer)
+584 -58
View File
File diff suppressed because it is too large Load Diff
+242 -145
View File
@@ -200,6 +200,7 @@ end
local bit32 = bit32 local bit32 = bit32
local band, bor, bnot, lshift, rshift = bit32.band, bit32.bor, bit32.bnot, bit32.lshift, bit32.rshift local band, bor, bnot, lshift, rshift = bit32.band, bit32.bor, bit32.bnot, bit32.lshift, bit32.rshift
local mathMax, mathMin = math.max, math.min -- [Luau] scalar ImMax()/ImMin() in table code are written as mathMax()/mathMin() (no call through the vector-aware helper)
IM_COL32_DISABLE = IM_COL32(0, 0, 0, 1) -- Special sentinel code which cannot be used as a regular color. IM_COL32_DISABLE = IM_COL32(0, 0, 0, 1) -- Special sentinel code which cannot be used as a regular color.
IMGUI_TABLE_MAX_COLUMNS = 512 -- Arbitrary "safety" maximum, may be lifted in the future if needed. Must fit in ImGuiTableColumnIdx/ImGuiTableDrawChannelIdx. IMGUI_TABLE_MAX_COLUMNS = 512 -- Arbitrary "safety" maximum, may be lifted in the future if needed. Must fit in ImGuiTableColumnIdx/ImGuiTableDrawChannelIdx.
@@ -330,9 +331,13 @@ end
-- Storage for a table .ini settings -- Storage for a table .ini settings
--- @class ImGuiTableSettings --- @class ImGuiTableSettings
MT.ImGuiTableSettings = {}
MT.ImGuiTableSettings.__index = MT.ImGuiTableSettings
function ImGuiTableSettings() function ImGuiTableSettings()
return { ID = 0, SaveFlags = 0, RefScale = 0.0, ColumnsCount = 0, ColumnsCountMax = 0, LastUsedDate = 0, WantApply = false, Columns = {} } return setmetatable({ ID = 0, SaveFlags = 0, RefScale = 0.0, ColumnsCount = 0, ColumnsCountMax = 0, LastUsedDate = 0, WantApply = false, Columns = {} }, MT.ImGuiTableSettings)
end end
-- [Luau] column settings are stored in a 0-based .Columns table (upstream: allocated right after the struct)
function MT.ImGuiTableSettings:GetColumnSettings() return self.Columns end
-- Storage data for a single column for legacy Columns() api -- Storage data for a single column for legacy Columns() api
--- @class ImGuiOldColumnData --- @class ImGuiOldColumnData
@@ -347,16 +352,6 @@ function ImGuiOldColumns()
HostBackupClipRect = ImRect(), HostBackupParentWorkRect = ImRect(), Columns = ImVector(), Splitter = ImDrawListSplitter() } HostBackupClipRect = ImRect(), HostBackupParentWorkRect = ImRect(), Columns = ImVector(), Splitter = ImDrawListSplitter() }
end end
-- Flags for internal's BeginColumns(). This is an obsolete API. Prefer using BeginTable() nowadays!
ImGuiOldColumnFlags = ImGuiOldColumnFlags or {
None = 0,
NoBorder = lshift(1, 0), -- Disable column dividers
NoResize = lshift(1, 1), -- Disable resizing columns when clicking on the dividers
NoPreserveWidths = lshift(1, 2), -- Disable column width preservation when adjusting columns
NoForceWithinWindow = lshift(1, 3), -- Disable forcing columns to fit within window
GrowParentContentsSize = lshift(1, 4), -- Restore pre-1.51 behavior of extending the parent window contents size but _without affecting the columns width at all_. Will eventually remove.
}
ImGuiTableColumnFlags.NoDirectResize_ = ImGuiTableColumnFlags.NoDirectResize_ or lshift(1, 30) -- [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge) ImGuiTableColumnFlags.NoDirectResize_ = ImGuiTableColumnFlags.NoDirectResize_ or lshift(1, 30) -- [Internal] Disable user resizing this column directly (it may however we resized indirectly from its left edge)
local function Rect(r) return ImRect(r.Min.x, r.Min.y, r.Max.x, r.Max.y) end local function Rect(r) return ImRect(r.Min.x, r.Min.y, r.Max.x, r.Max.y) end
@@ -482,7 +477,7 @@ function ImGui.BeginTableEx(name, id, columns_count, flags, outer_size, inner_wi
-- - always performing the GetOrAddByKey() O(log N) query in g.Tables.Map[]. -- - always performing the GetOrAddByKey() O(log N) query in g.Tables.Map[].
local use_child_window = band(flags, bor(ImGuiTableFlags.ScrollX, ImGuiTableFlags.ScrollY)) ~= 0 local use_child_window = band(flags, bor(ImGuiTableFlags.ScrollX, ImGuiTableFlags.ScrollY)) ~= 0
local avail_size = ImGui.GetContentRegionAvail() local avail_size = ImGui.GetContentRegionAvail()
local actual_outer_size = ImTrunc(ImGui.CalcItemSize(outer_size, ImMax(avail_size.x, IMGUI_WINDOW_HARD_MIN_SIZE), use_child_window and ImMax(avail_size.y, IMGUI_WINDOW_HARD_MIN_SIZE) or 0.0)) local actual_outer_size = ImTrunc(ImGui.CalcItemSize(outer_size, mathMax(avail_size.x, IMGUI_WINDOW_HARD_MIN_SIZE), use_child_window and mathMax(avail_size.y, IMGUI_WINDOW_HARD_MIN_SIZE) or 0.0))
local outer_rect = ImRect(outer_window.DC.CursorPos, outer_window.DC.CursorPos + actual_outer_size) local outer_rect = ImRect(outer_window.DC.CursorPos, outer_window.DC.CursorPos + actual_outer_size)
local outer_window_is_measuring_size = (outer_window.AutoFitFramesX > 0) or (outer_window.AutoFitFramesY > 0) -- Doesn't apply to AlwaysAutoResize windows! local outer_window_is_measuring_size = (outer_window.AutoFitFramesX > 0) or (outer_window.AutoFitFramesY > 0) -- Doesn't apply to AlwaysAutoResize windows!
if use_child_window and ImGui.IsClippedEx(outer_rect, 0) and not outer_window_is_measuring_size then if use_child_window and ImGui.IsClippedEx(outer_rect, 0) and not outer_window_is_measuring_size then
@@ -638,15 +633,15 @@ function ImGui.BeginTableEx(name, id, columns_count, flags, outer_size, inner_wi
-- different x/y values to BeginChild(). -- different x/y values to BeginChild().
local border_size = TABLE_BORDER_SIZE local border_size = TABLE_BORDER_SIZE
if band(flags, ImGuiTableFlags.BordersOuterV) ~= 0 then if band(flags, ImGuiTableFlags.BordersOuterV) ~= 0 then
table_.HostClipRect.Min.x = ImMin(table_.HostClipRect.Min.x + border_size, table_.HostClipRect.Max.x) table_.HostClipRect.Min.x = mathMin(table_.HostClipRect.Min.x + border_size, table_.HostClipRect.Max.x)
if inner_window.DecoOuterSizeX2 == 0.0 then if inner_window.DecoOuterSizeX2 == 0.0 then
table_.HostClipRect.Max.x = ImMax(table_.HostClipRect.Max.x - border_size, table_.HostClipRect.Min.x) table_.HostClipRect.Max.x = mathMax(table_.HostClipRect.Max.x - border_size, table_.HostClipRect.Min.x)
end end
end end
if band(flags, ImGuiTableFlags.BordersOuterH) ~= 0 then if band(flags, ImGuiTableFlags.BordersOuterH) ~= 0 then
table_.HostClipRect.Min.y = ImMin(table_.HostClipRect.Min.y + border_size, table_.HostClipRect.Max.y) table_.HostClipRect.Min.y = mathMin(table_.HostClipRect.Min.y + border_size, table_.HostClipRect.Max.y)
if inner_window.DecoOuterSizeY2 == 0.0 then if inner_window.DecoOuterSizeY2 == 0.0 then
table_.HostClipRect.Max.y = ImMax(table_.HostClipRect.Max.y - border_size, table_.HostClipRect.Min.y) table_.HostClipRect.Max.y = mathMax(table_.HostClipRect.Max.y - border_size, table_.HostClipRect.Min.y)
end end
end end
end end
@@ -679,7 +674,7 @@ function ImGui.BeginTableEx(name, id, columns_count, flags, outer_size, inner_wi
ImRect_Copy(table_.InnerClipRect, (inner_window == outer_window) and table_.WorkRect or inner_window.ClipRect) ImRect_Copy(table_.InnerClipRect, (inner_window == outer_window) and table_.WorkRect or inner_window.ClipRect)
table_.InnerClipRect:ClipWith(table_.WorkRect) -- We need this to honor inner_width table_.InnerClipRect:ClipWith(table_.WorkRect) -- We need this to honor inner_width
table_.InnerClipRect:ClipWithFull(table_.HostClipRect) table_.InnerClipRect:ClipWithFull(table_.HostClipRect)
table_.InnerClipRect.Max.y = (band(flags, ImGuiTableFlags.NoHostExtendY) ~= 0) and ImMin(table_.InnerClipRect.Max.y, inner_window.WorkRect.Max.y) or table_.HostClipRect.Max.y table_.InnerClipRect.Max.y = (band(flags, ImGuiTableFlags.NoHostExtendY) ~= 0) and mathMin(table_.InnerClipRect.Max.y, inner_window.WorkRect.Max.y) or table_.HostClipRect.Max.y
table_.RowPosY1 = table_.WorkRect.Min.y -- This is needed somehow table_.RowPosY1 = table_.WorkRect.Min.y -- This is needed somehow
table_.RowPosY2 = table_.WorkRect.Min.y table_.RowPosY2 = table_.WorkRect.Min.y
@@ -1050,7 +1045,7 @@ function ImGui.TableUpdateLayout(table_)
BitArrayClearAll(table_.EnabledMaskByIndex) BitArrayClearAll(table_.EnabledMaskByIndex)
BitArrayClearAll(table_.EnabledMaskByDisplayOrder) BitArrayClearAll(table_.EnabledMaskByDisplayOrder)
table_.LeftMostEnabledColumn = -1 table_.LeftMostEnabledColumn = -1
table_.MinColumnWidth = ImMax(1.0, g.Style.FramePadding.x * 1.0) -- g.Style.ColumnsMinSpacing -- FIXME-TABLE table_.MinColumnWidth = mathMax(1.0, g.Style.FramePadding.x * 1.0) -- g.Style.ColumnsMinSpacing -- FIXME-TABLE
-- [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns. -- [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns.
-- Process columns in their visible orders as we are building the Prev/Next indices. -- Process columns in their visible orders as we are building the Prev/Next indices.
@@ -1152,7 +1147,7 @@ function ImGui.TableUpdateLayout(table_)
stretch_sum_width_auto = stretch_sum_width_auto + column.WidthAuto stretch_sum_width_auto = stretch_sum_width_auto + column.WidthAuto
count_stretch = count_stretch + 1 count_stretch = count_stretch + 1
else else
fixed_max_width_auto = ImMax(fixed_max_width_auto, column.WidthAuto) fixed_max_width_auto = mathMax(fixed_max_width_auto, column.WidthAuto)
count_fixed = count_fixed + 1 count_fixed = count_fixed + 1
end end
end end
@@ -1204,7 +1199,7 @@ function ImGui.TableUpdateLayout(table_)
-- FIXME: Move this to .WidthGiven to avoid temporary lossyness? -- FIXME: Move this to .WidthGiven to avoid temporary lossyness?
-- FIXME: This break IsPreserveWidthAuto from not flickering if the stored WidthAuto was smaller. -- FIXME: This break IsPreserveWidthAuto from not flickering if the stored WidthAuto was smaller.
if column.AutoFitQueue > 0x01 and table_.IsInitializing and not column.IsPreserveWidthAuto then if column.AutoFitQueue > 0x01 and table_.IsInitializing and not column.IsPreserveWidthAuto then
column.WidthRequest = ImMax(column.WidthRequest, table_.MinColumnWidth * 4.0) -- FIXME-TABLE: Another constant/scale? column.WidthRequest = mathMax(column.WidthRequest, table_.MinColumnWidth * 4.0) -- FIXME-TABLE: Another constant/scale?
end end
sum_width_requests = sum_width_requests + column.WidthRequest sum_width_requests = sum_width_requests + column.WidthRequest
else else
@@ -1238,7 +1233,7 @@ function ImGui.TableUpdateLayout(table_)
local work_rect = Rect(table_.WorkRect) local work_rect = Rect(table_.WorkRect)
local width_spacings = (table_.OuterPaddingX * 2.0) + (table_.CellSpacingX1 + table_.CellSpacingX2) * (table_.ColumnsEnabledCount - 1) local width_spacings = (table_.OuterPaddingX * 2.0) + (table_.CellSpacingX1 + table_.CellSpacingX2) * (table_.ColumnsEnabledCount - 1)
local width_removed = (table_.HasScrollbarYPrev and not table_.InnerWindow.ScrollbarY) and g.Style.ScrollbarSize or 0.0 -- To synchronize decoration width of synced tables with mismatching scrollbar state (#5920) local width_removed = (table_.HasScrollbarYPrev and not table_.InnerWindow.ScrollbarY) and g.Style.ScrollbarSize or 0.0 -- To synchronize decoration width of synced tables with mismatching scrollbar state (#5920)
local width_avail = ImMax(1.0, ((band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0 and table_.InnerWidth == 0.0) and table_.InnerClipRect:GetWidth() or work_rect:GetWidth()) - width_removed) local width_avail = mathMax(1.0, ((band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0 and table_.InnerWidth == 0.0) and table_.InnerClipRect:GetWidth() or work_rect:GetWidth()) - width_removed)
local width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests local width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests
local width_remaining_for_stretched_columns = width_avail_for_stretched_columns local width_remaining_for_stretched_columns = width_avail_for_stretched_columns
table_.ColumnsGivenWidth = width_spacings + (table_.CellPaddingX * 2.0) * table_.ColumnsEnabledCount table_.ColumnsGivenWidth = width_spacings + (table_.CellPaddingX * 2.0) * table_.ColumnsEnabledCount
@@ -1249,7 +1244,7 @@ function ImGui.TableUpdateLayout(table_)
-- Allocate width for stretched/weighted columns (StretchWeight gets converted into WidthRequest) -- Allocate width for stretched/weighted columns (StretchWeight gets converted into WidthRequest)
if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 then if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 then
local weight_ratio = column.StretchWeight / stretch_sum_weights local weight_ratio = column.StretchWeight / stretch_sum_weights
column.WidthRequest = IM_TRUNC(ImMax(width_avail_for_stretched_columns * weight_ratio, table_.MinColumnWidth) + 0.01) column.WidthRequest = IM_TRUNC(mathMax(width_avail_for_stretched_columns * weight_ratio, table_.MinColumnWidth) + 0.01)
width_remaining_for_stretched_columns = width_remaining_for_stretched_columns - column.WidthRequest width_remaining_for_stretched_columns = width_remaining_for_stretched_columns - column.WidthRequest
end end
@@ -1260,7 +1255,7 @@ function ImGui.TableUpdateLayout(table_)
end end
-- Assign final width, record width in case we will need to shrink -- Assign final width, record width in case we will need to shrink
column.WidthGiven = ImTrunc(ImMax(column.WidthRequest, table_.MinColumnWidth)) column.WidthGiven = ImTrunc(mathMax(column.WidthRequest, table_.MinColumnWidth))
table_.ColumnsGivenWidth = table_.ColumnsGivenWidth + column.WidthGiven table_.ColumnsGivenWidth = table_.ColumnsGivenWidth + column.WidthGiven
end end
end end
@@ -1291,7 +1286,7 @@ function ImGui.TableUpdateLayout(table_)
table_instance.HoveredRowLast = table_instance.HoveredRowNext table_instance.HoveredRowLast = table_instance.HoveredRowNext
table_instance.HoveredRowNext = -1 table_instance.HoveredRowNext = -1
table_.HoveredColumnBody = -1; table_.HoveredColumnBorder = -1 table_.HoveredColumnBody = -1; table_.HoveredColumnBorder = -1
local mouse_hit_rect = ImRect(table_.OuterRect.Min.x, table_.OuterRect.Min.y, table_.OuterRect.Max.x, ImMax(table_.OuterRect.Max.y, table_.OuterRect.Min.y + table_instance.LastOuterHeight)) local mouse_hit_rect = ImRect(table_.OuterRect.Min.x, table_.OuterRect.Min.y, table_.OuterRect.Max.x, mathMax(table_.OuterRect.Max.y, table_.OuterRect.Min.y + table_instance.LastOuterHeight))
local backup_active_id = g.ActiveId local backup_active_id = g.ActiveId
g.ActiveId = 0 g.ActiveId = 0
local is_hovering_table = ImGui.ItemHoverable(mouse_hit_rect, 0, ImGuiItemFlags.None) local is_hovering_table = ImGui.ItemHoverable(mouse_hit_rect, 0, ImGuiItemFlags.None)
@@ -1346,8 +1341,8 @@ function ImGui.TableUpdateLayout(table_)
-- Lock width based on start position and minimum/maximum width for this position -- Lock width based on start position and minimum/maximum width for this position
column.WidthMax = ImGui.TableCalcMaxColumnWidth(table_, column_n) column.WidthMax = ImGui.TableCalcMaxColumnWidth(table_, column_n)
column.WidthGiven = ImMin(column.WidthGiven, column.WidthMax) column.WidthGiven = mathMin(column.WidthGiven, column.WidthMax)
column.WidthGiven = ImMax(column.WidthGiven, ImMin(column.WidthRequest, table_.MinColumnWidth)) column.WidthGiven = mathMax(column.WidthGiven, mathMin(column.WidthRequest, table_.MinColumnWidth))
column.MaxX = offset_x + column.WidthGiven + table_.CellSpacingX1 + table_.CellSpacingX2 + table_.CellPaddingX * 2.0 column.MaxX = offset_x + column.WidthGiven + table_.CellSpacingX1 + table_.CellSpacingX2 + table_.CellPaddingX * 2.0
-- Lock other positions -- Lock other positions
@@ -1455,7 +1450,7 @@ function ImGui.TableUpdateLayout(table_)
-- [Part 7] Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it) -- [Part 7] Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it)
-- Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either -- Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either
-- because of using _WidthAuto/_WidthStretch). This will hide the resizing option from the context menu. -- because of using _WidthAuto/_WidthStretch). This will hide the resizing option from the context menu.
local unused_x1 = ImMax(table_.WorkRect.Min.x, table_.Columns[table_.RightMostEnabledColumn].ClipRect.Max.x) local unused_x1 = mathMax(table_.WorkRect.Min.x, table_.Columns[table_.RightMostEnabledColumn].ClipRect.Max.x)
if is_hovering_table and table_.HoveredColumnBody == -1 then if is_hovering_table and table_.HoveredColumnBody == -1 then
if mouse_skewed_x >= unused_x1 then if mouse_skewed_x >= unused_x1 then
table_.HoveredColumnBody = columns_count table_.HoveredColumnBody = columns_count
@@ -1475,7 +1470,7 @@ function ImGui.TableUpdateLayout(table_)
end end
if band(table_.Flags, ImGuiTableFlags.NoHostExtendX) ~= 0 then if band(table_.Flags, ImGuiTableFlags.NoHostExtendX) ~= 0 then
table_.OuterRect.Max.x = unused_x1; table_.WorkRect.Max.x = unused_x1 table_.OuterRect.Max.x = unused_x1; table_.WorkRect.Max.x = unused_x1
table_.InnerClipRect.Max.x = ImMin(table_.InnerClipRect.Max.x, unused_x1) table_.InnerClipRect.Max.x = mathMin(table_.InnerClipRect.Max.x, unused_x1)
end end
ImRect_Copy(table_.InnerWindow.ParentWorkRect, table_.WorkRect) ImRect_Copy(table_.InnerWindow.ParentWorkRect, table_.WorkRect)
table_.BorderX1 = table_.InnerClipRect.Min.x table_.BorderX1 = table_.InnerClipRect.Min.x
@@ -1486,7 +1481,7 @@ function ImGui.TableUpdateLayout(table_)
if band(table_.Flags, ImGuiTableFlags.NoHostExtendY) ~= 0 then if band(table_.Flags, ImGuiTableFlags.NoHostExtendY) ~= 0 then
window_content_max_y = table_.OuterRect.Max.y window_content_max_y = table_.OuterRect.Max.y
else else
window_content_max_y = ImMax(table_.InnerWindow.ContentRegionRect.Max.y, (band(table_.Flags, ImGuiTableFlags.ScrollY) ~= 0) and 0.0 or table_.OuterRect.Max.y) window_content_max_y = mathMax(table_.InnerWindow.ContentRegionRect.Max.y, (band(table_.Flags, ImGuiTableFlags.ScrollY) ~= 0) and 0.0 or table_.OuterRect.Max.y)
end end
table_.InnerWindow.WorkRect.Max.y = ImClamp(window_content_max_y - g.Style.CellPadding.y, table_.InnerWindow.WorkRect.Min.y, table_.InnerWindow.WorkRect.Max.y) table_.InnerWindow.WorkRect.Max.y = ImClamp(window_content_max_y - g.Style.CellPadding.y, table_.InnerWindow.WorkRect.Min.y, table_.InnerWindow.WorkRect.Max.y)
@@ -1538,7 +1533,7 @@ function ImGui.TableUpdateLayout(table_)
local inner_window = table_.InnerWindow local inner_window = table_.InnerWindow
local bs = g.BoxSelectState local bs = g.BoxSelectState
if bs and bs.Window == inner_window and bs.UnclipMode then if bs.Window == inner_window and bs.UnclipMode then
ImGui.TableApplyExternalUnclipRect(table_, bs.UnclipRect) ImGui.TableApplyExternalUnclipRect(table_, bs.UnclipRect)
end end
@@ -1578,7 +1573,7 @@ function ImGui.TableUpdateBorders(table_)
local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent)
local hit_half_width = ImTrunc(TABLE_RESIZE_SEPARATOR_HALF_THICKNESS * g.CurrentDpiScale) local hit_half_width = ImTrunc(TABLE_RESIZE_SEPARATOR_HALF_THICKNESS * g.CurrentDpiScale)
local hit_y1 = ((table_.FreezeRowsCount >= 1) and table_.OuterRect.Min.y or table_.WorkRect.Min.y) + table_.AngledHeadersHeight local hit_y1 = ((table_.FreezeRowsCount >= 1) and table_.OuterRect.Min.y or table_.WorkRect.Min.y) + table_.AngledHeadersHeight
local hit_y2_body = ImMax(table_.OuterRect.Max.y, hit_y1 + table_instance.LastOuterHeight - table_.AngledHeadersHeight) local hit_y2_body = mathMax(table_.OuterRect.Max.y, hit_y1 + table_instance.LastOuterHeight - table_.AngledHeadersHeight)
local hit_y2_head = hit_y1 + table_instance.LastTopHeadersRowHeight local hit_y2_head = hit_y1 + table_instance.LastTopHeadersRowHeight
for order_n = 0, table_.ColumnsCount - 1 do for order_n = 0, table_.ColumnsCount - 1 do
@@ -1672,10 +1667,10 @@ function ImGui.EndTable()
if inner_window ~= outer_window then if inner_window ~= outer_window then
inner_window.DC.CursorMaxPos.y = inner_content_max_y inner_window.DC.CursorMaxPos.y = inner_content_max_y
elseif band(flags, ImGuiTableFlags.NoHostExtendY) == 0 then elseif band(flags, ImGuiTableFlags.NoHostExtendY) == 0 then
local v = ImMax(table_.OuterRect.Max.y, inner_content_max_y) -- Patch OuterRect/InnerRect height local v = mathMax(table_.OuterRect.Max.y, inner_content_max_y) -- Patch OuterRect/InnerRect height
table_.OuterRect.Max.y = v; table_.InnerRect.Max.y = v table_.OuterRect.Max.y = v; table_.InnerRect.Max.y = v
end end
table_.WorkRect.Max.y = ImMax(table_.WorkRect.Max.y, table_.OuterRect.Max.y) table_.WorkRect.Max.y = mathMax(table_.WorkRect.Max.y, table_.OuterRect.Max.y)
table_instance.LastOuterHeight = table_.OuterRect:GetHeight() table_instance.LastOuterHeight = table_.OuterRect:GetHeight()
-- Setup inner scrolling range -- Setup inner scrolling range
@@ -1685,10 +1680,10 @@ function ImGui.EndTable()
local outer_padding_for_border = (band(table_.Flags, ImGuiTableFlags.BordersOuterV) ~= 0) and TABLE_BORDER_SIZE or 0.0 local outer_padding_for_border = (band(table_.Flags, ImGuiTableFlags.BordersOuterV) ~= 0) and TABLE_BORDER_SIZE or 0.0
local max_pos_x = inner_window.DC.CursorMaxPos.x local max_pos_x = inner_window.DC.CursorMaxPos.x
if table_.RightMostEnabledColumn ~= -1 then if table_.RightMostEnabledColumn ~= -1 then
max_pos_x = ImMax(max_pos_x, table_.Columns[table_.RightMostEnabledColumn].WorkMaxX + table_.CellPaddingX + table_.OuterPaddingX - outer_padding_for_border) max_pos_x = mathMax(max_pos_x, table_.Columns[table_.RightMostEnabledColumn].WorkMaxX + table_.CellPaddingX + table_.OuterPaddingX - outer_padding_for_border)
end end
if table_.ResizedColumn ~= -1 then if table_.ResizedColumn ~= -1 then
max_pos_x = ImMax(max_pos_x, table_.ResizeLockMinContentsX2) max_pos_x = mathMax(max_pos_x, table_.ResizeLockMinContentsX2)
end end
inner_window.DC.CursorMaxPos.x = max_pos_x + table_.TempData.AngledHeadersExtraWidth inner_window.DC.CursorMaxPos.x = max_pos_x + table_.TempData.AngledHeadersExtraWidth
end end
@@ -1731,12 +1726,12 @@ function ImGui.EndTable()
auto_fit_width_for_stretched = auto_fit_width_for_stretched + column_width_request auto_fit_width_for_stretched = auto_fit_width_for_stretched + column_width_request
end end
if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoResize) ~= 0 then if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoResize) ~= 0 then
auto_fit_width_for_stretched_min = ImMax(auto_fit_width_for_stretched_min, column_width_request / (column.StretchWeight / table_.ColumnsStretchSumWeights)) auto_fit_width_for_stretched_min = mathMax(auto_fit_width_for_stretched_min, column_width_request / (column.StretchWeight / table_.ColumnsStretchSumWeights))
end end
end end
end end
local width_spacings = (table_.OuterPaddingX * 2.0) + (table_.CellSpacingX1 + table_.CellSpacingX2) * (table_.ColumnsEnabledCount - 1) local width_spacings = (table_.OuterPaddingX * 2.0) + (table_.CellSpacingX1 + table_.CellSpacingX2) * (table_.ColumnsEnabledCount - 1)
table_.ColumnsAutoFitWidth = width_spacings + (table_.CellPaddingX * 2.0) * table_.ColumnsEnabledCount + auto_fit_width_for_fixed + ImMax(auto_fit_width_for_stretched, auto_fit_width_for_stretched_min) table_.ColumnsAutoFitWidth = width_spacings + (table_.CellPaddingX * 2.0) * table_.ColumnsEnabledCount + auto_fit_width_for_fixed + mathMax(auto_fit_width_for_stretched, auto_fit_width_for_stretched_min)
-- Update scroll -- Update scroll
if band(table_.Flags, ImGuiTableFlags.ScrollX) == 0 and inner_window ~= outer_window then if band(table_.Flags, ImGuiTableFlags.ScrollX) == 0 and inner_window ~= outer_window then
@@ -1801,27 +1796,27 @@ function ImGui.EndTable()
-- FIXME-TABLE: Could we remove this section? -- FIXME-TABLE: Could we remove this section?
-- ColumnsAutoFitWidth may be one frame ahead here since for Fixed+NoResize is calculated from latest contents -- ColumnsAutoFitWidth may be one frame ahead here since for Fixed+NoResize is calculated from latest contents
IM_ASSERT(band(table_.Flags, ImGuiTableFlags.ScrollX) == 0) IM_ASSERT(band(table_.Flags, ImGuiTableFlags.ScrollX) == 0)
outer_window.DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table_.OuterRect.Min.x + table_.ColumnsAutoFitWidth) outer_window.DC.CursorMaxPos.x = mathMax(backup_outer_max_pos.x, table_.OuterRect.Min.x + table_.ColumnsAutoFitWidth)
elseif temp_data.UserOuterSize.x <= 0.0 then elseif temp_data.UserOuterSize.x <= 0.0 then
-- Some references for this: #7651 + tests "table_reported_size", "table_reported_size_outer" equivalent Y block, #9352 -- Some references for this: #7651 + tests "table_reported_size", "table_reported_size_outer" equivalent Y block, #9352
-- - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback? See broken test in 'table_reported_size_outer' -- - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback? See broken test in 'table_reported_size_outer'
local outer_content_max_x = table_.OuterRect.Min.x + table_.ColumnsAutoFitWidth local outer_content_max_x = table_.OuterRect.Min.x + table_.ColumnsAutoFitWidth
local decoration_size = table_.TempData.AngledHeadersExtraWidth + ((inner_window ~= outer_window) and inner_window.ScrollbarSizes.x or 0.0) local decoration_size = table_.TempData.AngledHeadersExtraWidth + ((inner_window ~= outer_window) and inner_window.ScrollbarSizes.x or 0.0)
outer_window.DC.IdealMaxPos.x = ImMax(outer_window.DC.IdealMaxPos.x, outer_content_max_x + decoration_size - temp_data.UserOuterSize.x) outer_window.DC.IdealMaxPos.x = mathMax(outer_window.DC.IdealMaxPos.x, outer_content_max_x + decoration_size - temp_data.UserOuterSize.x)
outer_window.DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table_.OuterRect.Max.x, outer_content_max_x + decoration_size)) outer_window.DC.CursorMaxPos.x = mathMax(backup_outer_max_pos.x, mathMin(table_.OuterRect.Max.x, outer_content_max_x + decoration_size))
else else
outer_window.DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table_.OuterRect.Max.x) outer_window.DC.CursorMaxPos.x = mathMax(backup_outer_max_pos.x, table_.OuterRect.Max.x)
end end
if temp_data.UserOuterSize.y <= 0.0 then if temp_data.UserOuterSize.y <= 0.0 then
-- (same comment as above) -- (same comment as above)
local outer_content_size_y = (inner_window == outer_window) and (inner_content_max_y - table_.InnerRect.Min.y) or (inner_content_max_y - inner_window.DC.CursorStartPos.y) local outer_content_size_y = (inner_window == outer_window) and (inner_content_max_y - table_.InnerRect.Min.y) or (inner_content_max_y - inner_window.DC.CursorStartPos.y)
local outer_content_max_y = table_.OuterRect.Min.y + outer_content_size_y local outer_content_max_y = table_.OuterRect.Min.y + outer_content_size_y
local decoration_size = (inner_window ~= outer_window) and inner_window.ScrollbarSizes.y or 0.0 local decoration_size = (inner_window ~= outer_window) and inner_window.ScrollbarSizes.y or 0.0
outer_window.DC.IdealMaxPos.y = ImMax(outer_window.DC.IdealMaxPos.y, outer_content_max_y + decoration_size - temp_data.UserOuterSize.y) outer_window.DC.IdealMaxPos.y = mathMax(outer_window.DC.IdealMaxPos.y, outer_content_max_y + decoration_size - temp_data.UserOuterSize.y)
outer_window.DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table_.OuterRect.Max.y, outer_content_max_y + decoration_size)) outer_window.DC.CursorMaxPos.y = mathMax(backup_outer_max_pos.y, mathMin(table_.OuterRect.Max.y, outer_content_max_y + decoration_size))
else else
-- OuterRect.Max.y may already have been pushed downward from the initial value (unless ImGuiTableFlags.NoHostExtendY is set) -- OuterRect.Max.y may already have been pushed downward from the initial value (unless ImGuiTableFlags.NoHostExtendY is set)
outer_window.DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, table_.OuterRect.Max.y) outer_window.DC.CursorMaxPos.y = mathMax(backup_outer_max_pos.y, table_.OuterRect.Max.y)
end end
-- Save settings -- Save settings
@@ -2039,7 +2034,7 @@ function ImGui.TableSetupScrollFreeze(columns, rows)
IM_ASSERT(columns >= 0 and columns < IMGUI_TABLE_MAX_COLUMNS) IM_ASSERT(columns >= 0 and columns < IMGUI_TABLE_MAX_COLUMNS)
IM_ASSERT(rows >= 0 and rows < 128) -- Arbitrary limit IM_ASSERT(rows >= 0 and rows < 128) -- Arbitrary limit
table_.FreezeColumnsRequest = (band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0) and ImMin(columns, table_.ColumnsCount) or 0 table_.FreezeColumnsRequest = (band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0) and mathMin(columns, table_.ColumnsCount) or 0
table_.FreezeColumnsCount = (table_.InnerWindow.Scroll.x ~= 0.0) and table_.FreezeColumnsRequest or 0 table_.FreezeColumnsCount = (table_.InnerWindow.Scroll.x ~= 0.0) and table_.FreezeColumnsRequest or 0
table_.FreezeRowsRequest = (band(table_.Flags, ImGuiTableFlags.ScrollY) ~= 0) and rows or 0 table_.FreezeRowsRequest = (band(table_.Flags, ImGuiTableFlags.ScrollY) ~= 0) and rows or 0
table_.FreezeRowsCount = (table_.InnerWindow.Scroll.y ~= 0.0) and table_.FreezeRowsRequest or 0 table_.FreezeRowsCount = (table_.InnerWindow.Scroll.y ~= 0.0) and table_.FreezeRowsRequest or 0
@@ -2065,11 +2060,23 @@ end
-- they would meddle many times with the underlying ImDrawCmd. -- they would meddle many times with the underlying ImDrawCmd.
-- Instead, we do a preemptive overwrite of clipping rectangle _without_ altering the command-buffer and let -- Instead, we do a preemptive overwrite of clipping rectangle _without_ altering the command-buffer and let
-- the subsequent single call to SetCurrentChannel() does it things once. -- the subsequent single call to SetCurrentChannel() does it things once.
@native
function ImGui.SetWindowClipRectBeforeSetChannel(window, clip_rect) function ImGui.SetWindowClipRectBeforeSetChannel(window, clip_rect)
local clip_rect_vec4 = clip_rect:ToVec4() -- [Luau] scalar copies, no temporary ImVec4: the clip rect stack top is a uniquely owned ImVec4 (PushClipRect always pushes a fresh one), so overwrite it in place
ImRect_Copy(window.ClipRect, clip_rect) local min, max = clip_rect.Min, clip_rect.Max
ImVec4_Copy(window.DrawList._CmdHeader.ClipRect, clip_rect_vec4) local x1, y1, x2, y2 = min.x, min.y, max.x, max.y
window.DrawList._ClipRectStack.Data[window.DrawList._ClipRectStack.Size] = clip_rect_vec4 local wcr = window.ClipRect
wcr.Min.x = x1; wcr.Min.y = y1; wcr.Max.x = x2; wcr.Max.y = y2
local draw_list = window.DrawList
local hcr = draw_list._CmdHeader.ClipRect
hcr.x = x1; hcr.y = y1; hcr.z = x2; hcr.w = y2
local stack = draw_list._ClipRectStack
local top = stack.Data[stack.Size]
if top ~= nil then
top.x = x1; top.y = y1; top.z = x2; top.w = y2
else
stack.Data[stack.Size] = ImVec4(x1, y1, x2, y2)
end
end end
function ImGui.TableGetColumnCount() function ImGui.TableGetColumnCount()
@@ -2154,8 +2161,8 @@ function ImGui.TableGetCellBgRect(table_, column_n)
-- x1 = x1 - table.OuterPaddingX -- x1 = x1 - table.OuterPaddingX
--if column.NextEnabledColumn == -1 then --if column.NextEnabledColumn == -1 then
-- x2 = x2 + table.OuterPaddingX -- x2 = x2 + table.OuterPaddingX
x1 = ImMax(x1, table_.WorkRect.Min.x) x1 = mathMax(x1, table_.WorkRect.Min.x)
x2 = ImMin(x2, table_.WorkRect.Max.x) x2 = mathMin(x2, table_.WorkRect.Max.x)
return ImRect(x1, table_.RowPosY1, x2, table_.RowPosY2) return ImRect(x1, table_.RowPosY1, x2, table_.RowPosY2)
end end
@@ -2274,8 +2281,7 @@ function ImGui.TableNextRow(row_flags, row_min_height)
-- We honor min_row_height requested by user, but cannot guarantee per-row maximum height, -- We honor min_row_height requested by user, but cannot guarantee per-row maximum height,
-- because that would essentially require a unique clipping rectangle per-cell. -- because that would essentially require a unique clipping rectangle per-cell.
table_.RowPosY2 = table_.RowPosY2 + table_.RowCellPaddingY * 2.0 table_.RowPosY2 = mathMax(table_.RowPosY2 + table_.RowCellPaddingY * 2.0, table_.RowPosY1 + row_min_height) -- [Luau] inline ImMax
table_.RowPosY2 = ImMax(table_.RowPosY2, table_.RowPosY1 + row_min_height)
-- Disable output until user calls TableNextColumn() -- Disable output until user calls TableNextColumn()
table_.InnerWindow.SkipItems = true table_.InnerWindow.SkipItems = true
@@ -2302,15 +2308,16 @@ function ImGui.TableBeginRow(table_)
table_.RowPosY1 = next_y1; table_.RowPosY2 = next_y1 table_.RowPosY1 = next_y1; table_.RowPosY2 = next_y1
table_.RowTextBaseline = 0.0 table_.RowTextBaseline = 0.0
table_.RowIndentOffsetX = window.DC.Indent.x - table_.HostIndentX -- Lock indent local dc = window.DC -- [Luau] cache
table_.RowIndentOffsetX = dc.Indent.x - table_.HostIndentX -- Lock indent
window.DC.PrevLineTextBaseOffset = 0.0 dc.PrevLineTextBaseOffset = 0.0
window.DC.CursorPosPrevLine.x = window.DC.CursorPos.x dc.CursorPosPrevLine.x = dc.CursorPos.x
window.DC.CursorPosPrevLine.y = window.DC.CursorPos.y + table_.RowCellPaddingY -- This allows users to call SameLine() to share LineSize between columns. dc.CursorPosPrevLine.y = dc.CursorPos.y + table_.RowCellPaddingY -- This allows users to call SameLine() to share LineSize between columns.
window.DC.PrevLineSize.x = 0.0; window.DC.PrevLineSize.y = 0.0 -- This allows users to call SameLine() to share LineSize between columns, and to call it from first column too. dc.PrevLineSize.x = 0.0; dc.PrevLineSize.y = 0.0 -- This allows users to call SameLine() to share LineSize between columns, and to call it from first column too.
window.DC.CurrLineSize.x = 0.0; window.DC.CurrLineSize.y = 0.0 dc.CurrLineSize.x = 0.0; dc.CurrLineSize.y = 0.0
window.DC.IsSameLine = false; window.DC.IsSetPos = false dc.IsSameLine = false; dc.IsSetPos = false
window.DC.CursorMaxPos.y = next_y1 dc.CursorMaxPos.y = next_y1
-- Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. -- Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging.
if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 then if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 then
@@ -2321,6 +2328,9 @@ function ImGui.TableBeginRow(table_)
end end
end end
-- [Luau] scratch vectors for TableEndRow() row background (never escape)
local TableEndRow_RowMin, TableEndRow_RowMax = ImVec2(), ImVec2()
-- [Internal] Called by TableNextRow() -- [Internal] Called by TableNextRow()
function ImGui.TableEndRow(table_) function ImGui.TableEndRow(table_)
local g = GImGui local g = GImGui
@@ -2347,7 +2357,7 @@ function ImGui.TableEndRow(table_)
local bg_y2 = table_.RowPosY2 local bg_y2 = table_.RowPosY2
local unfreeze_rows_actual = (table_.CurrentRow + 1 == table_.FreezeRowsCount) local unfreeze_rows_actual = (table_.CurrentRow + 1 == table_.FreezeRowsCount)
local unfreeze_rows_request = (table_.CurrentRow + 1 == table_.FreezeRowsRequest) local unfreeze_rows_request = (table_.CurrentRow + 1 == table_.FreezeRowsRequest)
local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) local table_instance = (table_.InstanceCurrent == 0) and table_.InstanceDataFirst or ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) -- [Luau] inline common case
if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 and (table_.CurrentRow == 0 or band(table_.LastRowFlags, ImGuiTableRowFlags.Headers) ~= 0) then if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 and (table_.CurrentRow == 0 or band(table_.LastRowFlags, ImGuiTableRowFlags.Headers) ~= 0) then
table_instance.LastTopHeadersRowHeight = table_instance.LastTopHeadersRowHeight + bg_y2 - bg_y1 table_instance.LastTopHeadersRowHeight = table_instance.LastTopHeadersRowHeight + bg_y2 - bg_y1
end end
@@ -2384,7 +2394,9 @@ function ImGui.TableEndRow(table_)
-- In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is -- In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is
-- always followed by a change of clipping rectangle we perform the smallest overwrite possible here. -- always followed by a change of clipping rectangle we perform the smallest overwrite possible here.
if band(table_.Flags, ImGuiTableFlags.NoClip) == 0 then if band(table_.Flags, ImGuiTableFlags.NoClip) == 0 then
ImVec4_Copy(window.DrawList._CmdHeader.ClipRect, table_.Bg0ClipRectForDrawCmd:ToVec4()) -- [Luau] scalar copy, no temporary ImVec4
local src, dst = table_.Bg0ClipRectForDrawCmd, window.DrawList._CmdHeader.ClipRect
dst.x = src.Min.x; dst.y = src.Min.y; dst.z = src.Max.x; dst.w = src.Max.y
end end
table_.DrawSplitter:SetCurrentChannel(window.DrawList, TABLE_DRAW_CHANNEL_BG0) table_.DrawSplitter:SetCurrentChannel(window.DrawList, TABLE_DRAW_CHANNEL_BG0)
end end
@@ -2392,13 +2404,16 @@ function ImGui.TableEndRow(table_)
-- Draw row background -- Draw row background
-- We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle -- We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle
if bg_col0 ~= 0 or bg_col1 ~= 0 then if bg_col0 ~= 0 or bg_col1 ~= 0 then
local row_rect = ImRect(table_.WorkRect.Min.x, bg_y1, table_.WorkRect.Max.x, bg_y2) -- [Luau] row_rect = ImRect(WorkRect.Min.x, bg_y1, WorkRect.Max.x, bg_y2); row_rect:ClipWith(BgClipRect), in scratch vectors (only read by AddRectFilled)
row_rect:ClipWith(table_.BgClipRect) local bg_clip_rect = table_.BgClipRect
if bg_col0 ~= 0 and row_rect.Min.y < row_rect.Max.y then local row_min, row_max = TableEndRow_RowMin, TableEndRow_RowMax
window.DrawList:AddRectFilled(row_rect.Min, row_rect.Max, bg_col0) row_min.x = mathMax(table_.WorkRect.Min.x, bg_clip_rect.Min.x); row_min.y = mathMax(bg_y1, bg_clip_rect.Min.y)
row_max.x = mathMin(table_.WorkRect.Max.x, bg_clip_rect.Max.x); row_max.y = mathMin(bg_y2, bg_clip_rect.Max.y)
if bg_col0 ~= 0 and row_min.y < row_max.y then
window.DrawList:AddRectFilled(row_min, row_max, bg_col0)
end end
if bg_col1 ~= 0 and row_rect.Min.y < row_rect.Max.y then if bg_col1 ~= 0 and row_min.y < row_max.y then
window.DrawList:AddRectFilled(row_rect.Min, row_rect.Max, bg_col1) window.DrawList:AddRectFilled(row_min, row_max, bg_col1)
end end
end end
@@ -2411,8 +2426,8 @@ function ImGui.TableEndRow(table_)
local column = table_.Columns[cell_data.Column] local column = table_.Columns[cell_data.Column]
local cell_bg_rect = ImGui.TableGetCellBgRect(table_, cell_data.Column) local cell_bg_rect = ImGui.TableGetCellBgRect(table_, cell_data.Column)
cell_bg_rect:ClipWith(table_.BgClipRect) cell_bg_rect:ClipWith(table_.BgClipRect)
cell_bg_rect.Min.x = ImMax(cell_bg_rect.Min.x, column.ClipRect.Min.x) -- So that first column after frozen one gets clipped when scrolling cell_bg_rect.Min.x = mathMax(cell_bg_rect.Min.x, column.ClipRect.Min.x) -- So that first column after frozen one gets clipped when scrolling
cell_bg_rect.Max.x = ImMin(cell_bg_rect.Max.x, column.MaxX) cell_bg_rect.Max.x = mathMin(cell_bg_rect.Max.x, column.MaxX)
if cell_bg_rect.Min.y < cell_bg_rect.Max.y then if cell_bg_rect.Min.y < cell_bg_rect.Max.y then
window.DrawList:AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data.BgColor) window.DrawList:AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data.BgColor)
end end
@@ -2438,7 +2453,7 @@ function ImGui.TableEndRow(table_)
for column_n = 0, table_.ColumnsCount - 1 do for column_n = 0, table_.ColumnsCount - 1 do
table_.Columns[column_n].NavLayerCurrent = table_.NavLayer table_.Columns[column_n].NavLayerCurrent = table_.NavLayer
end end
local y0 = ImMax(table_.RowPosY2 + 1, table_.InnerClipRect.Min.y) local y0 = mathMax(table_.RowPosY2 + 1, table_.InnerClipRect.Min.y)
table_instance.LastFrozenHeight = y0 - table_.OuterRect.Min.y table_instance.LastFrozenHeight = y0 - table_.OuterRect.Min.y
if unfreeze_rows_actual then if unfreeze_rows_actual then
@@ -2446,7 +2461,7 @@ function ImGui.TableEndRow(table_)
table_.IsUnfrozenRows = true table_.IsUnfrozenRows = true
-- BgClipRect starts as table.InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect -- BgClipRect starts as table.InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect
local min_y = ImMin(y0, table_.InnerClipRect.Max.y) local min_y = mathMin(y0, table_.InnerClipRect.Max.y)
table_.BgClipRect.Min.y = min_y; table_.Bg2ClipRectForDrawCmd.Min.y = min_y table_.BgClipRect.Min.y = min_y; table_.Bg2ClipRectForDrawCmd.Min.y = min_y
table_.BgClipRect.Max.y = table_.InnerClipRect.Max.y; table_.Bg2ClipRectForDrawCmd.Max.y = table_.InnerClipRect.Max.y table_.BgClipRect.Max.y = table_.InnerClipRect.Max.y; table_.Bg2ClipRectForDrawCmd.Max.y = table_.InnerClipRect.Max.y
table_.Bg2DrawChannelCurrent = table_.Bg2DrawChannelUnfrozen table_.Bg2DrawChannelCurrent = table_.Bg2DrawChannelUnfrozen
@@ -2518,6 +2533,7 @@ function ImGui.TableSetColumnIndex(column_n)
end end
-- [Public] Append into the next column, wrap and create a new row when already on last column -- [Public] Append into the next column, wrap and create a new row when already on last column
@native
function ImGui.TableNextColumn() function ImGui.TableNextColumn()
local g = GImGui local g = GImGui
local table_ = g.CurrentTable local table_ = g.CurrentTable
@@ -2525,28 +2541,33 @@ function ImGui.TableNextColumn()
return false return false
end end
if table_.IsInsideRow and table_.CurrentColumn + 1 < table_.ColumnsCount then local current_column = table_.CurrentColumn
if table_.CurrentColumn ~= -1 then local next_column
if table_.IsInsideRow and current_column + 1 < table_.ColumnsCount then
if current_column ~= -1 then
ImGui.TableEndCell(table_) ImGui.TableEndCell(table_)
end end
ImGui.TableBeginCell(table_, table_.CurrentColumn + 1) next_column = current_column + 1
else else
ImGui.TableNextRow() ImGui.TableNextRow()
ImGui.TableBeginCell(table_, 0) next_column = 0
end end
ImGui.TableBeginCell(table_, next_column)
-- Return whether the column is visible. User may choose to skip submitting items based on this return value, -- Return whether the column is visible. User may choose to skip submitting items based on this return value,
-- however they shouldn't skip submitting for columns that may have the tallest contribution to row height. -- however they shouldn't skip submitting for columns that may have the tallest contribution to row height.
return table_.Columns[table_.CurrentColumn].IsRequestOutput return table_.Columns[next_column].IsRequestOutput
end end
-- [Internal] Called by TableSetColumnIndex()/TableNextColumn() -- [Internal] Called by TableSetColumnIndex()/TableNextColumn()
-- This is called very frequently, so we need to be mindful of unnecessary overhead. -- This is called very frequently, so we need to be mindful of unnecessary overhead.
-- FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns. -- FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns.
@native
function ImGui.TableBeginCell(table_, column_n) function ImGui.TableBeginCell(table_, column_n)
local g = GImGui local g = GImGui
local column = table_.Columns[column_n] local column = table_.Columns[column_n]
local window = table_.InnerWindow local window = table_.InnerWindow
local dc = window.DC -- [Luau] cache
table_.CurrentColumn = column_n table_.CurrentColumn = column_n
-- Start position is roughly ~~ CellRect.Min + CellPadding + Indent -- Start position is roughly ~~ CellRect.Min + CellPadding + Indent
@@ -2555,35 +2576,45 @@ function ImGui.TableBeginCell(table_, column_n)
start_x = start_x + table_.RowIndentOffsetX -- ~~ += window.DC.Indent.x - table.HostIndentX, except we locked it for the row. start_x = start_x + table_.RowIndentOffsetX -- ~~ += window.DC.Indent.x - table.HostIndentX, except we locked it for the row.
end end
window.DC.CursorPos.x = start_x local start_y = table_.RowPosY1 + table_.RowCellPaddingY
window.DC.CursorPos.y = table_.RowPosY1 + table_.RowCellPaddingY dc.CursorPos.x = start_x
window.DC.CursorMaxPos.x = window.DC.CursorPos.x dc.CursorPos.y = start_y
window.DC.ColumnsOffset.x = start_x - window.Pos.x - window.DC.Indent.x -- FIXME-WORKRECT dc.CursorMaxPos.x = start_x
window.DC.CursorPosPrevLine.x = window.DC.CursorPos.x -- PrevLine.y is preserved. This allows users to call SameLine() to share LineSize between columns. dc.ColumnsOffset.x = start_x - window.Pos.x - dc.Indent.x -- FIXME-WORKRECT
window.DC.CurrLineTextBaseOffset = table_.RowTextBaseline dc.CursorPosPrevLine.x = start_x -- PrevLine.y is preserved. This allows users to call SameLine() to share LineSize between columns.
window.DC.NavLayerCurrent = column.NavLayerCurrent dc.CurrLineTextBaseOffset = table_.RowTextBaseline
dc.NavLayerCurrent = column.NavLayerCurrent
-- Note how WorkRect.Max.y is only set once during layout -- Note how WorkRect.Max.y is only set once during layout
window.WorkRect.Min.y = window.DC.CursorPos.y local work_rect = window.WorkRect
window.WorkRect.Min.x = column.WorkMinX work_rect.Min.y = start_y
window.WorkRect.Max.x = column.WorkMaxX work_rect.Min.x = column.WorkMinX
window.DC.ItemWidth = column.ItemWidth work_rect.Max.x = column.WorkMaxX
dc.ItemWidth = column.ItemWidth
window.SkipItems = column.IsSkipItems local skip_items = column.IsSkipItems
if column.IsSkipItems then window.SkipItems = skip_items
if skip_items then
g.LastItemData.ID = 0 g.LastItemData.ID = 0
g.LastItemData.StatusFlags = 0 g.LastItemData.StatusFlags = 0
end end
-- Also see TablePushColumnChannel() -- Also see TablePushColumnChannel()
-- [Luau] inline the SetCurrentChannel() early-out (same channel) to skip the method call
local splitter = table_.DrawSplitter
if band(table_.Flags, ImGuiTableFlags.NoClip) ~= 0 then if band(table_.Flags, ImGuiTableFlags.NoClip) ~= 0 then
-- FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed. -- FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed.
table_.DrawSplitter:SetCurrentChannel(window.DrawList, TABLE_DRAW_CHANNEL_NOCLIP) if splitter._Current ~= TABLE_DRAW_CHANNEL_NOCLIP then
splitter:SetCurrentChannel(window.DrawList, TABLE_DRAW_CHANNEL_NOCLIP)
end
--IM_ASSERT(table.DrawSplitter._Current == TABLE_DRAW_CHANNEL_NOCLIP) --IM_ASSERT(table.DrawSplitter._Current == TABLE_DRAW_CHANNEL_NOCLIP)
else else
-- FIXME-TABLE: Could avoid this if draw channel is dummy channel? -- FIXME-TABLE: Could avoid this if draw channel is dummy channel?
ImGui.SetWindowClipRectBeforeSetChannel(window, column.ClipRect) ImGui.SetWindowClipRectBeforeSetChannel(window, column.ClipRect)
table_.DrawSplitter:SetCurrentChannel(window.DrawList, column.DrawChannelCurrent) local channel = column.DrawChannelCurrent
if splitter._Current ~= channel then
splitter:SetCurrentChannel(window.DrawList, channel)
end
end end
-- Logging -- Logging
@@ -2594,31 +2625,34 @@ function ImGui.TableBeginCell(table_, column_n)
end end
-- [Internal] Called by TableNextRow()/TableSetColumnIndex()/TableNextColumn() -- [Internal] Called by TableNextRow()/TableSetColumnIndex()/TableNextColumn()
@native
function ImGui.TableEndCell(table_) function ImGui.TableEndCell(table_)
local column = table_.Columns[table_.CurrentColumn] local column = table_.Columns[table_.CurrentColumn]
local window = table_.InnerWindow local window = table_.InnerWindow
local dc = window.DC -- [Luau] cache; mathMax() inlined as math.max below
if window.DC.IsSetPos then if dc.IsSetPos then
ImGui.ErrorCheckUsingSetCursorPosToExtendParentBoundaries() ImGui.ErrorCheckUsingSetCursorPosToExtendParentBoundaries()
end end
-- Report maximum position so we can infer content size per column. -- Report maximum position so we can infer content size per column.
local x = window.DC.CursorMaxPos.x local cursor_max_pos = dc.CursorMaxPos
local x = cursor_max_pos.x
if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 then if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 then
column.ContentMaxXHeadersUsed = ImMax(column.ContentMaxXHeadersUsed, x) -- Useful in case user submit contents in header row that is not a TableHeader() call column.ContentMaxXHeadersUsed = mathMax(column.ContentMaxXHeadersUsed, x) -- Useful in case user submit contents in header row that is not a TableHeader() call
elseif table_.IsUnfrozenRows then elseif table_.IsUnfrozenRows then
column.ContentMaxXUnfrozen = ImMax(column.ContentMaxXUnfrozen, x) column.ContentMaxXUnfrozen = mathMax(column.ContentMaxXUnfrozen, x)
else else
column.ContentMaxXFrozen = ImMax(column.ContentMaxXFrozen, x) column.ContentMaxXFrozen = mathMax(column.ContentMaxXFrozen, x)
end end
if column.IsEnabled then if column.IsEnabled then
table_.RowPosY2 = ImMax(table_.RowPosY2, window.DC.CursorMaxPos.y + table_.RowCellPaddingY) table_.RowPosY2 = mathMax(table_.RowPosY2, cursor_max_pos.y + table_.RowCellPaddingY)
end end
column.ItemWidth = window.DC.ItemWidth column.ItemWidth = dc.ItemWidth
-- Propagate text baseline for the entire row -- Propagate text baseline for the entire row
-- FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one. -- FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one.
table_.RowTextBaseline = ImMax(table_.RowTextBaseline, window.DC.PrevLineTextBaseOffset) table_.RowTextBaseline = mathMax(table_.RowTextBaseline, dc.PrevLineTextBaseOffset)
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@@ -2663,11 +2697,11 @@ end
-- Note this is meant to be stored in column.WidthAuto, please generally use the WidthAuto field -- Note this is meant to be stored in column.WidthAuto, please generally use the WidthAuto field
function ImGui.TableGetColumnWidthAuto(table_, column) function ImGui.TableGetColumnWidthAuto(table_, column)
local content_width_body = ImMax(column.ContentMaxXFrozen, column.ContentMaxXUnfrozen) - column.WorkMinX local content_width_body = mathMax(column.ContentMaxXFrozen, column.ContentMaxXUnfrozen) - column.WorkMinX
local content_width_headers = column.ContentMaxXHeadersIdeal - column.WorkMinX local content_width_headers = column.ContentMaxXHeadersIdeal - column.WorkMinX
local width_auto = content_width_body local width_auto = content_width_body
if band(column.Flags, ImGuiTableColumnFlags.NoHeaderWidth) == 0 then if band(column.Flags, ImGuiTableColumnFlags.NoHeaderWidth) == 0 then
width_auto = ImMax(width_auto, content_width_headers) width_auto = mathMax(width_auto, content_width_headers)
end end
-- Non-resizable fixed columns preserve their requested width -- Non-resizable fixed columns preserve their requested width
@@ -2677,7 +2711,7 @@ function ImGui.TableGetColumnWidthAuto(table_, column)
end end
end end
return ImMax(width_auto, table_.MinColumnWidth) return mathMax(width_auto, table_.MinColumnWidth)
end end
-- 'width' = inner column width, without padding -- 'width' = inner column width, without padding
@@ -2693,7 +2727,7 @@ function ImGui.TableSetColumnWidth(column_n, width)
-- Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded) -- Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded)
IM_ASSERT(table_.MinColumnWidth > 0.0) IM_ASSERT(table_.MinColumnWidth > 0.0)
local min_width = table_.MinColumnWidth local min_width = table_.MinColumnWidth
local max_width = ImMax(min_width, column_0.WidthMax) -- Don't use TableCalcMaxColumnWidth() here as it would rely on MinX from last instance (#7933) local max_width = mathMax(min_width, column_0.WidthMax) -- Don't use TableCalcMaxColumnWidth() here as it would rely on MinX from last instance (#7933)
column_0_width = ImClamp(column_0_width, min_width, max_width) column_0_width = ImClamp(column_0_width, min_width, max_width)
if column_0.WidthGiven == column_0_width or column_0.WidthRequest == column_0_width then if column_0.WidthGiven == column_0_width or column_0.WidthRequest == column_0_width then
return return
@@ -2754,7 +2788,7 @@ function ImGui.TableSetColumnWidth(column_n, width)
-- Resizing from right-side of a Stretch column before a Fixed column forward sizing to left-side of fixed column. -- Resizing from right-side of a Stretch column before a Fixed column forward sizing to left-side of fixed column.
-- (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) -- (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b)
local column_1_width = ImMax(column_1.WidthRequest - (column_0_width - column_0.WidthRequest), min_width) local column_1_width = mathMax(column_1.WidthRequest - (column_0_width - column_0.WidthRequest), min_width)
column_0_width = column_0.WidthRequest + column_1.WidthRequest - column_1_width column_0_width = column_0.WidthRequest + column_1.WidthRequest - column_1_width
IM_ASSERT(column_0_width > 0.0 and column_1_width > 0.0) IM_ASSERT(column_0_width > 0.0 and column_1_width > 0.0)
column_0.WidthRequest = column_0_width column_0.WidthRequest = column_0_width
@@ -2970,6 +3004,12 @@ end
-- --
-- This function is particularly tricky to understand.. take a breath. -- This function is particularly tricky to understand.. take a breath.
-- (Lua: splitter channel n lives in splitter._Channels.Data[n + 1]; bit arrays are Lua sets) -- (Lua: splitter channel n lives in splitter._Channels.Data[n + 1]; bit arrays are Lua sets)
local TableMergeDrawChannels_MergeGroups = {}
for n = 0, 3 do
TableMergeDrawChannels_MergeGroups[n] = { ClipRect = ImRect(), ChannelsCount = 0, ChannelsMask = {} }
end
local TableMergeDrawChannels_RemainingMask = {}
local TableMergeDrawChannels_DstTmp = {}
function ImGui.TableMergeDrawChannels(table_) function ImGui.TableMergeDrawChannels(table_)
local splitter = table_.DrawSplitter local splitter = table_.DrawSplitter
local channels = splitter._Channels.Data local channels = splitter._Channels.Data
@@ -2979,12 +3019,18 @@ function ImGui.TableMergeDrawChannels(table_)
-- Track which groups we are going to attempt to merge, and which channels goes into each group. -- Track which groups we are going to attempt to merge, and which channels goes into each group.
local merge_group_mask = 0x00 local merge_group_mask = 0x00
local merge_groups = {} -- [Luau] reuse file-level scratch storage (function is not re-entrant, nothing escapes)
local merge_groups = TableMergeDrawChannels_MergeGroups
for n = 0, 3 do for n = 0, 3 do
merge_groups[n] = { ClipRect = ImRect(), ChannelsCount = 0, ChannelsMask = {} } local merge_group = merge_groups[n]
local cr = merge_group.ClipRect
cr.Min.x = 0.0; cr.Min.y = 0.0; cr.Max.x = 0.0; cr.Max.y = 0.0
merge_group.ChannelsCount = 0
table.clear(merge_group.ChannelsMask)
end end
local max_draw_channels = (4 + table_.ColumnsCount * 2) local max_draw_channels = (4 + table_.ColumnsCount * 2)
local remaining_mask = {} local remaining_mask = TableMergeDrawChannels_RemainingMask
table.clear(remaining_mask)
-- 1. Scan channels and take note of those which can be merged -- 1. Scan channels and take note of those which can be merged
for column_n = 0, table_.ColumnsCount - 1 do for column_n = 0, table_.ColumnsCount - 1 do
@@ -3011,9 +3057,9 @@ function ImGui.TableMergeDrawChannels(table_)
if band(column.Flags, ImGuiTableColumnFlags.NoClip) == 0 then if band(column.Flags, ImGuiTableColumnFlags.NoClip) == 0 then
local content_max_x local content_max_x
if not has_freeze_v then if not has_freeze_v then
content_max_x = ImMax(column.ContentMaxXUnfrozen, column.ContentMaxXHeadersUsed) -- No row freeze content_max_x = mathMax(column.ContentMaxXUnfrozen, column.ContentMaxXHeadersUsed) -- No row freeze
elseif merge_group_sub_n == 0 then elseif merge_group_sub_n == 0 then
content_max_x = ImMax(column.ContentMaxXFrozen, column.ContentMaxXHeadersUsed) -- Row freeze: use width before freeze content_max_x = mathMax(column.ContentMaxXFrozen, column.ContentMaxXHeadersUsed) -- Row freeze: use width before freeze
else else
content_max_x = column.ContentMaxXUnfrozen -- Row freeze: use width after freeze content_max_x = column.ContentMaxXUnfrozen -- Row freeze: use width after freeze
end end
@@ -3026,12 +3072,18 @@ function ImGui.TableMergeDrawChannels(table_)
IM_ASSERT(channel_no < max_draw_channels) IM_ASSERT(channel_no < max_draw_channels)
local merge_group = merge_groups[merge_group_n] local merge_group = merge_groups[merge_group_n]
if merge_group.ChannelsCount == 0 then if merge_group.ChannelsCount == 0 then
merge_group.ClipRect = ImRect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX) local gcr = merge_group.ClipRect -- [Luau] in place (merge_groups are local to this call)
gcr.Min.x = FLT_MAX; gcr.Min.y = FLT_MAX; gcr.Max.x = -FLT_MAX; gcr.Max.y = -FLT_MAX
end end
merge_group.ChannelsMask[channel_no] = true merge_group.ChannelsMask[channel_no] = true
merge_group.ChannelsCount = merge_group.ChannelsCount + 1 merge_group.ChannelsCount = merge_group.ChannelsCount + 1
local cr = cmds.Data[1].ClipRect local cr = cmds.Data[1].ClipRect
merge_group.ClipRect:Add(ImRect(cr.x, cr.y, cr.z, cr.w)) -- [Luau] scalar version of merge_group.ClipRect:Add(ImRect(cr.x, cr.y, cr.z, cr.w))
local gmin, gmax = merge_group.ClipRect.Min, merge_group.ClipRect.Max
if gmin.x > cr.x then gmin.x = cr.x end
if gmin.y > cr.y then gmin.y = cr.y end
if gmax.x < cr.z then gmax.x = cr.z end
if gmax.y < cr.w then gmax.y = cr.w end
merge_group_mask = bor(merge_group_mask, lshift(1, merge_group_n)) merge_group_mask = bor(merge_group_mask, lshift(1, merge_group_n))
until true until true
end end
@@ -3046,7 +3098,8 @@ function ImGui.TableMergeDrawChannels(table_)
if merge_group_mask ~= 0 then if merge_group_mask ~= 0 then
-- We skip channel 0 (Bg0/Bg1) and 1 (Bg2 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels(). -- We skip channel 0 (Bg0/Bg1) and 1 (Bg2 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels().
local LEADING_DRAW_CHANNELS = 2 local LEADING_DRAW_CHANNELS = 2
local dst_tmp = {} local dst_tmp = TableMergeDrawChannels_DstTmp -- [Luau] scratch
table.clear(dst_tmp)
for n = LEADING_DRAW_CHANNELS, splitter._Count - 1 do for n = LEADING_DRAW_CHANNELS, splitter._Count - 1 do
remaining_mask[n] = true remaining_mask[n] = true
end end
@@ -3069,16 +3122,16 @@ function ImGui.TableMergeDrawChannels(table_)
-- FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit -- FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit
-- within host clip rect, solely because of the half-padding difference between window.WorkRect and window.InnerClipRect. -- within host clip rect, solely because of the half-padding difference between window.WorkRect and window.InnerClipRect.
if band(merge_group_n, 1) == 0 or not has_freeze_h then if band(merge_group_n, 1) == 0 or not has_freeze_h then
merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x) merge_clip_rect.Min.x = mathMin(merge_clip_rect.Min.x, host_rect.Min.x)
end end
if band(merge_group_n, 2) == 0 or not has_freeze_v then if band(merge_group_n, 2) == 0 or not has_freeze_v then
merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y) merge_clip_rect.Min.y = mathMin(merge_clip_rect.Min.y, host_rect.Min.y)
end end
if band(merge_group_n, 1) ~= 0 then if band(merge_group_n, 1) ~= 0 then
merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x) merge_clip_rect.Max.x = mathMax(merge_clip_rect.Max.x, host_rect.Max.x)
end end
if band(merge_group_n, 2) ~= 0 and band(table_.Flags, ImGuiTableFlags.NoHostExtendY) == 0 then if band(merge_group_n, 2) ~= 0 and band(table_.Flags, ImGuiTableFlags.NoHostExtendY) == 0 then
merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y) merge_clip_rect.Max.y = mathMax(merge_clip_rect.Max.y, host_rect.Max.y)
end end
--ImGui.GetForegroundDrawList():AddRect(merge_group.ClipRect.Min, merge_group.ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0, 0, 1.0) -- [DEBUG] --ImGui.GetForegroundDrawList():AddRect(merge_group.ClipRect.Min, merge_group.ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0, 0, 1.0) -- [DEBUG]
--ImGui.GetForegroundDrawList():AddLine(merge_group.ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200)) --ImGui.GetForegroundDrawList():AddLine(merge_group.ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200))
@@ -3096,7 +3149,8 @@ function ImGui.TableMergeDrawChannels(table_)
local channel = channels[n + 1] local channel = channels[n + 1]
IM_ASSERT(channel._CmdBuffer.Size == 1) IM_ASSERT(channel._CmdBuffer.Size == 1)
ImVec4_Copy(channel._CmdBuffer.Data[1].ClipRect, merge_clip_rect:ToVec4()) local dst = channel._CmdBuffer.Data[1].ClipRect -- [Luau] scalar copy, no temporary ImVec4
dst.x = merge_clip_rect.Min.x; dst.y = merge_clip_rect.Min.y; dst.z = merge_clip_rect.Max.x; dst.w = merge_clip_rect.Max.y
dst_tmp[#dst_tmp + 1] = channel dst_tmp[#dst_tmp + 1] = channel
end end
n = n + 1 n = n + 1
@@ -3122,6 +3176,7 @@ function ImGui.TableMergeDrawChannels(table_)
for i = 1, #dst_tmp do for i = 1, #dst_tmp do
channels[LEADING_DRAW_CHANNELS + i] = dst_tmp[i] channels[LEADING_DRAW_CHANNELS + i] = dst_tmp[i]
end end
table.clear(dst_tmp) -- [Luau] don't keep channel references alive in the scratch
end end
end end
@@ -3152,9 +3207,9 @@ function ImGui.TableDrawBorders(table_)
-- Draw inner border and resizing feedback -- Draw inner border and resizing feedback
local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent)
local border_size = TABLE_BORDER_SIZE local border_size = TABLE_BORDER_SIZE
local draw_y1 = ImMax(table_.InnerRect.Min.y, ((table_.FreezeRowsCount >= 1) and table_.InnerRect.Min.y or table_.WorkRect.Min.y) + table_.AngledHeadersHeight) + ((band(table_.Flags, ImGuiTableFlags.BordersOuterH) ~= 0) and border_size or 0.0) local draw_y1 = mathMax(table_.InnerRect.Min.y, ((table_.FreezeRowsCount >= 1) and table_.InnerRect.Min.y or table_.WorkRect.Min.y) + table_.AngledHeadersHeight) + ((band(table_.Flags, ImGuiTableFlags.BordersOuterH) ~= 0) and border_size or 0.0)
local draw_y2_body = table_.InnerRect.Max.y local draw_y2_body = table_.InnerRect.Max.y
local draw_y2_head = table_.IsUsingHeaders and ImMin(table_.InnerRect.Max.y, ((table_.FreezeRowsCount >= 1) and table_.InnerRect.Min.y or table_.WorkRect.Min.y) + table_instance.LastTopHeadersRowHeight) or draw_y1 local draw_y2_head = table_.IsUsingHeaders and mathMin(table_.InnerRect.Max.y, ((table_.FreezeRowsCount >= 1) and table_.InnerRect.Min.y or table_.WorkRect.Min.y) + table_instance.LastTopHeadersRowHeight) or draw_y1
if band(table_.Flags, ImGuiTableFlags.BordersInnerV) ~= 0 then if band(table_.Flags, ImGuiTableFlags.BordersInnerV) ~= 0 then
for order_n = 0, table_.ColumnsCount - 1 do for order_n = 0, table_.ColumnsCount - 1 do
repeat repeat
@@ -3310,7 +3365,7 @@ function ImGui.TableSetColumnSortDirection(column_n, sort_direction, append_to_s
local sort_order_max = 0 local sort_order_max = 0
if append_to_sort_specs then if append_to_sort_specs then
for other_column_n = 0, table_.ColumnsCount - 1 do for other_column_n = 0, table_.ColumnsCount - 1 do
sort_order_max = ImMax(sort_order_max, table_.Columns[other_column_n].SortOrder) sort_order_max = mathMax(sort_order_max, table_.Columns[other_column_n].SortOrder)
end end
end end
@@ -3468,7 +3523,7 @@ function ImGui.TableGetHeaderRowHeight()
for column_n = 0, table_.ColumnsCount - 1 do for column_n = 0, table_.ColumnsCount - 1 do
if BitTest(table_.EnabledMaskByIndex, column_n) then if BitTest(table_.EnabledMaskByIndex, column_n) then
if band(table_.Columns[column_n].Flags, ImGuiTableColumnFlags.NoHeaderLabel) == 0 then if band(table_.Columns[column_n].Flags, ImGuiTableColumnFlags.NoHeaderLabel) == 0 then
row_height = ImMax(row_height, ImGui.CalcTextSize(ImGui.TableGetColumnName(table_, column_n)).y) row_height = mathMax(row_height, ImGui.CalcTextSize(ImGui.TableGetColumnName(table_, column_n)).y)
end end
end end
end end
@@ -3482,7 +3537,7 @@ function ImGui.TableGetHeaderAngledMaxLabelWidth()
for column_n = 0, table_.ColumnsCount - 1 do for column_n = 0, table_.ColumnsCount - 1 do
if BitTest(table_.EnabledMaskByIndex, column_n) then if BitTest(table_.EnabledMaskByIndex, column_n) then
if band(table_.Columns[column_n].Flags, ImGuiTableColumnFlags.AngledHeader) ~= 0 then if band(table_.Columns[column_n].Flags, ImGuiTableColumnFlags.AngledHeader) ~= 0 then
width = ImMax(width, ImGui.CalcTextSize(ImGui.TableGetColumnName(table_, column_n), nil, true).x) width = mathMax(width, ImGui.CalcTextSize(ImGui.TableGetColumnName(table_, column_n), nil, true).x)
end end
end end
end end
@@ -3561,8 +3616,11 @@ function ImGui.TableHeader(label)
-- If we already got a row height, there's use that. -- If we already got a row height, there's use that.
-- FIXME-TABLE: Padding problem if the correct outer-padding CellBgRect strays off our ClipRect? -- FIXME-TABLE: Padding problem if the correct outer-padding CellBgRect strays off our ClipRect?
local cell_r = ImGui.TableGetCellBgRect(table_, column_n) -- [Luau] scalar version of: cell_r = TableGetCellBgRect(table_, column_n) (Min.x/Min.y/Max.x/Max.y -> cell_x1/cell_y1/cell_x2/cell_y2)
local label_height = ImMax(label_size.y, table_.RowMinHeight - table_.RowCellPaddingY * 2.0) local cell_x1 = mathMax(column.MinX, table_.WorkRect.Min.x)
local cell_x2 = mathMin(column.MaxX, table_.WorkRect.Max.x)
local cell_y1, cell_y2 = table_.RowPosY1, table_.RowPosY2
local label_height = mathMax(label_size.y, table_.RowMinHeight - table_.RowCellPaddingY * 2.0)
-- Calculate ideal size for sort order arrow -- Calculate ideal size for sort order arrow
local w_arrow = 0.0 local w_arrow = 0.0
@@ -3583,12 +3641,12 @@ function ImGui.TableHeader(label)
-- We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considered for merging. -- We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considered for merging.
local max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow local max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow
column.ContentMaxXHeadersUsed = ImMax(column.ContentMaxXHeadersUsed, sort_arrow and cell_r.Max.x or ImMin(max_pos_x, cell_r.Max.x)) column.ContentMaxXHeadersUsed = mathMax(column.ContentMaxXHeadersUsed, sort_arrow and cell_x2 or mathMin(max_pos_x, cell_x2))
column.ContentMaxXHeadersIdeal = ImMax(column.ContentMaxXHeadersIdeal, max_pos_x) column.ContentMaxXHeadersIdeal = mathMax(column.ContentMaxXHeadersIdeal, max_pos_x)
-- Keep header highlighted when context menu is open. -- Keep header highlighted when context menu is open.
local id = window:GetID(label) local id = window:GetID(label)
local bb = ImRect(cell_r.Min.x, cell_r.Min.y, cell_r.Max.x, ImMax(cell_r.Max.y, cell_r.Min.y + label_height + g.Style.CellPadding.y * 2.0)) local bb = ImRect(cell_x1, cell_y1, cell_x2, mathMax(cell_y2, cell_y1 + label_height + g.Style.CellPadding.y * 2.0))
ImGui.ItemSize(ImVec2(0.0, label_height)) -- Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal ImGui.ItemSize(ImVec2(0.0, label_height)) -- Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal
if not ImGui.ItemAdd(bb, id) then if not ImGui.ItemAdd(bb, id) then
return return
@@ -3626,13 +3684,13 @@ function ImGui.TableHeader(label)
-- ... 2 3 4 ... 2 3 4 (Display order) -- ... 2 3 4 ... 2 3 4 (Display order)
-- - The other constraints are enforced by TableQueueSetColumnDisplayOrder() which might early out. -- - The other constraints are enforced by TableQueueSetColumnDisplayOrder() which might early out.
table_.InstanceInteracted = table_.InstanceCurrent table_.InstanceInteracted = table_.InstanceCurrent
if g.IO.MouseDelta.x < 0.0 and g.IO.MousePos.x < cell_r.Min.x then if g.IO.MouseDelta.x < 0.0 and g.IO.MousePos.x < cell_x1 then
local prev_column = (column.PrevEnabledColumn ~= -1) and table_.Columns[column.PrevEnabledColumn] or nil local prev_column = (column.PrevEnabledColumn ~= -1) and table_.Columns[column.PrevEnabledColumn] or nil
if prev_column then if prev_column then
ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, prev_column.DisplayOrder) ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, prev_column.DisplayOrder)
end end
end end
if g.IO.MouseDelta.x > 0.0 and g.IO.MousePos.x > cell_r.Max.x then if g.IO.MouseDelta.x > 0.0 and g.IO.MousePos.x > cell_x2 then
local next_column = (column.NextEnabledColumn ~= -1) and table_.Columns[column.NextEnabledColumn] or nil local next_column = (column.NextEnabledColumn ~= -1) and table_.Columns[column.NextEnabledColumn] or nil
if next_column then if next_column then
ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, next_column.DisplayOrder) ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, next_column.DisplayOrder)
@@ -3643,10 +3701,10 @@ function ImGui.TableHeader(label)
-- Sort order arrow -- Sort order arrow
-- - Only clip label for a visible arrow. Auto-fit still accounts for a possible arrow, but when -- - Only clip label for a visible arrow. Auto-fit still accounts for a possible arrow, but when
-- manually sized smaller we don't clip the label for the sake of an arrow that isn't displayed. -- manually sized smaller we don't clip the label for the sake of an arrow that isn't displayed.
local ellipsis_max = ImMax(cell_r.Max.x - (sort_arrow and (w_arrow + w_sort_text) or 0.0), label_pos.x) local ellipsis_max = mathMax(cell_x2 - (sort_arrow and (w_arrow + w_sort_text) or 0.0), label_pos.x)
if band(table_.Flags, ImGuiTableFlags.Sortable) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoSort) == 0 then if band(table_.Flags, ImGuiTableFlags.Sortable) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoSort) == 0 then
if column.SortOrder ~= -1 then if column.SortOrder ~= -1 then
local x = ImMax(cell_r.Min.x, cell_r.Max.x - w_arrow - w_sort_text) local x = mathMax(cell_x1, cell_x2 - w_arrow - w_sort_text)
local y = label_pos.y local y = label_pos.y
if column.SortOrder > 0 then if column.SortOrder > 0 then
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.Text, 0.70)) ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.Text, 0.70))
@@ -4173,6 +4231,7 @@ function ImGui.TableSaveSettings(table_)
table_.SettingsOffset = SettingsTablesOffsetFromPtr(settings) table_.SettingsOffset = SettingsTablesOffsetFromPtr(settings)
end end
settings.ColumnsCount = table_.ColumnsCount settings.ColumnsCount = table_.ColumnsCount
settings.LastUsedDate = GImGui.SessionDate:Unpack()
-- Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings -- Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings
IM_ASSERT(settings.ID == table_.ID) IM_ASSERT(settings.ID == table_.ID)
@@ -4241,6 +4300,7 @@ function ImGui.TableLoadSettings(table_)
table_.SettingsLoadedFlags = settings.SaveFlags table_.SettingsLoadedFlags = settings.SaveFlags
table_.RefScale = settings.RefScale table_.RefScale = settings.RefScale
settings.LastUsedDate = GImGui.SessionDate:Unpack()
-- TableUpdateLayout() will then call TableLoadSettingsForColumns() to apply the data. -- TableUpdateLayout() will then call TableLoadSettingsForColumns() to apply the data.
end end
@@ -4330,6 +4390,17 @@ function ImGui.TableLoadSettingsForColumn(column, column_settings, load_flags)
column.SortDirection = column_settings.SortDirection column.SortDirection = column_settings.SortDirection
end end
-- [Luau] boolean "less than" for table.sort; takes (table, lhs column idx, rhs column idx) instead of ImGuiTableFixDisplayOrderColumnData
local function TableFixDisplayOrderComparer(table_, lhs_idx, rhs_idx)
-- Assume ImGuiTableColumnIdx == signed short, to turn -1 into a large value so that it always sort after.
local lhs_order, rhs_order = table_.Columns[lhs_idx].DisplayOrder, table_.Columns[rhs_idx].DisplayOrder
if lhs_order < 0 then lhs_order = lhs_order + 0x10000 end
if rhs_order < 0 then rhs_order = rhs_order + 0x10000 end
local order_delta = lhs_order - rhs_order
if order_delta ~= 0 then return order_delta < 0 end
return lhs_idx < rhs_idx
end
-- Fix invalid display order data: compact values (0,1,3 -> 0,1,2); preserve relative order (0,3,1 -> 0,2,1); deduplicate (0,4,1,1 -> 0,3,1,2) -- Fix invalid display order data: compact values (0,1,3 -> 0,1,2); preserve relative order (0,3,1 -> 0,2,1); deduplicate (0,4,1,1 -> 0,3,1,2)
function ImGui.TableFixDisplayOrder(table_) function ImGui.TableFixDisplayOrder(table_)
if band(table_.Flags, ImGuiTableFlags.Reorderable) == 0 then if band(table_.Flags, ImGuiTableFlags.Reorderable) == 0 then
@@ -4344,17 +4415,7 @@ function ImGui.TableFixDisplayOrder(table_)
fdo_columns[n + 1] = n fdo_columns[n + 1] = n
end end
-- Sort by DisplayOrder and then Index -- Sort by DisplayOrder and then Index
-- Assume ImGuiTableColumnIdx == signed short, to turn -1 into a large value so that it always sort after. table.sort(fdo_columns, function(lhs_idx, rhs_idx) return TableFixDisplayOrderComparer(table_, lhs_idx, rhs_idx) end)
local function key(idx)
local o = table_.Columns[idx].DisplayOrder
if o < 0 then o = o + 0x10000 end
return o
end
table.sort(fdo_columns, function(lhs_idx, rhs_idx)
local a, b = key(lhs_idx), key(rhs_idx)
if a ~= b then return a < b end
return lhs_idx < rhs_idx
end)
for n = 0, table_.ColumnsCount - 1 do for n = 0, table_.ColumnsCount - 1 do
table_.Columns[fdo_columns[n + 1]].DisplayOrder = n table_.Columns[fdo_columns[n + 1]].DisplayOrder = n
end end
@@ -4363,6 +4424,27 @@ function ImGui.TableFixDisplayOrder(table_)
end end
end end
local function TableSettingsHandler_Cleanup(ctx, handler, args)
local g = ctx
for i = 0, g.Tables:GetMapSize() - 1 do
local table_ = g.Tables:GetByIndex(i)
if table_ then
table_.SettingsOffset = -1
end
end
-- [Luau] settings.LastUsedDate is stored unpacked (YYYYMMDD int, 0 = none); g.SessionDate is an ImGuiPackedDate
for _, settings in g.SettingsTables:iter() do
local is_valid = ImGuiPackedDate(settings.LastUsedDate):IsValid()
if args._DiscardOlderThanDate ~= 0 and settings.LastUsedDate < args._DiscardOlderThanDate then
settings.ID = 0
elseif args.DiscardWhenMissingDate and not is_valid then
settings.ID = 0
elseif args.SetCurrentSessionDateToAll or (args.SetCurrentSessionDateWhenMissingDate and not is_valid) then
settings.LastUsedDate = g.SessionDate:Unpack()
end
end
end
local function ForEachTable(g, fn) local function ForEachTable(g, fn)
for i = 0, g.Tables:GetMapSize() - 1 do for i = 0, g.Tables:GetMapSize() - 1 do
local t = g.Tables:GetByIndex(i) local t = g.Tables:GetByIndex(i)
@@ -4406,7 +4488,7 @@ local function TableSettingsHandler_ReadLine(ctx, handler, settings, line)
local f = string.match(line, "^RefScale=([%-%d%.eE+]+)") local f = string.match(line, "^RefScale=([%-%d%.eE+]+)")
if f then settings.RefScale = tonumber(f); return end if f then settings.RefScale = tonumber(f); return end
local n = string.match(line, "^LastUsed=(%d+)") local n = string.match(line, "^LastUsed=(%d+)")
if n then settings.LastUsedDate = tonumber(n); return end if n then settings.LastUsedDate = ImGuiPackedDate(tonumber(n)):Unpack(); return end
local column_n, rest = string.match(line, "^Column (%-?%d+)(.*)$") local column_n, rest = string.match(line, "^Column (%-?%d+)(.*)$")
if column_n then if column_n then
@@ -4488,6 +4570,7 @@ function ImGui.TableSettingsAddSettingsHandler()
ini_handler.ReadLineFn = TableSettingsHandler_ReadLine ini_handler.ReadLineFn = TableSettingsHandler_ReadLine
ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll
ini_handler.WriteAllFn = TableSettingsHandler_WriteAll ini_handler.WriteAllFn = TableSettingsHandler_WriteAll
ini_handler.CleanupFn = TableSettingsHandler_Cleanup
ImGui.AddSettingsHandler(ini_handler) ImGui.AddSettingsHandler(ini_handler)
end end
@@ -4551,6 +4634,20 @@ function ImGui.TableGcCompactSettings()
ForEachTable(g, function(t) t.SettingsOffset = -1 end) ForEachTable(g, function(t) t.SettingsOffset = -1 end)
end end
-------------------------------------------------------------------------------
-- [SECTION] Tables: Debugging
-------------------------------------------------------------------------------
-- [Luau] global (upstream: static) so ImGui.DebugNodeTable() in imgui.lua can use it
function DebugNodeTableGetSizingPolicyDesc(sizing_policy)
sizing_policy = band(sizing_policy, ImGuiTableFlags.SizingMask_)
if sizing_policy == ImGuiTableFlags.SizingFixedFit then return "FixedFit" end
if sizing_policy == ImGuiTableFlags.SizingFixedSame then return "FixedSame" end
if sizing_policy == ImGuiTableFlags.SizingStretchProp then return "StretchProp" end
if sizing_policy == ImGuiTableFlags.SizingStretchSame then return "StretchSame" end
return "N/A"
end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- [SECTION] Columns, BeginColumns, EndColumns, etc. -- [SECTION] Columns, BeginColumns, EndColumns, etc.
-- (This is a legacy API, prefer using BeginTable/EndTable!) -- (This is a legacy API, prefer using BeginTable/EndTable!)
+1919 -296
View File
File diff suppressed because it is too large Load Diff
+31 -14
View File
@@ -3,7 +3,8 @@
-- Supports: -- Supports:
-- - %x, %X, %nx, %nX (where n is a number) -- - %x, %X, %nx, %nX (where n is a number)
-- - %d, %nd (where n is a number) -- - %d, %nd, %u, %nu (where n is a number)
-- - length modifiers h, hh, l, ll, I64 on integers: ll/I64 = 64-bit range (doubles: exact only within +-2^53)
-- - %f, %lf (very limited) -- - %f, %lf (very limited)
-- --
-- Returns items_matched instead of EOF on error -- Returns items_matched instead of EOF on error
@@ -17,6 +18,9 @@ local CHAR_0, CHAR_9 = _byte'0', _byte'9'
local CHAR_a, CHAR_A = _byte'a', _byte'A' local CHAR_a, CHAR_A = _byte'a', _byte'A'
local CHAR_d = _byte'd' local CHAR_d = _byte'd'
local CHAR_l = _byte'l' local CHAR_l = _byte'l'
local CHAR_h = _byte'h'
local CHAR_u = _byte'u'
local CHAR_I = _byte'I'
local CHAR_f, CHAR_F = _byte'f', _byte'F' local CHAR_f, CHAR_F = _byte'f', _byte'F'
local CHAR_x, CHAR_X = _byte'x', _byte'X' local CHAR_x, CHAR_X = _byte'x', _byte'X'
@@ -37,10 +41,15 @@ local function hex_digit_to_int(c)
end end
-- we force this behavior by default -- we force this behavior by default
local function clamp_if_overflow(spec, val) local function clamp_if_overflow(spec, val, is_64)
if spec == CHAR_d or spec == CHAR_x or spec == CHAR_X then if spec == CHAR_d or spec == CHAR_x or spec == CHAR_X then
if val > INT_MAX then return INT_MAX end local max = is_64 and 9223372036854775807 or INT_MAX
if val < INT_MIN then return INT_MIN end local min = is_64 and -9223372036854775808 or INT_MIN
if val > max then return max end
if val < min then return min end
elseif spec == CHAR_u then
local max = is_64 and 18446744073709551615 or UINT_MAX
if val > max then return max end
end end
return val return val
@@ -104,22 +113,27 @@ local function sscanf(buffer, buffer_begin, format, result)
width = math.huge width = math.huge
end end
-- length modifiers
local n_l = 0
while _byte(format, f, f) == CHAR_l or _byte(format, f, f) == CHAR_h do
if _byte(format, f, f) == CHAR_l then n_l = n_l + 1 end
f = f + 1
end
if _byte(format, f, f) == CHAR_I and string.sub(format, f, f + 2) == "I64" then
n_l = 2
f = f + 3
end
local is_64 = n_l >= 2
local spec = _byte(format, f, f) local spec = _byte(format, f, f)
local base local base
local is_float = false local is_float = false
if spec == CHAR_x or spec == CHAR_X then if spec == CHAR_x or spec == CHAR_X then
base = 16 base = 16
elseif spec == CHAR_d then elseif spec == CHAR_d or spec == CHAR_u then
base = 10 base = 10
elseif spec == CHAR_f or spec == CHAR_F then elseif spec == CHAR_f or spec == CHAR_F then
is_float = true is_float = true -- TODO: distinguish %lf from %f?
elseif spec == CHAR_l then
f = f + 1
if _byte(format, f, f) == CHAR_f then
is_float = true -- TODO: distinguish from float?
else
return items_matched
end
else else
return items_matched return items_matched
end end
@@ -197,11 +211,14 @@ local function sscanf(buffer, buffer_begin, format, result)
val = val * base + sign * hex_digit_to_int(c) val = val * base + sign * hex_digit_to_int(c)
local old_val = val local old_val = val
val = clamp_if_overflow(spec, val) val = clamp_if_overflow(spec, val, is_64)
if old_val ~= val then if old_val ~= val then
break break
end end
end end
if spec == CHAR_u and val < 0 then
val = val + (is_64 and 18446744073709551616 or 4294967296) -- unsigned wrap-around like strtoul()
end
end end
result[assigned + 1] = val result[assigned + 1] = val
+220 -12
View File
@@ -7,15 +7,34 @@
# Each source file becomes a function in __FILES; IM_INCLUDE(name) runs it once and caches the result. # Each source file becomes a function in __FILES; IM_INCLUDE(name) runs it once and caches the result.
# The module returns a table with every global the library defines (ImGui, ImVec2, ImGuiConfigFlags, ImGui_ImplRoblox, ...). # The module returns a table with every global the library defines (ImGui, ImVec2, ImGuiConfigFlags, ImGui_ImplRoblox, ...).
# Roblox target also writes ImGuiTyped.luau (types + require(script.Impl), see tools/gen_types.py). # Roblox target also writes ImGuiTyped.luau (types + require(script.Impl), see tools/gen_types.py).
import os, re #
# Build-time optimizations (token rewrites with minify.py's lexer: strings/comments are never touched, every source line
# stays on its line so linemap.json still maps errors). IMGUI_BUNDLE_NOOPT=1 turns all off, IMGUI_BUNDLE_NO_<X>=1 one:
# A hot library globals (most referenced names assigned at a file's top level) become bundle-level locals declared
# before __FILES: every file's assignment/reference then hits an upvalue instead of GETGLOBAL. Fewer names for
# LuaJIT (60 upvalues per function). Library globals can't be patched from outside (use the returned table).
# B enum folding: the bundle is loaded once headless (Luau CLI) to dump enum-like tables (all values numbers, never
# written inside a function) and numeric constants assigned once at top level; reads `ImGuiCol.Text` / `FLT_MAX`
# inside function bodies become `0 --[[ImGuiCol.Text]]`. Load-time (top-level) code is left alone.
# C `--@hoist` functions in the sources (pure numeric helpers: ImMin, ImMax, ImClamp, ...) are moved to the bundle top
# as `local function`s (their source lines become blank) so Luau -O2 can inline them everywhere.
# E statement-form `IM_ASSERT(...)` becomes `assert(...)` (fast builtin); expression uses are left alone.
import os, re, sys, json, shutil, subprocess, tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
sys.path.insert(0, HERE)
from minify import TOKEN
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
import sys
NO_DEMO = "--no-demo" in sys.argv NO_DEMO = "--no-demo" in sys.argv
LOVE = False LOVE = False
if "--target" in sys.argv: if "--target" in sys.argv:
i = sys.argv.index("--target"); LOVE = sys.argv[i + 1] == "love"; del sys.argv[i:i + 2] i = sys.argv.index("--target"); LOVE = sys.argv[i + 1] == "love"; del sys.argv[i:i + 2]
sys.argv = [a for a in sys.argv if a != "--no-demo"] sys.argv = [a for a in sys.argv if a != "--no-demo"]
OPT = {x: os.environ.get("IMGUI_BUNDLE_NOOPT") != "1" and os.environ.get("IMGUI_BUNDLE_NO_" + x) != "1" for x in "ABCE"}
# A limits: Luau 200 locals in the main chunk (+ helpers, __FILES, ...), 200 upvalues per function; LuaJIT 60 upvalues
# per function (margin left for nested functions that also capture file locals)
MAX_LOCALS, MAX_UPVALUES = 160, (45 if LOVE else 150)
BACKEND = "imgui_impl_love.lua" if LOVE else "imgui_impl_roblox.lua" BACKEND = "imgui_impl_love.lua" if LOVE else "imgui_impl_roblox.lua"
SKIP = {"imgui_impl_roblox.lua", "imgui_impl_roblox_fonts.lua"} if LOVE else {"imgui_impl_love.lua"} SKIP = {"imgui_impl_roblox.lua", "imgui_impl_roblox_fonts.lua"} if LOVE else {"imgui_impl_love.lua"}
FILES = {} FILES = {}
@@ -26,6 +45,7 @@ for d in (ROOT, os.path.join(ROOT, "backends"), os.path.join(ROOT, "fonts")):
if LOVE: src = re.sub(r"^@native$", "", src, flags=re.M) # keep line numbers if LOVE: src = re.sub(r"^@native$", "", src, flags=re.M) # keep line numbers
FILES[f] = src FILES[f] = src
ORIG = dict(FILES) # for gen_types
ENTRY = ["imgui.lua", BACKEND] + ([] if NO_DEMO else ["imgui_demo.lua"]) # run order ENTRY = ["imgui.lua", BACKEND] + ([] if NO_DEMO else ["imgui_demo.lua"]) # run order
names = set() names = set()
@@ -33,16 +53,207 @@ for src in FILES.values():
names.update(re.findall(r"^function ([A-Za-z_]\w*)\s*\(", src, re.M)) names.update(re.findall(r"^function ([A-Za-z_]\w*)\s*\(", src, re.M))
names.update(re.findall(r"^([A-Za-z_]\w*)\s*=[^=]", src, re.M)) names.update(re.findall(r"^([A-Za-z_]\w*)\s*=[^=]", src, re.M))
out = ["--!optimize 2", "-- GENERATED by tools/bundle.py from *.lua, backends/, fonts/ - do not edit", ""] # ---- lexer helpers ----
if LOVE: KW = set("and break do else elseif end false for function goto if in local nil not or repeat return then true until while".split())
out = ["-- GENERATED by tools/bundle.py --target love - do not edit", re.search(r"--@compat-begin\n(.*?)--@compat-end", open(os.path.join(ROOT, "backends", "imgui_impl_love.lua")).read(), re.S).group(1), ""]
out += [ def lex(src):
"local __FILES, __LOADED = {}, {}", """[(kind, text, start)] without whitespace/comments; kinds: name num str op."""
toks, pos = [], 0
while pos < len(src):
m = TOKEN.match(src, pos)
k = m.lastgroup
if k in ("ceq", "seq"): k = "lcom" if m.group(0).startswith("--") else "lstr"
if k not in ("ws", "lcom", "com"): toks.append(("str" if k == "lstr" else k, m.group(0), pos))
pos = m.end()
return toks
def fdepths(toks):
"""function nesting depth of every token (0 = runs at load time)"""
st, fd, out = [], 0, []
for k, t, _ in toks:
out.append(fd)
if k == "name":
if t == "function": st.append(1); fd += 1
elif t in ("if", "do", "repeat"): st.append(0)
elif t in ("end", "until"): fd -= st.pop()
assert not st, "unbalanced blocks"
return out
def declared(toks):
"""names declared as locals / parameters / loop variables anywhere"""
out, n = set(), len(toks)
for i, (k, t, _) in enumerate(toks):
j = None
if t == "local": j = i + 2 if toks[i + 1][1] == "function" else i + 1
elif t == "for": j = i + 1
elif t == "function":
j = i + 1
while toks[j][1] != "(": j += 1
j += 1
while j is not None and j < n and toks[j][0] == "name" and toks[j][1] not in KW:
out.add(toks[j][1])
j += 1
if j < n and toks[j][1] == ",": j += 1
else: break
return out
def apply(src, edits):
for s, e, r in sorted(edits, reverse=True): src = src[:s] + r + src[e:]
return src
def isref(toks, i):
"""toks[i] is a variable read/write (not a field name)"""
return toks[i][0] == "name" and (i == 0 or toks[i - 1][1] not in (".", ":"))
def nexttext(toks, i): return toks[i + 1][1] if i + 1 < len(toks) else ""
# ---- C: --@hoist helpers ----
hoisted, hoisted_names = [], []
if OPT["C"]:
for f, src in FILES.items():
while "--@hoist\n" in src:
s = src.index("--@hoist\n") + len("--@hoist\n")
toks = lex(src[s:])
depth, i = 0, 0
while True: # first `function` ... matching end
t = toks[i][1]
if t == "function" or t in ("if", "do", "repeat"): depth += 1
elif t in ("end", "until"):
depth -= 1
if depth == 0: break
i += 1
e = s + toks[i][2] + 3
text = src[s:e]
m = re.search(r"^(local )?function ([A-Za-z_]\w*)", text, re.M)
hoisted_names.append(m.group(2))
hoisted.append(text[:m.start()] + "local function " + text[m.start() + len(m.group(0)) - len(m.group(2)):])
src = src[:s - len("--@hoist\n")] + "\n" + re.sub(r"[^\n]", "", text) + src[e:]
FILES[f] = src
HOIST_DEPS = "local type, mathMin, mathMax, mathFloor, mathCeil, bitOr, bitLShift = type, math.min, math.max, math.floor, math.ceil, bit32.bor, bit32.lshift"
# ---- E: statement IM_ASSERT -> assert ----
STMT_PREV_KW = {"end", "true", "false", "nil", "break", "then", "do", "else", "repeat"}
def rewrite_assert(src):
toks, edits = lex(src), []
for i, (k, t, p) in enumerate(toks):
if t == "IM_ASSERT" and isref(toks, i) and nexttext(toks, i) == "(":
pk, pt = (toks[i - 1][0], toks[i - 1][1]) if i else ("op", ";")
if (pk == "name" and (pt not in KW or pt in STMT_PREV_KW)) or pk in ("num", "str") or pt in (")", "]", "}", ";", "..."):
edits.append((p, p + len(t), "assert"))
return apply(src, edits)
if OPT["E"]:
FILES = {f: rewrite_assert(s) for f, s in FILES.items()}
hoisted = [rewrite_assert(s) for s in hoisted]
# ---- B: enum / constant folding ----
LUAU = shutil.which("luau") or "/nix/store/2m2ja613nndihc8qm10f2rhq6znp1nh4-luau-0.734/bin/luau"
def header():
return ["local __FILES, __LOADED = {}, {}",
"function IM_INCLUDE(name)", "function IM_INCLUDE(name)",
" local r = __LOADED[name]", " local r = __LOADED[name]",
" if r == nil then r = __FILES[name](); if r == nil then r = true end; __LOADED[name] = r end", " if r == nil then r = __FILES[name](); if r == nil then r = true end; __LOADED[name] = r end",
" return r", " return r",
"end", ""] "end", ""]
def numtext(s):
v = float(s)
if v != v or v in (float("inf"), float("-inf")) or (v == 0 and s.startswith("-")): return None
r = str(int(v)) if v.is_integer() and abs(v) < 2 ** 53 else repr(v)
return "(%s)" % r if v < 0 else r
def dump_values():
"""{name: number} and {name: {key: number}} of the loaded library (imgui.lua and what it includes)"""
src = ["--!optimize 1"] + ([HOIST_DEPS] + hoisted if hoisted else []) + header()
for name, s in sorted(FILES.items()): src += ["__FILES[%r] = function()" % name, s, "end"]
src += ['IM_INCLUDE("imgui.lua")',
'local function D(n, v) if type(v) == "number" then print("C", n, string.format("%.17g", v)) elseif type(v) == "table" then',
' for k, x in pairs(v) do print("T", n, type(k) == "string" and k or "?", type(x) == "number" and string.format("%.17g", x) or "?") end end end']
src += ["D(%r, %s)" % (n, n) for n in sorted(names)]
with tempfile.NamedTemporaryFile("w", suffix=".luau", delete=False) as fp: fp.write("\n".join(src))
try:
p = subprocess.run([LUAU, fp.name], capture_output=True, text=True, timeout=60)
finally:
os.unlink(fp.name)
if p.returncode: raise RuntimeError("enum dump failed:\n" + p.stdout[-2000:] + p.stderr[-2000:])
consts, tables = {}, {}
for line in p.stdout.splitlines():
a = line.split("\t")
if a[0] == "C": consts[a[1]] = a[2]
elif a[0] == "T": tables.setdefault(a[1], {})[a[2]] = a[3]
tables = {n: t for n, t in tables.items() if "?" not in t and "?" not in t.values()}
return consts, tables
def fold(files):
consts, tables = dump_values()
lexed = {f: lex(s) for f, s in files.items()}
depth = {f: fdepths(t) for f, t in lexed.items()}
# tables written inside a function, constants assigned more than once or not at top level: not foldable
assigns = {}
for f, toks in lexed.items():
for i, (k, t, _) in enumerate(toks):
if not isref(toks, i): continue
if t in tables and depth[f][i] > 0:
j = i + 1
if nexttext(toks, i) == ".": j = i + 3
elif nexttext(toks, i) == "[":
b = 0
while True:
b += {"[": 1, "]": -1}.get(toks[j][1], 0); j += 1
if b == 0: break
if j < len(toks) and toks[j][1] == "=": tables.pop(t, None)
if t in consts and nexttext(toks, i) == "=":
assigns[t] = assigns.get(t, 0) + (1 if depth[f][i] == 0 else 2)
consts = {n: v for n, v in consts.items() if assigns.get(n) == 1 and numtext(v)}
out = {}
for f, toks in lexed.items():
shadow = declared(toks)
edits = []
for i, (k, t, p) in enumerate(toks):
if depth[f][i] == 0 or not isref(toks, i) or t in shadow: continue
if t in tables and nexttext(toks, i) == "." and i + 2 < len(toks):
key = toks[i + 2][1]
if key in tables[t] and numtext(tables[t][key]) and nexttext(toks, i + 2) not in ("=", ".", ":", "[", "(", "{") and toks[i + 3][0] != "str":
edits.append((p, toks[i + 2][2] + len(key), "%s --[[%s.%s]]" % (numtext(tables[t][key]), t, key)))
elif t in consts and nexttext(toks, i) not in ("=", ".", ":", "[", "(", "{") and toks[i + 1][0] != "str":
edits.append((p, p + len(t), "%s --[[%s]]" % (numtext(consts[t]), t)))
out[f] = apply(files[f], edits)
return out
if OPT["B"]:
try:
allf = dict(FILES); allf.update({"@hoist%d" % i: "local function __h()\n" + s + "\nend" for i, s in enumerate(hoisted)})
allf = fold(allf)
hoisted = [allf.pop("@hoist%d" % i)[len("local function __h()\n"):-len("\nend")] for i in range(len(hoisted))]
FILES = allf
except (OSError, RuntimeError) as e:
if os.environ.get("IMGUI_BUNDLE_STRICT"): raise # CI: a release must not silently lose the folding
print("bundle.py: enum folding skipped (%s)" % str(e).strip()[:300], file=sys.stderr)
# ---- A: hot globals -> bundle locals ----
hot = []
if OPT["A"]:
count, used = {}, []
for src in FILES.values():
toks = lex(src)
used.append({t for i, (k, t, _) in enumerate(toks) if isref(toks, i)})
for i, (k, t, _) in enumerate(toks):
if t in names and isref(toks, i): count[t] = count.get(t, 0) + 1
# greedy by reference count; each file function's upvalues (hoisted names it uses) stay <= MAX_UPVALUES
taken = [len(u & set(hoisted_names + re.findall(r"\w+", HOIST_DEPS.split("=")[0]))) for u in used]
for n in sorted((n for n in count if n not in hoisted_names), key=lambda n: (-count[n], n)):
if len(hot) == MAX_LOCALS: break
if all(taken[i] < MAX_UPVALUES for i, u in enumerate(used) if n in u):
hot.append(n)
taken = [c + (n in u) for c, u in zip(taken, used)]
out = ["--!optimize 2", "-- GENERATED by tools/bundle.py from *.lua, backends/, fonts/ - do not edit", ""]
if LOVE:
out = ["-- GENERATED by tools/bundle.py --target love - do not edit", re.search(r"--@compat-begin\n(.*?)--@compat-end", open(os.path.join(ROOT, "backends", "imgui_impl_love.lua")).read(), re.S).group(1), ""]
if hot: out.append("local " + ", ".join(hot))
if hoisted: out += [HOIST_DEPS] + hoisted
out += header()
linemap = [] # (first bundle line, file) for error -> source mapping (tools/test/run.py) linemap = [] # (first bundle line, file) for error -> source mapping (tools/test/run.py)
for name, src in sorted(FILES.items()): for name, src in sorted(FILES.items()):
out.append("__FILES[%r] = function()" % name) out.append("__FILES[%r] = function()" % name)
@@ -58,15 +269,12 @@ for n in sorted(names):
out.append(" %s = %s," % (n, n)) out.append(" %s = %s," % (n, n))
out.append("}") out.append("}")
import sys
OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(ROOT, "build") OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(ROOT, "build")
os.makedirs(OUT, exist_ok=True) os.makedirs(OUT, exist_ok=True)
NAME = "imgui_love.lua" if LOVE else "ImGui.luau" NAME = "imgui_love.lua" if LOVE else "ImGui.luau"
open(os.path.join(OUT, NAME), "w").write("\n".join(out) + "\n") open(os.path.join(OUT, NAME), "w").write("\n".join(out) + "\n")
import json
json.dump(linemap, open(os.path.join(OUT, NAME.rsplit(".", 1)[0] + ".linemap.json"), "w")) json.dump(linemap, open(os.path.join(OUT, NAME.rsplit(".", 1)[0] + ".linemap.json"), "w"))
if not LOVE: if not LOVE:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from gen_types import wrapper from gen_types import wrapper
open(os.path.join(OUT, "ImGuiTyped.luau"), "w").write(wrapper(FILES)) open(os.path.join(OUT, "ImGuiTyped.luau"), "w").write(wrapper(ORIG))
print("wrote build/%s (%d files, %d exports)" % (NAME, len(FILES), len(names))) print("wrote build/%s (%d files, %d exports)" % (NAME, len(FILES), len(names)))
+645
View File
@@ -0,0 +1,645 @@
#!/usr/bin/env python3
# Compares the port against upstream Dear ImGui (tools/test/upstream/*) and writes docs/PORT_STATUS.txt.
# python3 tools/compare_upstream.py # print summary, write docs/PORT_STATUS.txt
# Ported %: upstream functions (by qualified name, overloads merged) that have a port definition; enum values; struct fields;
# public/internal API declarations in imgui.h / imgui_internal.h (namespace ImGui).
# Similarity %: for each ported function, both bodies are normalized to a common token stream (comments/types/punctuation
# dropped, C++ operators and names mapped to Lua: -> :: . ! && || _Flag) and compared with difflib ratio.
# File figures are weighted by upstream body size. Matching is by name, so it's an estimate, not a proof.
# Excluded from the %: C++-only (operators, destructors), #if 0, IMGUI_DISABLE_OBSOLETE_FUNCTIONS, OS-specific (#ifdef _WIN32...),
# test engine. They're listed separately in the report.
import os, re, sys
from difflib import SequenceMatcher
from fnmatch import fnmatchcase
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from minify import TOKEN as LUA_TOKEN
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
UP = os.path.join(ROOT, "tools", "test", "upstream")
CPP_FILES = ["imgui.cpp", "imgui_widgets.cpp", "imgui_draw.cpp", "imgui_tables.cpp", "imgui_demo.cpp", "imgui.h", "imgui_internal.h"]
PORT_FILES = sorted(f for f in os.listdir(ROOT) if f.endswith(".lua"))
LUA_CLASS_ALIAS = {"IM_RECT": "ImRect", "IM_VECTOR": "ImVector", "IM_POOL": "ImPool", "IMGUI_VIEWPORT_P": "ImGuiViewportP",
"ImGuiTextBufferMT": "ImGuiTextBuffer", "IMGUI_TEXT_INDEX": "ImGuiTextIndex", "IM_PAYLOAD": "ImGuiPayload",
"DN": "ImGuiDockNode", "IM_VTXBUF": "ImVector"}
# ---------------------------------------------------------------------------------------------------------------------
# Intentional Lua replacements: [=] rows, not counted as missing. (glob over the upstream key "Class::name" / "Struct::field",
# reason, regex that must match the port sources - if the port's mechanism disappears, the entry falls back to [ ] / missing).
# Only applies when no same-named port definition / field exists.
# ---------------------------------------------------------------------------------------------------------------------
REPLACED = [
# functions
("ImGuiStorage::*", "Lua table keyed by ID (e.g. window.StateStorage = {})", r"StateStorage\s*=\s*\{\}"),
("ImGuiStoragePair::*", "Lua table keyed by ID", r"StateStorage\s*=\s*\{\}"),
("PairComparerByID", "table.sort over Lua table keys", r"table\.sort\("),
("ImStr*", "Lua string library (string.find/sub/lower, immutable strings)", r"string\.find\("),
("ImMemdup", "Lua strings/tables are values, no copy needed", r"string\.find\("),
("ImFormatString*", "string.format", r"ImFormatString\s*=\s*string\.format"),
("ImText*Utf8*", "strings stay UTF-8 bytes: ImStd.ImTextCharFromUtf8 decoder + utf8 library", r"function ImStd\.ImTextCharFromUtf8"),
("MallocWrapper", "GC; IM_ALLOC = ImGui.MemAlloc", r"IM_ALLOC\s*=\s*ImGui\.MemAlloc"),
("FreeWrapper", "GC; IM_ALLOC = ImGui.MemAlloc", r"IM_ALLOC\s*=\s*ImGui\.MemAlloc"),
("ImGui::GetAllocatorFunctions", "GC, no allocator to query", r"IM_ALLOC\s*=\s*ImGui\.MemAlloc"),
("ImFileWrite", "no filesystem on Roblox: SaveIniSettingsToDisk is a no-op, use SaveIniSettingsToMemory", r"Roblox: no disk"),
("ImSwap", "Lua multiple assignment a, b = b, a", r"\b([\w.]+), ([\w.]+) = \2, \1\b"),
("ImGui::DebugCheckVersionAndDataLayout", "C++ ABI / sizeof check", None),
("TableSettingsCalcChunkSize", "sizeof; table settings are a plain ImVector", r"SettingsTables\s*=\s*ImVector\(\)"),
("ImVector::size", "v.Size field", r"function IM_VECTOR:"),
("ImVector::capacity", "v.Capacity field", r"function IM_VECTOR:"),
("ImVector::front", "v.Data[1]", r"function IM_VECTOR:"),
("ImVector::begin", "v.Data / v:iter()", r"function IM_VECTOR:iter\("),
("ImVector::end", "v.Data / v:iter()", r"function IM_VECTOR:iter\("),
("ImVector::size_in_bytes", "sizeof", None),
("ImVector::max_size", "sizeof", None),
("ImVector::clear_destruct", "GC: clear_delete / clear", r"function IM_VECTOR:clear_delete\("),
("ImVector::find", "find_index (no element pointers)", r"function IM_VECTOR:find_index\("),
("ImSpan::*", "plain Lua tables (e.g. table.Columns = {})", r"Columns = \{\}, ColumnsSize = 0"),
("ImSpanAllocator::*", "plain Lua tables, no arena", r"Columns = \{\}, ColumnsSize = 0"),
("ImStableVector::*", "ImVector (Lua references are stable)", r"BakedPool\s*=\s*ImVector\(\)"),
("ImChunkStream::*", "plain ImVector of settings tables", r"SettingsTables\s*=\s*ImVector\(\)"),
("ImPool::GetAliveCount", "pool.AliveCount field", r"\.AliveCount\)"),
("ImPool::GetBufSize", "pool.BufSize field / GetMapSize()", r"function IM_POOL:GetMapSize\(\) return self\.BufSize"),
("ImPool::TryGetMapData", "GetByIndex (removed slots hold false)", r"function IM_POOL:GetByIndex\("),
("ImGuiTextBuffer::begin", "chunk-list string builder: c_str()", r"function ImGuiTextBufferMT:c_str\("),
("ImGuiTextBuffer::end", "chunk-list string builder: c_str()", r"function ImGuiTextBufferMT:c_str\("),
("ImGuiTextBuffer::resize", "chunk-list string builder, no byte buffer", r"function ImGuiTextBufferMT:c_str\("),
("ImGuiTextBuffer::reserve", "chunk-list string builder, no byte buffer", r"function ImGuiTextBufferMT:c_str\("),
("ImGuiTextBuffer::appendfv", "appendf(fmt, ...) (Lua varargs)", r"function ImGuiTextBufferMT:appendf\("),
("ImGuiTextIndex::append", "debug log keeps a Lua line list (g.DebugLogLines)", r"g\.DebugLogLines\s*="),
# struct fields (ImSpan*/ImStableVector/ImGuiStoragePair fields: covered by the globs above)
("ImGuiTextBuffer::EmptyString", "c_str() of an empty chunk list", r"function ImGuiTextBufferMT:c_str\("),
("ImGuiWindow::NameBufLen", "window.Name is an immutable Lua string", None),
("ImGuiTextFilterItem::Begin", "item.Text substring", r"Text = string\.sub\(buf, b, e - 1\)"),
("ImGuiTextFilterItem::Len", "item.Text substring", r"Text = string\.sub\(buf, b, e - 1\)"),
("ImDrawVert::uv", "vertices packed into VtxBuffer.Buf (VTX_STRIDE bytes per vertex)", r"VTX_STRIDE\s*=\s*\d+"),
("ImDrawVert::col", "vertices packed into VtxBuffer.Buf (VTX_STRIDE bytes per vertex)", r"VTX_STRIDE\s*=\s*\d+"),
("ImGuiContext::DebugLogIndex", "Lua line list g.DebugLogLines", r"g\.DebugLogLines\s*="),
("ImGuiContext::TempKeychordName", "GetKeyChordName returns a Lua string", r"\.\. \(\(key ~= ImGuiKey\.None"),
("ImGuiContext::DragDropPayloadBuf*", "payload.Data holds the Lua value", r"payload\.Data = data"),
("ImGuiTabBar::TabsNames", "names stored on the tab (tab.Name)", r"Tab names are stored on the tab itself"),
]
# ---------------------------------------------------------------------------------------------------------------------
# C++ side
# ---------------------------------------------------------------------------------------------------------------------
CPP_TOKEN = re.compile(r"""
(?P<nl>\n)
| (?P<ws>[ \t\r\f\v]+|\\\n)
| (?P<com>//[^\n]*|/\*.*?\*/)
| (?P<pp>\#(?:[^\n\\]|\\.|\\\n)*)
| (?P<str>(?:u8|u|U|L)?"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*')
| (?P<num>0[xX][0-9a-fA-F']+[uUlL]*|(?:\d[\d']*\.?\d*|\.\d+)(?:[eE][+-]?\d+)?[fFuUlL]*)
| (?P<name>[A-Za-z_]\w*)
| (?P<op>->\*?|::|<<=|>>=|<<|>>|&&|\|\||==|!=|<=|>=|\+\+|--|[-+*/%&|^]=|\.\.\.|.)
""", re.S | re.X)
EXCLUDE_PP = [("if0", re.compile(r"^\s*#\s*if\s+0\b")),
("obsolete", re.compile(r"#\s*ifndef\s+IMGUI_DISABLE_OBSOLETE_FUNCTIONS|#\s*if\s+!defined\(IMGUI_DISABLE_OBSOLETE")),
("os-specific", re.compile(r"_WIN32|__APPLE__|__linux__|_MSC_VER|__EMSCRIPTEN__|IMGUI_DISABLE_WIN32|__ANDROID__|_XBOX|__MINGW")),
("test-engine", re.compile(r"IMGUI_ENABLE_TEST_ENGINE"))]
def cpp_tokens(src):
"""[(kind, text, line, excl)] without whitespace/comments; excl = exclusion tag from enclosing #if, or None."""
out, line, stack, sections = [], 1, [], []
for m in CPP_TOKEN.finditer(src):
kind, text = m.lastgroup, m.group(0)
if kind == "nl": line += 1; continue
if kind == "com":
s = re.match(r"//\s*\[SECTION\]\s*(.+)", text)
if s: sections.append((line, s.group(1).strip()))
line += text.count("\n"); continue
if kind == "ws": line += text.count("\n"); continue
if kind == "pp":
d = re.match(r"#\s*(\w+)", text); d = d.group(1) if d else ""
if d in ("if", "ifdef", "ifndef"):
stack.append(next((t for t, r in EXCLUDE_PP if r.search(text)), None))
elif d in ("else", "elif") and stack:
stack[-1] = None if stack[-1] in ("if0", "obsolete") else stack[-1] # #else of `#if 0` / obsolete is the live path
elif d == "endif" and stack:
stack.pop()
line += text.count("\n"); continue
out.append((kind, text, line, next((x for x in reversed(stack) if x), None)))
return out, sections
CPP_MACROS = {"IM_MSVC_RUNTIME_CHECKS_OFF", "IM_MSVC_RUNTIME_CHECKS_RESTORE"} # argument-less macro lines, no trailing ';'
CTRL = {"if", "for", "while", "switch", "catch", "sizeof", "return", "alignof", "decltype", "IM_ASSERT", "defined"}
def match_back(toks, i, close, open_):
depth = 0
while i >= 0:
t = toks[i][1]
if t == close: depth += 1
elif t == open_:
depth -= 1
if depth == 0: return i
i -= 1
return -1
def match_fwd(toks, i):
depth = 0
pairs = {"{": "}", "(": ")", "[": "]"}
open_ = toks[i][1]; close = pairs[open_]
while i < len(toks):
t = toks[i][1]
if t == open_: depth += 1
elif t == close:
depth -= 1
if depth == 0: return i
i += 1
return len(toks) - 1
def func_head(toks, i):
"""toks[i] is '{'. If it opens a function body, return (class, name, name_tok_index, cpp_only) else None."""
j = i - 1
while j >= 0 and toks[j][1] in ("const", "noexcept", "override", "final", "IM_FMTARGS", "IM_FMTLIST"):
j -= 1
if j >= 0 and toks[j][1] == ")" and toks[match_back(toks, j, ")", "(") - 1][1] in ("IM_FMTARGS", "IM_FMTLIST"):
j = match_back(toks, j, ")", "(") - 2
# constructor initializer list: Foo::Foo(...) : a(1), b{2} {
k = j
while k >= 0 and toks[k][1] in (")", "}"):
o = match_back(toks, k, toks[k][1], "(" if toks[k][1] == ")" else "{")
if o <= 1: break
if toks[o - 2][1] in (",",) or (toks[o - 2][1] == ":" ):
if toks[o - 2][1] == ":": j = o - 3; break
k = o - 3; continue
break
if j < 0 or toks[j][1] != ")": return None
o = match_back(toks, j, ")", "(")
if o <= 0: return None
n = o - 1
name = toks[n][1]
cpp_only = False
if toks[n - 1][1] == "operator" or (n >= 1 and toks[n][0] == "op" and toks[n - 1][1] == "operator"):
return (None, "operator" + name, n, True)
if toks[n][0] == "op" and n >= 2 and toks[n - 1][1] == "operator": return (None, "operator", n, True)
if toks[n][0] != "name" or name in CTRL: return None
if toks[n - 1][1] == "~": cpp_only = True; name = "~" + name
cls = None
if toks[n - 1][1] == "::" or (cpp_only and toks[n - 2][1] == "::"):
q = n - 2 if not cpp_only else n - 3
cls = toks[q][1]
if toks[n - 1][1] == "operator": return (cls, "operator", n, True)
return (cls, name, n, cpp_only)
class UpFunc:
def __init__(s, file, cls, name, line, body, section, excl):
s.file, s.cls, s.name, s.line, s.body, s.section, s.excl = file, cls, name, line, body, section, excl
@property
def key(s): return "%s::%s" % (s.cls, s.name) if s.cls else s.name
def parse_cpp(fname):
src = open(os.path.join(UP, fname), encoding="utf-8", errors="replace").read()
toks, sections = cpp_tokens(src)
funcs, enums, structs, decls = [], {}, {}, {} # decls: namespace ImGui declarations (name -> line)
def section_at(line):
cur = ""
for l, s in sections:
if l <= line and l > 120: cur = s # skip the index at the top of each file
return cur
def scan(a, b, cls, ns):
i = a
stmt_start = a
while i < b:
k, t, line, excl = toks[i]
if t == "{":
head = func_head(toks, i)
e = match_fwd(toks, i)
pre = [x[1] for x in toks[stmt_start:i]]
if head:
hc, hn, ni, cpp_only = head
c = hc or cls
if cls and hn == cls: c = cls # inline ctor in struct
ex = "c++-only" if cpp_only or hn.startswith("~") else toks[ni][3]
if c and hn == c and fname.endswith(".h") and e - i < 4: ex = ex # trivial ctor kept
funcs.append(UpFunc(fname, c, hn, toks[ni][2], toks[i + 1:e], section_at(toks[ni][2]), ex))
elif "namespace" in pre:
q = pre.index("namespace")
scan(i + 1, e, cls, (pre[q + 1] if len(pre) > q + 1 else ""))
elif "enum" in pre:
nm = next((x for x in pre[pre.index("enum") + 1:] if re.match(r"[A-Za-z_]\w*$", x) and x not in ("class", "int")), None)
vals, j, depth = [], i + 1, 0
expect = True
while j < e:
tt = toks[j][1]
if tt in "([{": depth += 1
elif tt in ")]}": depth -= 1
elif depth == 0 and tt == ",": expect = True
elif expect and depth == 0 and toks[j][0] == "name":
vals.append((tt, toks[j][2], toks[j][3])); expect = False
j += 1
if nm: enums[nm] = (vals, line, section_at(line))
elif any(x in ("struct", "class", "union") for x in pre) and "=" not in pre:
kw = max(q for q, x in enumerate(pre) if x in ("struct", "class", "union")) # skips macro lines / template<...> before it
names = [x for x in pre[kw:] if re.match(r"[A-Za-z_]\w*$", x) and x not in ("struct", "class", "union", "IMGUI_API", "typedef", "final", "public", "private", "alignas")]
nm = names[0] if names else cls
if nm and "union" not in pre: structs.setdefault(nm, ([], line, section_at(line)))
scan(i + 1, e, nm, ns)
if nm and pre[:1] != ["union"] and toks[e + 1][1] != ";" and toks[e + 1][0] == "name" and cls:
pass
i = e + 1
if i < b and toks[i][1] == ";": i += 1
stmt_start = i
continue
if t == ";":
st = toks[stmt_start:i]
txt = [x[1] for x in st]
if txt and txt[0] in ("public", "private", "protected") and len(txt) > 1 and txt[1] == ":": st, txt = st[2:], txt[2:]
while txt and txt[0] in CPP_MACROS: st, txt = st[1:], txt[1:]
if txt and ns == "ImGui" and cls is None and "(" in txt and txt[0] not in ("typedef", "struct", "enum", "using"):
p = txt.index("(")
if p > 0 and st[p - 1][0] == "name" and txt[p - 1] not in CTRL:
decls.setdefault(txt[p - 1], (st[p - 1][2], st[p - 1][3], section_at(st[p - 1][2])))
elif txt and cls and txt[0] not in ("typedef", "friend", "static", "using", "enum", "struct", "class", "template"):
eq = txt.index("=") if "=" in txt else len(txt)
pa = txt.index("(") if "(" in txt else len(txt)
if pa >= eq: # not a method declaration
depth = 0
for j, x in enumerate(txt[:eq] + [";"]):
if x in ("<", "(", "["): depth += 1
elif x in (">", ")", "]"): depth -= 1
elif depth == 0 and x in (",", ";", ":") or (depth == 0 and x == "[" ):
if j > 0 and re.match(r"[A-Za-z_]\w*$", txt[j - 1]) and txt[j - 1] not in ("const", "unsigned", "int", "float"):
structs.setdefault(cls, ([], 0, ""))[0].append((txt[j - 1], st[j - 1][2], st[j - 1][3]))
if x == "[" and depth == 1 and j > 0 and re.match(r"[A-Za-z_]\w*$", txt[j - 1]):
structs.setdefault(cls, ([], 0, ""))[0].append((txt[j - 1], st[j - 1][2], st[j - 1][3]))
if eq < len(txt) and re.match(r"[A-Za-z_]\w*$", txt[eq - 1]):
structs.setdefault(cls, ([], 0, ""))[0].append((txt[eq - 1], st[eq - 1][2], st[eq - 1][3]))
stmt_start = i + 1
i += 1
scan(0, len(toks), None, "")
for k in structs: # dedupe fields
seen, out = set(), []
for f in structs[k][0]:
if f[0] not in seen: seen.add(f[0]); out.append(f)
structs[k] = (out,) + structs[k][1:]
return funcs, enums, structs, decls
# ---------------------------------------------------------------------------------------------------------------------
# Lua side
# ---------------------------------------------------------------------------------------------------------------------
def lua_tokens(src):
out, line, pos = [], 1, 0
while pos < len(src):
m = LUA_TOKEN.match(src, pos)
kind, text = m.lastgroup, m.group(0)
if kind in ("ceq", "seq"): kind = "lcom" if text.startswith("--") else "lstr"
if kind not in ("ws", "com", "lcom"):
out.append(("str" if kind in ("str", "lstr") else kind, text, line))
line += text.count("\n"); pos = m.end()
return out
OPENERS = {"function", "if", "do", "repeat"}
CLOSERS = {"end", "until"}
class PortFunc:
alias = None
def __init__(s, file, cls, name, line, body):
s.file, s.cls, s.name, s.line, s.body = file, cls, name, line, body
def parse_lua():
defs, enum_keys, globals_, fields = [], set(), set(), set()
for f in PORT_FILES:
toks = lua_tokens(open(os.path.join(ROOT, f), encoding="utf-8").read())
n = len(toks)
for i, (k, t, line) in enumerate(toks):
chain, start = None, None
if t == "function" and i + 1 < n and toks[i + 1][0] == "name":
j = i + 1; chain = [toks[j][1]]
while j + 2 < n and toks[j + 1][1] in (".", ":") and toks[j + 2][0] == "name":
chain.append(toks[j + 2][1]); j += 2
if j + 1 < n and toks[j + 1][1] == "(": start = j + 1
else: chain = None
elif k == "name" and i + 3 < n and toks[i + 1][1] == "=" and toks[i + 2][1] == "function" and toks[i + 3][1] == "(":
j = i; chain = [t]
while j >= 2 and toks[j - 1][1] == "." and toks[j - 2][0] == "name":
chain.insert(0, toks[j - 2][1]); j -= 2
start = i + 3; i0 = i + 2
if chain is None: continue
# body: from start's ')' to matching end
j = start
while toks[j][1] != ")": j += 1
body_start, depth, j = j + 1, 1, j + 1
while j < n and depth > 0:
tt = toks[j][1]
if tt in OPENERS and toks[j][0] == "name": depth += 1
elif tt in CLOSERS and toks[j][0] == "name": depth -= 1
j += 1
name = chain[-1]
cls = LUA_CLASS_ALIAS.get(chain[-2], chain[-2]) if len(chain) > 1 else None
if cls and cls.endswith("MT") and len(cls) > 2: cls = cls[:-2] # method tables: function ExampleAppLogMT:AddLog()
if cls is None and re.match(r"[A-Z]\w*_[A-Z]\w*$", name) and "__" not in name: # Class_Method globals (C++ static members)
c, _, m = name.partition("_")
defs.append(PortFunc(f, c, m, line, toks[body_start:j - 1]))
pf = PortFunc(f, cls, name, line, toks[body_start:j - 1])
bt = [x[1] for x in pf.body]
if len(bt) >= 5 and bt[0] == "return" and bt[-3:] == ["(", "...", ")"] and all(x[0] == "name" or x[1] in (".", ":") for x in pf.body[1:-3]):
pf.alias = " ".join(bt[1:-3]) + "(...)"; pf.body = pf.body[1:-3] # forwarder: function(...) return ImGui.X(...) end
defs.append(pf)
# aliases / factories: ImGui.DragFloat2 = DragFloatN(2) ImFormatString = string.format ImGui.LogTextV = ImGui.LogText
for i, (k, t, line) in enumerate(toks):
if k != "name" or (i and toks[i - 1][2] == line) or t == "local": continue
j, chain = i, [t]
while j + 2 < n and toks[j + 1][1] == "." and toks[j + 2][0] == "name":
chain.append(toks[j + 2][1]); j += 2
if j + 2 >= n or toks[j + 1][1] != "=" or toks[j + 2][0] != "name" or toks[j + 2][1] in ("function", "nil", "true", "false", "not"): continue
if len(chain) == 1 and not re.match(r"(Im|STB|IMSTB|stb)", t): continue
if len(chain) > 1 and chain[0] not in ("ImGui", "ImStd", "ImStb", "MT") and chain[-2] not in LUA_CLASS_ALIAS: continue
rhs = [x for x in toks[j + 2:] if x[2] == toks[j + 2][2]]
cls = LUA_CLASS_ALIAS.get(chain[-2], chain[-2]) if len(chain) > 1 else None
pf = PortFunc(f, cls, chain[-1], line, rhs); pf.alias = " ".join(x[1] for x in rhs[:6])
defs.append(pf)
# enum tables / globals / fields
depth_stack = []
for i, (k, t, line) in enumerate(toks):
if t == "{":
owner = None
if i >= 2 and toks[i - 1][1] == "=" and toks[i - 2][0] == "name": owner = toks[i - 2][1]
depth_stack.append(owner)
elif t == "}":
if depth_stack: depth_stack.pop()
elif k == "name" and i + 1 < n and toks[i + 1][1] == "=":
prev = toks[i - 1][1] if i else ""
if prev in ("{", ",", ";") or (prev == "." ) or (prev not in ("local", "for") and toks[i - 1][0] != "name" and prev not in ("]", ")")):
fields.add(t)
if prev in ("{", ",") and depth_stack and depth_stack[-1]: enum_keys.add((depth_stack[-1], t))
if prev == "." and i >= 2: enum_keys.add((toks[i - 2][1], t))
if prev not in (".", "local", ",") and not depth_stack: globals_.add(t)
elif k == "str" and i >= 1 and toks[i - 1][1] == "[" and i + 2 < n and toks[i + 1][1] == "]" and toks[i + 2][1] == "=" and depth_stack and depth_stack[-1]:
enum_keys.add((depth_stack[-1], toks[i][1].strip("\"'")))
return defs, enum_keys, globals_, fields
# ---------------------------------------------------------------------------------------------------------------------
# Normalization + similarity
# ---------------------------------------------------------------------------------------------------------------------
CPP_DROP = {"const", "static", "float", "int", "unsigned", "signed", "bool", "char", "void", "auto", "double", "size_t", "short", "long",
"ImU8", "ImU16", "ImU32", "ImU64", "ImS8", "ImS16", "ImS32", "ImS64", "ImWchar", "ImGuiID", "struct", "inline", "IMGUI_API",
"constexpr", "template", "typename", "IM_UNUSED", "this", ";", "{", "}", "(", ")", "&", "*", "ImGuiContext", "ImGuiWindow", "ImGuiStyle",
"ImGuiIO", "ImDrawList", "ImFont", "ImRect", "ImVec2", "ImVec4", "case", "break", "default", ":", "[", "]"}
LUA_DROP = {"local", "then", "do", "end", "self", "(", ")", ";", "{", "}", "&", "*", "[", "]", ":", "bit32", "math", "string", "table"}
LUA_MAP = {"~=": "!=", "not": "!", "and": "&&", "or": "||", "nil": "NULL", "elseif": "else if", "band": "&", "bor": "|", "bxor": "^",
"bnot": "~", "lshift": "<<", "rshift": ">>", "btest": "&", "ImMax": "ImMax", "ImMin": "ImMin"}
CPP_MAP = {"->": ".", "::": ".", "nullptr": "NULL", "++": "+=", "--": "-=", "IM_COUNTOF": "#", "IM_ARRAYSIZE": "#", "true": "true", "false": "false"}
def norm_num(t):
try:
if t.lower().startswith("0x"): return str(int(t.rstrip("uUlL").replace("'", ""), 16))
return repr(float(t.rstrip("fFuUlL").replace("'", "")))
except ValueError: return t
def split_enum(t):
m = re.match(r"(Im[A-Za-z]+)_(\w+)$", t)
return [m.group(1), ".", m.group(2)] if m and not t.endswith("_") else [t]
def norm_cpp(body):
out = []
for k, t, _, _ in body:
if k == "num": out.append(norm_num(t)); continue
if k == "str": out.append("S"); continue
t = CPP_MAP.get(t, t)
if t in CPP_DROP: continue
out.extend(split_enum(t) if k == "name" else [t])
o2 = []
for i, t in enumerate(out): # drop the implicit ImGui:: qualifier (C++ calls inside the namespace omit it)
if t == "ImGui" and i + 1 < len(out) and out[i + 1] == ".": continue
if t == "." and o2 and o2[-1] in (".",): continue
o2.append(t)
return [t for i, t in enumerate(o2) if not (t == "." and (i == 0 or o2[i - 1] in ("(", ",", "=", "return")))]
def norm_lua(body):
out = []
for k, t, _ in body:
if k == "num": out.append(norm_num(t)); continue
if k == "str": out.append("S"); continue
if t in LUA_DROP: continue
t = LUA_MAP.get(t, t)
out.extend(t.split(" ") if " " in t else [t])
o2 = []
for i, t in enumerate(out):
if t == "ImGui" and i + 1 < len(out) and out[i + 1] == ".": continue
if t == "." and (not o2 or o2[-1] in ("&", "|", "^", "~", "<<", ">>", "=", ",", "return", "if", "while", "!")): continue
o2.append(t)
return o2
def similarity(a, b):
if not a and not b: return 1.0
return SequenceMatcher(None, a, b, autojunk=False).ratio()
def is_stub(port_norm, up_norm):
real = [t for t in port_norm if t not in ("return", "NULL", "false", "true", "0.0", "S", "IM_ASSERT", "!", ",", "=", "0")]
return len(up_norm) > 12 and (len(real) < 3 or not any(re.match(r"[A-Za-z_]\w*$", t) for t in real))
# ---------------------------------------------------------------------------------------------------------------------
def main():
defs, enum_keys, lua_globals, lua_fields = parse_lua()
by_name = {}
for d in sorted(defs, key=lambda d: d.alias is not None): by_name.setdefault(d.name, []).append(d)
port_classes = {d.cls for d in defs if d.cls}
port_class_files = {}
for d in defs:
if d.cls: port_class_files.setdefault(d.cls, set()).add(d.file)
def find(u):
m = re.match(r"I?M?STB_TEXTEDIT_(\w+?)(_IMPL)?$", u.name)
if m: # STB_TEXTEDIT_INSERTCHARS / IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL -> ImStb.TEXTEDIT_*
c = [d for d in by_name.get("TEXTEDIT_" + m.group(1), []) if d.cls == "ImStb"]
return c[0] if c else None
cands = by_name.get(u.name, [])
if u.cls and u.cls not in ("ImGui",):
exact = [d for d in cands if d.cls == u.cls]
if exact: return exact[0]
if u.name == u.cls: # constructor: function ImGuiStyle() / MT.X.new
c = [d for d in cands if d.cls is None] or [d for d in by_name.get("new", []) if d.cls == u.cls]
return c[0] if c else None
if u.cls in port_classes: # only a classless helper in the class's own file (local function _grow_capacity)
loose = [d for d in cands if d.cls is None and d.file in port_class_files[u.cls]] if len(cands) == 1 else []
return loose[0] if loose else None
loose = [d for d in cands if d.cls in (None, "ImGui")] if len(cands) == 1 else []
return loose[0] if loose else None
pref = [d for d in cands if d.cls == "ImGui"] + [d for d in cands if d.cls is None] + [d for d in cands if d.cls in ("ImStd", "ImStb")]
return pref[0] if pref else None
up_names = set()
for f in CPP_FILES:
for u in parse_cpp(f)[0]: up_names.add(u.name)
def expand(p, depth=0, seen=()):
"""Port body with calls to port-only helpers in the same file inlined (the port splits some big functions)."""
out, body = [], p.body
for i, x in enumerate(body):
out.append(x)
if depth < 3 and x[0] == "name" and i + 1 < len(body) and body[i + 1][1] == "(" and x[1] not in up_names and (i == 0 or body[i - 1][1] not in (".", ":")):
h = [d for d in by_name.get(x[1], []) if d.file == p.file and not d.alias and d is not p and d.name not in seen]
if h: out.extend(expand(h[0], depth + 1, seen + (p.name,)))
return out
report, summary = [], []
all_up = {}
enums_all, structs_all, decls_by_file = {}, {}, {}
for f in CPP_FILES:
funcs, enums, structs, decls = parse_cpp(f)
merged = {}
for u in funcs: # merge overloads (keep the largest body for similarity)
if u.key not in merged or len(u.body) > len(merged[u.key].body):
if u.key in merged and merged[u.key].excl is None: u.excl = None
merged[u.key] = u
elif u.excl is None: merged[u.key].excl = None
all_up[f] = list(merged.values())
for k, v in enums.items(): enums_all.setdefault(k, v)
for k, v in structs.items():
if k in structs_all: structs_all[k][0].extend(x for x in v[0] if x[0] not in {y[0] for y in structs_all[k][0]})
else: structs_all[k] = v
decls_by_file[f] = decls
port_src = "\n".join(open(os.path.join(ROOT, f), encoding="utf-8").read() for f in PORT_FILES)
ev_ok = {ev: ev is None or re.search(ev, port_src) is not None for _, _, ev in REPLACED}
def replaced(key):
return next((why for pat, why, ev in REPLACED if ev_ok[ev] and fnmatchcase(key, pat)), None)
tot = {"n": 0, "have": 0, "stub": 0, "size": 0, "size_have": 0, "simw": 0.0, "size_sim": 0, "repl": 0}
for f in CPP_FILES:
rows = []
st = {"n": 0, "have": 0, "stub": 0, "size": 0, "size_have": 0, "simw": 0.0, "size_sim": 0, "repl": 0}
for u in sorted(all_up[f], key=lambda u: u.line):
un = norm_cpp(u.body)
if u.excl:
rows.append((u.section, "[-]", "", u, None, u.excl)); continue
p = find(u)
why = replaced(u.key) if p is None else None
if why:
st["repl"] += 1; rows.append((u.section, "[=]", "", u, None, why)); continue
st["n"] += 1; st["size"] += len(un)
if p is None:
rows.append((u.section, "[ ]", "", u, None, "")); continue
body = p.body
if p.alias: # ImGui.X = ImGui.Y / Factory(n): compare against the target's body when it is a plain reference
tgt = [x[1] for x in p.body if x[0] == "name"]
t = by_name.get(tgt[-1]) if tgt and all(x[1] == "." or x[0] == "name" for x in p.body) else None
t = [d for d in (t or []) if not d.alias]
body = t[0].body if t else None
if body is None: # alias of a Lua builtin / factory: ported, but no body to compare
st["have"] += 1; st["size_have"] += len(un)
rows.append((u.section, "[x]", " n/a", u, p, "= " + p.alias)); continue
pn = norm_lua(expand(p) if not p.alias else body)
sim = similarity(un, pn)
stub = is_stub(pn, un) and not p.alias
st["have"] += 1; st["size_have"] += len(un); st["simw"] += sim * len(un); st["size_sim"] += len(un)
if stub: st["stub"] += 1
rows.append((u.section, "[~]" if stub else "[x]", "%3.0f%%" % (100 * sim), u, p, "stub" if stub else ("= " + p.alias if p.alias else "")))
for k in tot: tot[k] += st[k]
summary.append((f, st))
report.append((f, rows))
def pct(a, b): return "%5.1f%%" % (100.0 * a / b) if b else " - "
# enums / fields / API declarations
enum_rows, enum_n, enum_have = [], 0, 0
for nm, (vals, line, sec) in sorted(enums_all.items(), key=lambda x: x[0]):
base = re.sub(r"(Private)?_$", "", nm)
missing, have, counted = [], 0, 0
for v, vline, excl in vals:
if excl or v.endswith("_COUNT") and False: continue
pre, _, key = v.partition("_")
ok = v in lua_globals or (pre, key) in enum_keys or (base, key) in enum_keys or (nm, v) in enum_keys or (base, v) in enum_keys
if not ok and not key: ok = v in lua_globals
enum_n += 1; counted += 1
if ok: have += 1; enum_have += 1
else: missing.append(v)
enum_rows.append((nm, have, counted, missing))
field_rows, field_n, field_have, field_repl = [], 0, 0, 0
for nm, (fl, line, sec) in sorted(structs_all.items()):
fl = [x for x in fl if not x[2]]
repl = [x[0] for x in fl if x[0] not in lua_fields and replaced("%s::%s" % (nm, x[0]))]
fl = [x for x in fl if x[0] not in repl]
if not fl and not repl: continue
missing = [x[0] for x in fl if x[0] not in lua_fields]
field_n += len(fl); field_have += len(fl) - len(missing); field_repl += len(repl)
field_rows.append((nm, len(fl) - len(missing), len(fl), missing, repl))
api_rows, api_repl = [], 0
port_names = set(by_name)
for f, label in (("imgui.h", "Public API (imgui.h, namespace ImGui)"), ("imgui_internal.h", "Internal API (imgui_internal.h, namespace ImGui)")):
d = {k: v for k, v in decls_by_file[f].items() if not v[1]}
r = [k for k in d if k not in port_names and replaced("ImGui::" + k)]
api_repl += len(r)
for k in r: del d[k]
have = [k for k in d if k in port_names]
api_rows.append((label, len(have), len(d), sorted((k for k in d if k not in port_names), key=lambda k: d[k][0]), d))
# ------------------------------------------------------------------ summary (stdout + file)
head = []
head.append("%-22s %9s %9s %9s %11s %9s %9s" % ("upstream file", "funcs", "ported", "by size", "similarity", "stubs", "replaced"))
for f, s in summary:
head.append("%-22s %9d %9s %9s %11s %9d %9d" % (f, s["n"], pct(s["have"], s["n"]), pct(s["size_have"], s["size"]),
pct(s["simw"], s["size_sim"]), s["stub"], s["repl"]))
head.append("%-22s %9d %9s %9s %11s %9d %9d" % ("TOTAL", tot["n"], pct(tot["have"], tot["n"]), pct(tot["size_have"], tot["size"]),
pct(tot["simw"], tot["size_sim"]), tot["stub"], tot["repl"]))
head.append("")
head.append("upstream code reproduced (similarity x ported size / all upstream size): %s" % pct(tot["simw"] / max(1, tot["size_sim"]) * tot["size_have"], tot["size"]))
for label, h, n, _, _ in api_rows: head.append("%-52s %4d / %4d %s" % (label, h, n, pct(h, n)))
head.append("%-52s %4d / %4d %s" % ("Enum values (imgui.h + imgui_internal.h)", enum_have, enum_n, pct(enum_have, enum_n)))
head.append("%-52s %4d / %4d %s" % ("Struct fields (imgui.h + imgui_internal.h)", field_have, field_n, pct(field_have, field_n)))
head.append("%d intentionally replaced by Lua equivalents [=] (%d functions, %d API declarations, %d struct fields), not counted above"
% (tot["repl"] + api_repl + field_repl, tot["repl"], api_repl, field_repl))
print("\n".join(head))
L = []
L.append("PORT STATUS: imgui-luau vs upstream Dear ImGui (docking, %s)" % re.search(r'IMGUI_VERSION\s+"([^"]+)"', open(os.path.join(UP, "imgui.h")).read()).group(1))
L.append("Generated by tools/compare_upstream.py - do not edit. Upstream sources: tools/test/upstream/.")
L.append("")
L.append("Columns: funcs = upstream function definitions (overloads merged), excluding [-] rows")
L.append(" ported = share that has a port definition with the same name")
L.append(" by size = same, weighted by upstream body size (tokens)")
L.append(" similarity = normalized token similarity of the ported bodies (difflib ratio), weighted by size")
L.append(" stubs = ported but (nearly) empty in the port")
L.append(" replaced = [=] rows: intentionally replaced by a Lua mechanism (REPLACED table), excluded from funcs")
L.append("Name-based matching: a renamed port function shows as [ ], a same-named one counts even if it differs. Treat as estimates.")
L.append("Port-only helpers called from a ported function (same file) are inlined before comparing, since the port splits some big functions.")
L.append("Known low scores that are not missing code: data-driven port code (e.g. DemoWindowTables' section table), the older 1.92")
L.append("polyline stroker (AddPolyline), Lua-native replacements (ImGuiStorage, ImStr*, ImFormatString -> string.format).")
L.append("")
L += head
L.append("")
L.append("Legend: [x] ported (sim %) [~] stub [ ] not ported [=] replaced by Lua equivalent (reason) [-] excluded (c++-only / obsolete / os-specific / #if 0 / test-engine)")
for f, rows in report:
L.append("")
L.append("=" * 100)
L.append("%s" % f)
L.append("=" * 100)
cur = None
for sec, mark, sim, u, p, note in rows:
if sec != cur:
cur = sec; L.append(""); L.append(" -- %s" % (sec or "(top)"))
where = "%s:%d" % (p.file, p.line) if p else ""
L.append(" %s %5s %-58s %-32s %s" % (mark, sim, u.key, where, note))
L.append("")
L.append("=" * 100)
L.append("API declarations not ported (by upstream line; [=] replacements excluded)")
L.append("=" * 100)
for label, h, n, missing, d in api_rows:
L.append(""); L.append(" %s: %d / %d" % (label, h, n))
cur = None
for k in missing:
if d[k][2] != cur: cur = d[k][2]; L.append(" -- %s" % (cur or "(top)"))
L.append(" [ ] ImGui::%s" % k)
L.append("")
L.append("=" * 100)
L.append("Enums (values ported / total; missing listed)")
L.append("=" * 100)
for nm, h, n, missing in enum_rows:
if not n: continue
L.append(" %-40s %4d / %4d%s" % (nm, h, n, (" missing: " + ", ".join(missing)) if missing else ""))
L.append("")
L.append("=" * 100)
L.append("Struct fields (name found in the port / total; missing listed; [=] replaced by Lua equivalent, excluded from total). Field names are matched globally, so common names may over-count.")
L.append("=" * 100)
for nm, h, n, missing, repl in field_rows:
L.append(" %-40s %4d / %4d%s%s" % (nm, h, n, (" missing: " + ", ".join(missing)) if missing else "",
(" [=] " + ", ".join("%s (%s)" % (x, replaced("%s::%s" % (nm, x))) for x in repl)) if repl else ""))
out = os.path.join(ROOT, "docs", "PORT_STATUS.txt")
open(out, "w").write("\n".join(L) + "\n")
print("\nwrote docs/PORT_STATUS.txt")
if __name__ == "__main__":
main()
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# All local checks: CI's "Check" step (release.yml) + every scripted test + whole demo forced open + LÖVE.
# Usage: tools/test/check_all.sh (prints PASS/FAIL per check, exit 1 if any failed)
# HASHES=1 tools/test/check_all.sh (also prints the draw-data checksums, see hashes.sh)
cd "$(dirname "$0")/../.."
export PATH="$PATH:/nix/store/2m2ja613nndihc8qm10f2rhq6znp1nh4-luau-0.734/bin:/nix/store/4qxq4arv32ly13kl3g0m5q80gm3a35xr-luajit-2.1.1741730670/bin"
T=$(mktemp -d /tmp/imgui_check.XXXX)
R="python3 tools/test/run.py --quiet --roblox --timeout 600"
# name | expected output | command
CHECKS=(
"readme|README OK|$R --main tools/test/scripts/t_readme.luau --frames 30"
"readme-min|README OK|MINIFY=1 $R --main tools/test/scripts/t_readme.luau --frames 30"
"cached|CACHED OK|$R --main tools/test/scripts/t_cached_window.luau --frames 130"
"fixes|FIXES OK|$R --main tools/test/scripts/t_fixes.luau --frames 55"
"debugtools|DEBUGTOOLS OK|$R --main tools/test/scripts/t_debugtools.luau --frames 65"
"helpers|HELPERS OK|$R --main tools/test/scripts/t_helpers.luau --frames 45"
"multiselect|MULTISELECT OK|$R --main tools/test/scripts/t_multiselect.luau --frames 380"
"nodemo|NODEMO OK|NO_DEMO=1 $R --main tools/test/scripts/t_nodemo.luau --frames 25"
"widgets|OK|$R --main tools/test/scripts/t_widgets.luau --frames 80"
"tables|OK|$R --main tools/test/scripts/t_tables.luau --frames 150"
"tabs|scrollbtn.true|$R --main tools/test/scripts/t_tabs.luau --frames 60"
"modal|round trip ok|$R --main tools/test/scripts/t_modal.luau --frames 40"
"dock|DOCK OK|$R --main tools/test/scripts/t_dock.luau --frames 60"
"dock_close|DOCK CLOSE OK|$R --main tools/test/scripts/t_dock_close.luau --frames 60"
"dock_drag|OK|$R --main tools/test/scripts/t_dock_drag.luau --frames 80"
"dock_ini|ROUNDTRIP OK|$R --main tools/test/scripts/t_dock_ini.luau --frames 60"
"dock_move|OK|$R --main tools/test/scripts/t_dock_move.luau --frames 80"
"passthru|PASSTHRU OK|$R --main tools/test/scripts/t_passthru.luau --frames 40"
"fonts|FONTS OK|$R --main tools/test/scripts/t_fonts.luau --frames 8"
"textproxy|PROXY OK|$R --main tools/test/scripts/t_textproxy.luau --frames 30"
"style_editor|SE OK|$R --main tools/test/scripts/t_style_editor.luau --frames 20"
"style_leak|STYLE OK|$R --main tools/test/scripts/t_style_leak.luau --frames 60 --open"
"apps|APPS OK|$R --main tools/test/scripts/t_apps.luau --frames 120 --open"
"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"
)
run() { # $1=name $2=expect $3=cmd
bash -c "$3" > "$T/$1.txt" 2>&1; code=$?
if [ $code -eq 0 ] && grep -q "$2" "$T/$1.txt" && ! grep -qiE "error|traceback|stack begin|FAIL" "$T/$1.txt"; then echo "PASS $1"
else echo "FAIL $1 (exit $code, log $T/$1.txt)"; tail -5 "$T/$1.txt" | sed 's/^/ /'; fi
}
for c in "${CHECKS[@]}"; do IFS='|' read -r n e cmd <<< "$c"; run "$n" "$e" "$cmd" & done > "$T/summary.txt"
wait
sort "$T/summary.txt"
[ -n "$HASHES" ] && tools/test/hashes.sh
! grep -q "^FAIL" "$T/summary.txt"
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Draw-data checksums of the working tree vs a git rev (default HEAD), to prove a change renders identically.
# tools/test/hashes.sh [rev]
# lib = t_libhash (library only, no demo): must stay equal unless rendering is meant to change
# demo = t_drawhash with Widgets,Basic / whole demo open: changes with any demo edit
cd "$(dirname "$0")/../.."
export PATH="$PATH:/nix/store/2m2ja613nndihc8qm10f2rhq6znp1nh4-luau-0.734/bin:/nix/store/4qxq4arv32ly13kl3g0m5q80gm3a35xr-luajit-2.1.1741730670/bin"
REV=${1:-HEAD}
hashes() { # $1 = repo dir
( cd "$1"
R="python3 tools/test/run.py --quiet --roblox --timeout 600"
echo "lib $(NO_DEMO=1 $R --main "$OLDPWD/tools/test/scripts/t_libhash.luau" --frames 120 | grep -o 'LIBHASH.*')" &
echo "demo-basic $($R --main tools/test/scripts/t_drawhash.luau --frames 150 --open-only Widgets,Basic | grep -o 'DRAWHASH.*')" &
echo "demo-all $($R --main tools/test/scripts/t_drawhash.luau --frames 150 --open | grep -o 'DRAWHASH.*')" &
echo "love-basic $(python3 tools/bundle.py --target love >/dev/null; luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic | grep -o 'DRAWHASH.*')" &
wait ) | tr "\t" " " | sort
}
W=$(mktemp -d /tmp/imgui_rev.XXXX)
git worktree add -q --detach "$W" "$REV"
hashes "$W" > "$W.old" & hashes . > "$W.new"; wait
git worktree remove --force "$W"
echo "check $REV working tree"
paste "$W.old" "$W.new" | awk -F'\t' '{split($1,a," "); split($2,b," "); printf "%-12s %-30s %-30s %s\n", a[1], a[3], b[3], (a[3]==b[3]?"same":"CHANGED")}'
+1 -1
View File
@@ -4,4 +4,4 @@ set -e
cd "$(dirname "$0")/../../.." cd "$(dirname "$0")/../../.."
python3 tools/bundle.py --target love >/dev/null 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" 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): 1385106188 / 1292122782" echo "expected (Luau, tools/test/scripts/t_drawhash.luau): 154977593 / 263656315"
+2 -2
View File
@@ -1,5 +1,5 @@
-- Runs examples/example_love and examples/example_shared main.lua headless under LuaJIT (mock love) for some frames. -- Runs examples/example_love main.lua headless under LuaJIT (mock love) for some frames.
for _, ex in ipairs({ "examples/example_love", "examples/example_shared" }) do for _, ex in ipairs({ "examples/example_love" }) do
dofile("tools/test/love/mock_love.lua") dofile("tools/test/love/mock_love.lua")
love.filesystem.getSource = function() return ex end love.filesystem.getSource = function() return ex end
love.filesystem.load = function(p) return assert(loadfile(ex .. "/" .. p)) end love.filesystem.load = function(p) return assert(loadfile(ex .. "/" .. p)) end
+1 -1
View File
@@ -68,7 +68,7 @@ def main():
ap.add_argument("--quiet", action="store_true", help="no random input (scripted tests drive io themselves)") ap.add_argument("--quiet", action="store_true", help="no random input (scripted tests drive io themselves)")
args = ap.parse_args() args = ap.parse_args()
code, out = run(args, "") code, out = run(args, os.environ.get("RUN_FLAGS", ""))
print(out if os.environ.get("FULL_OUTPUT") else out[-4000:]) print(out if os.environ.get("FULL_OUTPUT") else out[-4000:])
sys.exit(code) sys.exit(code)
+1 -1
View File
@@ -7,7 +7,7 @@ io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.DockingEnable)
G.ImGui_ImplRoblox.Init() G.ImGui_ImplRoblox.Init()
local names = { "ShowExampleAppConsole", "ShowExampleAppLog", "ShowExampleAppLayout", "ShowExampleAppPropertyEditor", "ShowExampleAppLongText", local names = { "ShowExampleAppConsole", "ShowExampleAppLog", "ShowExampleAppLayout", "ShowExampleAppPropertyEditor", "ShowExampleAppLongText",
"ShowExampleAppAutoResize", "ShowExampleAppConstrainedResize", "ShowExampleAppSimpleOverlay", "ShowExampleAppWindowTitles", "ShowExampleAppAutoResize", "ShowExampleAppConstrainedResize", "ShowExampleAppSimpleOverlay", "ShowExampleAppWindowTitles",
"ShowExampleAppCustomRendering", "ShowExampleAppDocuments", "ShowExampleAppDockSpace", "ShowExampleAppFullscreen", "ShowExampleAppAssetsBrowser", "ShowExampleAppMainMenuBar" } "ShowExampleAppCustomRendering", "ShowExampleAppDocuments", "ShowExampleAppDockSpace", "ShowExampleAppFullscreen", "ShowExampleAppAssetsBrowser", "ShowExampleAppMainMenuBar", "ShowExampleAppImageViewer" }
local tools = { "ShowMetricsWindow", "ShowDebugLogWindow", "ShowIDStackToolWindow", "ShowAboutWindow" } local tools = { "ShowMetricsWindow", "ShowDebugLogWindow", "ShowIDStackToolWindow", "ShowAboutWindow" }
local open = {} local open = {}
local frame = 0 local frame = 0
+92
View File
@@ -0,0 +1,92 @@
-- Debug tools + misc imgui.cpp ports: item picker, DebugFlashStyleColor, DebugLocateItem, context hooks, AddInputCharactersUTF8, ImBezierCubicClosestPoint
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
local ctx = ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
local io = ImGui.GetIO()
local failed = {}
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
-- ImBezierCubicClosestPoint: straight-line cubic (0,0)-(30,0)-(60,0)-(90,0): closest point to (45,10) is (45,0)
local p1, p2, p3, p4 = ImVec2(0, 0), ImVec2(30, 0), ImVec2(60, 0), ImVec2(90, 0)
local cp = G.ImBezierCubicClosestPoint(p1, p2, p3, p4, ImVec2(45, 10), 20)
check(math.abs(cp.x - 45) < 1e-3 and math.abs(cp.y) < 1e-6, ("ImBezierCubicClosestPoint %g,%g"):format(cp.x, cp.y))
-- Curved: result must lie on the curve (distance to a dense sampling of the curve ~0) and beat the endpoints
local q1, q2, q3, q4 = ImVec2(0, 0), ImVec2(0, 100), ImVec2(100, 100), ImVec2(100, 0)
local target = ImVec2(50, 120)
for _, r in { G.ImBezierCubicClosestPoint(q1, q2, q3, q4, target, 64), G.ImBezierCubicClosestPointCasteljau(q1, q2, q3, q4, target, 0.25) } do
check(math.abs(r.x - 50) < 0.5 and math.abs(r.y - 75) < 0.5, ("closest point on curve %g,%g (expected ~50,75)"):format(r.x, r.y))
end
-- AddInputCharactersUTF8 queues one Text event per codepoint
io:AddInputCharactersUTF8("a\u{e9}\u{20ac}\u{1F600}")
local chars = {}
for _, e in ctx.InputEventsQueue.Data do if e.Type == G.ImGuiInputEventType.Text then table.insert(chars, e.Text.Char) end end
-- (this build has IM_UNICODE_CODEPOINT_MAX = 0xFFFF like upstream's default ImWchar16: U+1F600 becomes IM_UNICODE_CODEPOINT_INVALID)
check(#chars == 4 and chars[1] == 0x61 and chars[2] == 0xE9 and chars[3] == 0x20AC and chars[4] == ((G.IM_UNICODE_CODEPOINT_MAX >= 0x1F600) and 0x1F600 or 0xFFFD), "AddInputCharactersUTF8 " .. table.concat(chars, ","))
-- Context hooks
local log = {}
local function hook(t, name)
local h = G.ImGuiContextHook(); h.Type = t; h.Callback = function(c, hk) table.insert(log, name) end
return ImGui.AddContextHook(ctx, h)
end
local T = G.ImGuiContextHookType
hook(T.NewFramePre, "nfpre"); hook(T.NewFramePost, "nfpost"); hook(T.RenderPre, "rpre"); local rpost = hook(T.RenderPost, "rpost")
-- Context defaults (ImGuiContext()/ImGuiIO() constructors, upstream values)
check(ctx.DebugFlashStyleColorIdx == G.ImGuiCol.COUNT and ctx.ActiveIdSource == G.ImGuiInputSource.None and io.MouseSource == G.ImGuiMouseSource.Mouse
and ctx.DebugBreakKeyChord == G.ImGuiKey.Pause and ctx.DebugItemPickerActive == false and ctx.DebugLocateId == 0 and ctx.CurrentPixelDensity == 1.0, "context defaults")
check(T.PendingRemoval_ == 7 and T.Shutdown == 6, "ImGuiContextHookType values")
check(io.BackendPlatformName == "imgui_impl_roblox" and io.BackendRendererName == "imgui_impl_roblox", "BackendPlatformName/BackendRendererName")
check((os.date == nil) == (ImGui.GetPlatformIO().Platform_SessionDate == 0), "Platform_SessionDate set from os.date() when available") -- (the test sandbox has no os.date)
ImGui.GetPlatformIO().Platform_SessionDate = 20261015 -- exercise the metrics Settings section date code
-- Style color flash
local col = ImGui.GetStyle().Colors[G.ImGuiCol.Button]
local orig = { col.x, col.y, col.z, col.w }
local f, rect, picked, broke, button_id = 0, nil, nil, 0, 0
ctx.DebugBreakCallback = function() broke += 1 end
game:GetService("RunService").Stepped:Connect(function()
f += 1
G.ImGui_ImplRoblox.NewFrame()
if rect and f == 5 then io:AddMousePosEvent((rect[1].x + rect[2].x) / 2, (rect[1].y + rect[2].y) / 2) end
if f == 6 then ImGui.DebugStartItemPicker() end
if f == 8 then io:AddMouseButtonEvent(0, true) elseif f == 9 then io:AddMouseButtonEvent(0, false) end
ImGui.NewFrame()
if f == 1 then check(table.concat(log, " ") == "nfpre nfpost", "hooks after NewFrame: " .. table.concat(log, " ")) end
if f == 8 then picked = ctx.DebugItemPickerBreakId end
if f == 3 then ImGui.DebugFlashStyleColor(G.ImGuiCol.Button) end
ImGui.SetNextWindowPos(ImVec2(10, 10), G.ImGuiCond.Always)
ImGui.Begin("DebugTools")
ImGui.Button("Target")
if f == 2 then local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); rect = { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end
local target_id = ctx.LastItemData.ID
button_id = target_id
if f == 12 then ImGui.DebugLocateItem(target_id) end
if f == 13 then check(ctx.DebugLocateId == 0, "DebugLocateItem resolved by ItemAdd()") end
ImGui.DebugDrawItemRect(); ImGui.DebugDrawCursorPos(); ImGui.DebugDrawLineExtents()
ImGui.DebugTextUnformattedWithLocateItem(("id 0x%08X here"):format(target_id))
ImGui.End()
if f == 3 then -- open the metrics Settings/InputText sections
local mw = ImGui.FindWindowByName("Dear ImGui Metrics/Debugger")
for _, lbl in { "Settings", "SettingsWindows", "SettingsTables", "InputText" } do mw.DC.StateStorage[mw:GetID(lbl)] = true end
ctx.DebugMetricsConfig.SettingsHighlightOldEntries = true
end
ImGui.SetNextWindowPos(ImVec2(400, 10), G.ImGuiCond.Always); ImGui.ShowMetricsWindow()
ImGui.SetNextWindowPos(ImVec2(400, 300), G.ImGuiCond.Always); ImGui.ShowDebugLogWindow()
if f == 4 then
local c = ImGui.GetStyle().Colors[G.ImGuiCol.Button]
check(c.w == 1.0 and ctx.DebugFlashStyleColorIdx == G.ImGuiCol.Button, "flash active")
end
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if f == 1 then check(table.concat(log, " ") == "nfpre nfpost rpre rpost", "hooks after Render: " .. table.concat(log, " ")); ImGui.RemoveContextHook(ctx, rpost) end
if f == 2 then check(table.concat(log, " ") == "nfpre nfpost rpre rpost nfpre nfpost rpre", "RemoveContextHook: " .. table.concat(log, " ")) end
if f == 60 then
local c = ImGui.GetStyle().Colors[G.ImGuiCol.Button]
check(c.x == orig[1] and c.y == orig[2] and c.z == orig[3] and c.w == orig[4] and ctx.DebugFlashStyleColorIdx == G.ImGuiCol.COUNT, "flash restored color exactly")
check(picked ~= nil and picked ~= 0 and picked == button_id, ("item picker set DebugItemPickerBreakId (%s vs %s)"):format(tostring(picked), tostring(button_id)))
check(not ctx.DebugItemPickerActive and broke >= 1, ("item picker break hook called (%d)"):format(broke))
if #failed == 0 then print("DEBUGTOOLS OK") end
end
end)
+142
View File
@@ -0,0 +1,142 @@
-- Regressions vs upstream: clipboard paste filter (InputTextEx), ImParseFormatTrimDecorations (Ctrl+click temp input), ShrinkWidths tie order,
-- CalcItemSize by value, PushClipRectFullScreen (drop target outside clip rect), long TextUnformatted, PushFont/PopFont, InputTextMultiline line index (render/scroll/edit), Shortcut() default
-- args, IsWindowAppearing() on frame 1 (FrameCount starts at 0), ColorButton -> ColorEdit4 drag and drop
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext()
ImGui.GetIO().Fonts:AddFontDefault()
local font2 = ImGui.GetIO().Fonts:AddFontDefault() -- second font for the PushFont/PopFont check
G.ImGui_ImplRoblox.Init()
local io = ImGui.GetIO()
local failed = {}
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
-- ShrinkWidths: equal widths sort by Index descending (upstream ShrinkWidthItemComparer), so the rounding remainder goes to the highest Index first
local items = {}
for i = 1, 4 do local it = G.ImGuiShrinkWidthItem(); it.Index = i - 1; it.Width = 100; it.InitialWidth = 100; items[i] = it end
ImGui.ShrinkWidths(items, 1, 4, 2.0, 10.0)
local w = {}
for _, it in items do w[it.Index] = it.Width end
check(w[3] == 100 and w[2] == 100 and w[1] == 99 and w[0] == 99, ("ShrinkWidths widths %s %s %s %s"):format(w[0], w[1], w[2], w[3]))
check(G.ImFloor(-1.5) == -2 and G.ImFloor(-0.2) == -1 and G.ImFloor(2.7) == 2, "ImFloor negative")
local long_text = {}
for i = 1, 300 do long_text[i] = string.format("line %04d", i) end
long_text = table.concat(long_text, "\n") -- 2999 bytes
local function edit_text()
local st = ImGui.GetCurrentContext().InputTextState
return G.ImGui._ByteArrayToString(st.TextA.Data, 1, st.TextLen + 1)
end
local buf = ImGui.NewTextBuffer("", 64)
local f, sf, di = 0, 45.0, 50
local rects = {}
local function rect() local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); return { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end -- copies: GetItemRectMin() returns the live LastItemData.Rect.Min
local function click(name, ctrl)
local r = rects[name]
io:AddMousePosEvent((r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2)
if ctrl then io:AddKeyEvent(G.ImGuiMod_Ctrl, true) end
io:AddMouseButtonEvent(0, true)
end
local function release() io:AddMouseButtonEvent(0, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
local function key(k) io:AddKeyEvent(k, true); io:AddKeyEvent(k, false) end
local function center(name) local r = rects[name]; return (r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2 end
local ml_text = "line1\nline2\nline3\nline4\nline5\nline6"
local mbuf = ImGui.NewTextBuffer(ml_text, 256)
local wbuf = ImGui.NewTextBuffer("a long line that has to be word wrapped several times inside the narrow box\nsecond", 256)
local shortcut_frames = {}
local cb_col, ce_col = G.ImVec4(1, 0, 0, 1), { 0, 0, 0, 1 }
game:GetService("RunService").Stepped:Connect(function()
f += 1
G.ImGui_ImplRoblox.NewFrame()
if f == 3 then click("t") elseif f == 4 then release() end
-- clipboard: tab is filtered out (single line, no AllowTabInput), é is 2 bytes
if f == 6 then ImGui.SetClipboardText("ab\tc\u{e9}"); io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.V, true) end
if f == 7 then io:AddKeyEvent(G.ImGuiKey.V, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
if f == 9 then key(G.ImGuiKey.Escape) end
if f == 11 then click("s", true) elseif f == 12 then release() end
if f == 14 then key(G.ImGuiKey.Escape) end
if f == 16 then click("d", true) elseif f == 17 then release() end
-- multi-line: click, Ctrl+End, type "x", Enter, "y"
if f == 21 then click("m") elseif f == 22 then release() end
if f == 23 then io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.End, true) end
if f == 24 then io:AddKeyEvent(G.ImGuiKey.End, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
if f == 25 then io:AddInputCharacter(string.byte("x")) end
if f == 26 then key(G.ImGuiKey.Enter) end
if f == 28 then io:AddInputCharacter(string.byte("y")) end
if f == 29 then io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.A, true) end -- select all: renders the multi-line selection
if f == 30 then io:AddKeyEvent(G.ImGuiKey.A, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
if f == 31 then key(G.ImGuiKey.Escape) end
-- Shortcut(chord) with default flags/owner
if f == 33 then io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.S, true) end
if f == 34 then io:AddKeyEvent(G.ImGuiKey.S, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
-- drag the ColorButton onto the ColorEdit4
if f == 36 then io:AddMousePosEvent(center("cb")) end
if f == 37 then io:AddMouseButtonEvent(0, true) end
if f == 38 then local x, y = center("cb"); io:AddMousePosEvent(x + 10, y + 5) end
if f == 39 then io:AddMousePosEvent(center("ce")) end
if f == 42 then io:AddMouseButtonEvent(0, false) end
-- word-wrapped multi-line: click, Ctrl+End, type "z"
if f == 45 then click("w") elseif f == 46 then release() end
if f == 47 then io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.End, true) end
if f == 48 then io:AddKeyEvent(G.ImGuiKey.End, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
if f == 49 then io:AddInputCharacter(string.byte("z")) end
ImGui.NewFrame()
ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(300, 200))
ImGui.Begin("T")
if f == 1 then check(ImGui.GetFrameCount() == 1 and ImGui.IsWindowAppearing(), "IsWindowAppearing() on frame 1") end
if f == 2 then check(not ImGui.IsWindowAppearing(), "IsWindowAppearing() on frame 2") end
ImGui.InputText("##t", buf, 64); rects.t = rect()
sf = ImGui.SliderFloat("##s", sf, 0.0, 100.0, "%.0f deg"); rects.s = rect()
di = ImGui.DragInt("##d", di, 1, 0, 100, "%d%%"); rects.d = rect()
-- CalcItemSize takes 'size' by value: the caller's ImVec2 must not be written to
local bsz, csz = ImVec2(0, 0), ImVec2(-1, 40)
ImGui.Button("sz", bsz); check(bsz.x == 0 and bsz.y == 0, "Button wrote size_arg")
ImGui.BeginChild("c", csz); ImGui.EndChild(); check(csz.x == -1 and csz.y == 40, "BeginChild wrote size_arg")
-- drop target rect sticking out of the window clip rect pushes a full-screen clip rect
ImGui.RenderDragDropTargetRectForItem(G.ImRect(-50, -50, 900, 900))
ImGui.End()
ImGui.SetNextWindowPos(ImVec2(320, 10)); ImGui.SetNextWindowSize(ImVec2(300, 200))
ImGui.Begin("M")
ImGui.InputTextMultiline("##m", mbuf, 256, ImVec2(200, ImGui.GetTextLineHeight() * 3)); rects.m = rect()
if f == 30 then
local st = ImGui.GetCurrentContext().InputTextState
check(st.LineCount == 7, "multiline LineCount " .. tostring(st.LineCount))
local cw -- the multi-line child window: cursor on the last line scrolled it
for _, w in ImGui.GetCurrentContext().Windows.Data do if w.Name:find("^M/##m_") then cw = w end end
check(cw ~= nil and cw.Scroll.y > 0, "multiline scroll " .. tostring(cw and cw.Scroll.y))
end
ImGui.InputTextMultiline("##w", wbuf, 256, ImVec2(120, 80), G.ImGuiInputTextFlags.WordWrap); rects.w = rect()
if f == 30 then check(ImGui.GetCurrentContext().InputTextState:HasSelection(), "multiline select all") end
if ImGui.Shortcut(bit32.bor(G.ImGuiMod_Ctrl, G.ImGuiKey.S)) then shortcut_frames[f] = true end
ImGui.End()
ImGui.SetNextWindowPos(ImVec2(10, 220)); ImGui.SetNextWindowSize(ImVec2(300, 150))
ImGui.Begin("D")
ImGui.ColorButton("cb", cb_col); rects.cb = rect()
ImGui.ColorEdit4("##ce", ce_col); rects.ce = rect()
ImGui.End()
-- TextUnformatted() > 2000 bytes (coarse-clipped long text path): each visible line renders only up to its '\n'
ImGui.SetNextWindowPos(ImVec2(320, 220)); ImGui.SetNextWindowSize(ImVec2(300, 150))
ImGui.Begin("L")
local dl = ImGui.GetWindowDrawList()
local v0 = dl.VtxBuffer.Size
ImGui.TextUnformatted(long_text)
local nv = dl.VtxBuffer.Size - v0
if f == 3 then check(nv > 0 and nv <= 14 * 9 * 4, "long TextUnformatted vertices " .. nv) end
-- PushFont() saves the CURRENT font: PopFont() must restore it
local font1 = ImGui.GetFont()
check(font2 ~= font1, "font2 is the default font"); ImGui.PushFont(font2, 0.0); ImGui.PopFont()
if f == 3 then check(ImGui.GetFont() == font1, "PopFont() did not restore the previous font") end
ImGui.End()
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if f == 8 then check(edit_text() == "abc\u{e9}", "paste: '" .. edit_text() .. "'") end
if f == 13 then check(edit_text() == "45", "SliderFloat temp input: '" .. edit_text() .. "'") end
if f == 18 then check(edit_text() == "50", "DragInt temp input: '" .. edit_text() .. "'") end
if f == 30 then check(ImGui.TextBufferToString(mbuf) == ml_text .. "x\ny", "multiline buffer: '" .. ImGui.TextBufferToString(mbuf) .. "'") end
if f == 35 then check(shortcut_frames[33] and not shortcut_frames[32] and not shortcut_frames[34], "Shortcut(Ctrl+S) default args") end
if f == 44 then check(ce_col[1] == 1 and ce_col[2] == 0 and ce_col[3] == 0 and ce_col[4] == 1, ("color drop: %s %s %s %s"):format(ce_col[1], ce_col[2], ce_col[3], ce_col[4])) end
if f == 51 then check(ImGui.TextBufferToString(wbuf):sub(-7) == "secondz", "word-wrap buffer: '" .. ImGui.TextBufferToString(wbuf) .. "'") end
if f == 52 and #failed == 0 then print("FIXES OK") end
end)
+174
View File
@@ -0,0 +1,174 @@
-- Font pieces: ImFontGlyphRangesBuilder, ImFont:CalcWordWrapPosition() vs TextWrapped(), IsGlyphRangeUnused(),
-- stb_decompress + Decode85 via AddFontFromMemoryCompressedBase85TTF() (same glyphs/metrics as the raw TTF).
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
local io = ImGui.GetIO()
local failed = {}
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
-- GlyphRangesBuilder
local builder = G.ImFontGlyphRangesBuilder()
builder:AddText("Hello \u{e9}")
local ranges = G.ImVector()
builder:BuildRanges(ranges)
local got = table.concat(table.move(ranges.Data, 1, ranges.Size, 1, {}), ",")
check(got == "32,32,72,72,101,101,108,108,111,111,233,233,0", "BuildRanges " .. got)
builder:Clear()
builder:AddRanges(io.Fonts:GetGlyphRangesDefault())
builder:AddChar(0x41) -- already in range
ranges = G.ImVector(); builder:BuildRanges(ranges)
check(ranges.Size == 3 and ranges.Data[1] == 0x20 and ranges.Data[2] == 0xFF and ranges.Data[3] == 0, "AddRanges default")
check(builder:GetBit(0x20) and not builder:GetBit(0x1F) and not builder:GetBit(0x100), "GetBit")
-- stb-compressed + base85 ProggyClean.ttf (generated with an stb_compress-format encoder + binary_to_compressed_c's base85)
local B85 = ([==[
7])#######hV0qs#####]h?>#/:jl&Ql#v#[Zre=N=K_o(-(##mQ%##B>A0F_%#g(g/'##r[(##PF6'I</,GM*BiC$v%###s,0%J*RB8O+d68%eKRrQ(.d<BW_*vCL%###E%###:d@UC%)BE#=.>>#'####<_[FH
O97MKO->>#i6YY#i1JuB5eI`[VRUV$=.<T7oH[^ICAS5#T.>>#K####+>00FnH/5`S7g1TS->>#k%S+Hot3>MYJGiTLPUV$>eFVCUwS>#9G:;$n%###Sh(a1aA.;6BE-hAeI1=#au8eZ2D$CI[Mn20Gb?=iMPrIL
DSGkFI5>##Q+`AMB%]`ac<%##CFgn*<WFX:&M'^#3@:B#`2B2#[0b30lOj)#-####.%*=Cvf./LVSiHdaT_n#X$###csu8.r:%)34Dk>$<,;.OOMDgEKnIS7EM9>ZoSAZ$*3)qMQv//L^MkA#wmCAMUnG<-7F>HM
DHw>Pt*p+M[ul=Q<O]CPUoINNUm+p7S<oG76Mqr)twv9Mt0[0#=S&r;QDqk+SEqFR@0p--.iPDN+Y')$PP;vnQbxRMqJQ*OD:H-M#C]GQJ-jBPO&%^Qmus+M3@C58qNO&#sNk,%?$xu#Fle&,W6=G2,Hb;-;wcP$
tPBuuE1V:vm3SML+x0B#/Dabh^KfW-j(.`&V8WQ-2$ZtQQpp5#l,$R#=`i['#l+S#Q*IX#6GMk#op/i#:n*x#tJV0$t#F=$X6iC$1/oh$TrKb$S/kt$&,)O%=q]E%o]n[%6dRd%lGTq%_#`C&$T1_&>Y^Y&2wbq&
TB-K'@IZC'%]#V'Uv'n'[R%1(D6?c(OD1$)e>Lr(Z*24)C5Yb)R/uY)#PPj)uaFu)pEt**@7KY*j<MS*5Nki*SAC?+x/x5+ugwH+;f6w+**;8,V?'+,3Y6W,pGkM,sgEa,%5h7-QbL,-Z8sL-.[r<-r;tI-5hnl-
Cnqc-CX86.QEKF.,hI9.1W`S%mQrV[C4s&#t3&KDU+DvZ)$SpA>O*dskh5??pb?^Y44[W7m@O9&/?o&YNgT?-'aPdsd(-qA0[9_#WMT9AcO#wm%e;e3?oWq/R1,XIghlWnL4mEX_v(R^+$4:A6.*x6Y+w_,ED)`,
PIDRpbf::fN$F.N4Bv:/>80xm6wgLiuWsFXrv)AHt/sl19::#%T1?Ynm49;SFb8/N7n.a><s3T0OpXm(4@4sf)_W/WEcIZ70Ne5_QYMTBLMbT0%t4Tph<HT^;n@*>$P%O)]J/0j$)macqO]pg2V2u$&t@##5f1$#
E@%%#Uqn%#fKb&#v&U'#0WH(#@2<)#Pc/*#a=#+#qnl+#+I`,#;$S-#KTF.#[/:/#l`-0#&;w0#6lj1#oiFJ('Moe2;4HZ$mgfX-b/g/HIt71#DRT<Lq8%dM$bLDNoaVV-Pm^dOJ@6##XaYx')_#&4Za=sQ-,7&4
Oe)>Pn<<>P0]15/$[K<Lrhci9h&@2LaX:9$;$Ne?L*'?76@Vp.%XK<LdcPe6%l^>$?:b1#^QZ`*uqmV-8lt.ZRwbVRqjtH*PhBQ(-t]N-0*aYMiEVq7a=W4#`6_c)$/*JL(L-^(]$wIMlcs(UsvHv,,rbA#/2u]O
hdcHMucWU-k^n:$VGR;oFHnq7e(Q&#uDdE&];bLcqC/^daSxFNtTGw.P3w.Ln1k,O7$22%r]<WS?OY)NJ[vI:DJNGokW_'6Q;I28n8X,Okie;)sJl_fawt-$II`;-sINN$tre+MLFg;-cG,-M<vp%N`tb48]_c6s
:#^#$J?HC$,>2U%qS#n8as:jrQ=$##/)>uuN?Sq7^fV2ivEjJN-MT;-UJ;QMM=6##Cni&O1/Ut'7#[UN>+]K3M@2+8UkW1#k.WUN4DU;-j8(1$)[888aOHpgh<7r7#n8I?%R7p7tGF]u%OtlSE4$##6We'8SdfFI
WVZ>nZ6c8MPhc(.i10p7x0'.=;lxb-4r'E>qC0%%?].e;I#72LclTAO0qD>$Vvnq2h^fW-LL4OiYW-mNFJuH&O(35&85c58@<iv%VwpGMX#$*&WFjp'jZCx$5N:q8/sgd4LhC^++re_8de;m8?37w%RICm8o4wd+
*OhaW?_m.'3XYjM/gh.(xEe58>Y31#_Lx-[?u>q7:5i8K5(;mOM8#=Q'`+%&7'6i#BtxA?0KPM-1PHM&/esr7gC>B$1wGJ(>Q5t7]#:xT:tl.$Tvx;-%&26&`JT58^V_,FWN-c0F####HSI@1K<8FN&?bO'2[IfM
j'YWN,<hn3bqwJN0:kC:(Yf>et`e;)LBf68(24gj1WXW'afQfFv7V.8R02?[LUlDN3cha'&]T&#b96$2*T5s7L5jv[&tsR%3dG2M0.(58=RK=`37;ENkWl9)Y7dp7v_k;fk&Z31P>5r'ZC$M&7&i)<U/5F%<bj/:
b5vAmK4$##&J<j$&6G<QMA&D:Hbkxu.NTkM6ev.:XCF&#iR7S82tuF%k$IAOvjw:$ku0p7sTMBm9pUh$KAuu#5[,qpn%(.*;?>5SkFGm;i'72Ljb]1#+BcK:Fgs.F8`aBNT($28/7*.H1GrvqoTa2`b:EO-=sKj9
crw0#.4D.M(s<A+,DPc=mTGQ:uaAL:Wjp#(R^uw9KvsDN781]&6tgA=_$Dc%.7vcMO>m;51/tW8Y@@S/#1jn8BZT/-Y,lJa-2Ht$i=(HMv'(P8Y>4AuNb6j9hP6(d?^OE23b<Q8)H*nkb$M#1kJAqgUJSk8kK'dk
5#uk%xHA88V`;3;KicD%RE-RLwIsJ%#Et,krIK64v,N/Vn@^5+,(;eMs7jf%Pa64:0nhEYGlk%(I9x9Mk3AN9PV*1#m1OL;Y@0qb]2``]dhsp8HNDLPQ(C-M(NbS85^wek`P@'$fCAV8_hGWSE<?tO(=ZU*EK949
9ux0#2nKA=dT'^#>sR&ZePWV&-=i>RM-']:uc%4;r>8R$tZgSN$fo1#8&n/;%G7@[Jf4x+.bjq7rlBEYk>h-%(X8j9VCxj2OM:/1_>qs7fEt-$n17tQE&9A.S+Ne+3]a:%$MLp7t]Wx'>3Ks7wtpDP.%L)$Q]BDN
NOZf:iskA#j:8T@)BC[.<2N=8c4G`&EW<a&`aQd+s_Pp'a_&6(M@M.$:t1W?4@0#AKhL&OZr_G5Ndap''9nq&@EQkTL0>@$%,m/:PIh)P<e=K8`cI4;5/hu0$qVv[#aKm<Vh;BmVxk4%x3Kn9+Nkxu6Ro94fPQ:8
P_)edQ/G0MPJKU11j?NWE?N(80m^&v+J?J:DRPx^YwdHMNf4-:K3<p^FSfo7kKtWSvQjN9)v[B739$^*i-It]VKVv/>Eq3:+x&'-t0sc;>[:</nIMS2He60Vdig`&6]Hj9QO_&RvTf'&mZ]mBRJ3SabaG'?hIi.$
$./32CCP)NBxoO(9L(c%jxcp7a.-e4Y5n3-qg-a/9c639%wJ+G5X1e&?5V?-a?>Q-;4*88df9dk@]?Y'/ehS@A:R&m2_(:)QYJwTme-LEcuko$4B:T1=^J$&2(+@-]Ziu,ae_-tbUGDN`-Ra$;hhjW/v[lA3pKq^
H<;&(8#DwP3c)/-SoDu7GaHQ:AX=W%+7k,4dQ,N11A>2Bv%03M_/wU&oNi5A)(&3MslOf-.brg=5SHx#7jg]O.F`c)6jK788At1#UfqV-`K<h=Vdb-8U,$90QK@I-E8o3%$grI;4HtdX.Qs$9nga.-c;@v><=ijV
anS,,diDack,uG%(q-&>^Ep&?uTh&UIULu:N*DkDaoK>0T>R@@ho/P+uw<T8#pekXXLZ)GN?Hf,CHuU8&GTpTdx:O3h,p583DQa5frln2-eHrS`V[r7V<$+%Ej0HN,NbN&WQKP9p9qDP-L%r'r(Mj9P+3*GJ*2$/
4_R;-22R2$*LKj:':c_-%4+X.)=a78Q<x0#JP;'PY?=(&BgGp8r;7F%@<PWJYRkH,+GX&#9ePr^o1%/8JE#QUU8jn'S=uPA$TS'J)G=@>^$r?%=FY%8=cFU_Kffo7m-u8NI/5'$NF81.taNq7)?Kq^T+DB)Wg>jV
(ckS*I,1qAb-<p^7Mjf%NcuFNgX12)80EuK,1)g0jPmd;%aZp0ggZdM)j&Z$v>ZQS/U@)2.KDgU#Kk95sor>e@>(QYI<%Z*uB-x0*0K39$uQRX^T]M9_U?^?XD-:8t`MqM0pLP9BmS6(xp?e9dca,bq<3K*^w@T[
bub7%Vu;S8/G5F%0'8rVUDCnNj9%dMllOf-B)(Wf2(73)oGHH;IoZp0Q1Gp7%Wc&#v6D39^P()HBs(2';aFq72v6b6P77'&h2da<TJ_pT`rE)-rf,78tAk)>6Rm3/Zt8#QHo4`)XmW7h'<>e+2NgM9dboLavtx$$
KIK$>1fAoU4NP^0whIt7^iWx''x:Q:`PRB$lhV29)-.K<)dFu3^;mU8UO1S*KTJ/M$=Iw3qi6JN4AFoOalkGNhxNv'HM)?-0(Oh;0:aJsj[lT'5kWm83sdC%hHV7;LSb<S7du7IeKD'mS9JC9Jxr0$t8l>Pxwh9.
L,hs7Elx2)+C30*5d'q7#L?T0W#H^Zths.L2*578H]]^-nO,X&2/Z&FM9%%B8&E#>OgR?RC5'T*;Xx9RhdA7(L01H;Pk1NV87(p7Qd41#fFQ;-xe=C$uf['$C+RPs51b-<8x.4rJ;pt-aA,q7Z6QYq]s/FN6PT[%
gdf68Omx0#HA2s-tGPSIU*@w^dr,X/kJm;?T$dF#IwGP$hn1a*]p7,M.S?M9a$nx0;,Gp74,IpgKNdm'@Bt784N&:DUix*=bc^<Vh+&G[/;dFGR$=I$_Y##PPEV+8=uWd=5KWB%j&h'FPb84=_lDQ(Red/:KD/F.
-o'^#nvkQ8'FjZSZ96Y/o(,,M[jxJjk)lWNSaX%(x11$8SF=[.P8k2B>_sR8w[gJj7*:6:5`)QLb:^i?lw%rMgu>q78aq5M?^w>%IVPp7M8td=/H+s7>&d:;k:&qK=?ex%a*sYPO[+<%&K2;8;fD.$Wk@vPTlC:R
,9R>%-u#6AQ]m&6e32?%THYs%m-n58^<(0E=f2#$wonB$QB]m8R(7mMSpse*)jK29*O$90+$bk)VgB^m/i9R$)8LV4iPpdFBFX)-Q6s58&a3dX&TB59&@_[KAuJmSp13/%8Pp'5D2ZJ:j3:`f+5d+Nc2r.([59p7
6A#'v>J6u'm[2Q8CS+5rbs14$nK%B>m%6Q:`%P1):BA*N1a-A)96D3#1TCt7#IgE#U/VR=`(qQ(U:c58:RRPDZ+,58@`EdtKP>[PQd/,M9u#>9d*-g)&%B@-j<1e(>ih29via.-S?5`8FO:;B&?ch'PMk8@YG@*n
P@st7QGt-$,``wP[ZNC(uTEq70l)?[nS$i$FKgS8jJN9B<kU';c$/32mbBD$4;qeM?5R=MB:K'd@-K^&L.M'Oq4n2$SK%+dwY&(m7/HJ.emw-MLS]0)75,S32BtQ8f3;nqGb=<%Ckb69s#Z&Q^OVv-/vOp7:Mh,F
s2K1:'.vhOt3QA%7rt:(?h+G%2V:<@?O#Qqkl2$;YqE#1:xK,M&%uW)DLo;oA^4e1cnV2([,HKDk)U/dN+dP8:N-Up5d1$1LNP^=EMv^ZR7cP8$Obx%xL>p'CUH-MgU,3D7)D_0'?D_=g>d&QMFJk*7&Pp7Gt3[T
TwY+Y0v2o0DvI^#s-%g:26KE>o_`h*%L^VH1LbC=3;xji-O7r'Lf7KW4N+[&i7PW87O,i8DUjTBFRS?$UG1o*(VRd;4QG1#mpl%$>iraIcDKQ(d%g4([email protected]?^O&gCAr7t$.Ae*b8k&;1=6`d=_d'-05fD.D)E.
uFxZ(d4vQ8.(J(8.%NX-X3RpK:'oLabx4I-1q.:)0.<q0/jF88;0ESCEE8SRN0]u:kpB+$eu*a*2XTe;?@)%IZ[./$HD]BfZ1jk9iCJ32e9R78a=)c<)]<T^'Kc'@lKLKE,LNn%%cl-MpehT$W2`l2b'nj959tT'
1wv*$&9#[JNAC]&[mtvPGwA5;ckQdb`;]R$I&gjDXVfh<NTX1#a8T;-*F:`$qqu,MVqgZ$R(*@II4?uuW%QW@4LoKYVb%R8tks-$HG<f;^31ObK;lR(U@k+MLa'VIt)LE%IrM7)wU:EN&W[39ijMG%#w4g:,Xw0#
w0[588iGJ$ps_>.,umo%x+B7$S)0p71m99pnERN93)9I$)#(IM_Yd>#6x;XCg]5<-3efW85,/n1#S]C'qqvcD2n4`8u9j<-E`Y%&`Qhg*tt2e$N$t49<#oW&^a*@,$^^dk7xK)$GJP/M>KO0&ND_j9>>P&#JuF.3
S<r>I:OPE*XF>dOo#P-(I7Vj;vrlm968R,tB,cS(]FeW&&O`9$-L-5#aS-Ak*o#iSuvYmTPpq9&E*n&%lBap7aS=r7jnP7Ah-]pK>xo_N]mjvPjtJ.2/*'W$4BFQ8bN((&WpULN`$^3i'@+G%.[#.$_6>3)jr[PA
&Wp5Uk_uk%Hk@bNH`(S%T)p/MdebV$4iQr7(QMG%k%8bRfU/L.bn/mSCf>SI$`G%h9JSpB%>xsF,^-Ehgn*Q8&j]K<l(Md8m/9MmPZ4j`a4K2`eI,H:m*Zeohdc,X+fTuYcjEE%M^5t7YsvK3FWHI9$lM4#0*joT
?tM56R'Aq7$d95#n3nAI&'cm3Y(L-MnV#x'qj+l+liD.-8W;v>@wMQh=6)m$1_b-;I?Q5;7>`*6lg3G;sv&:`[JLN9w<gPqa/S/Mx@aJ%NNa,MG>gr'mKDKNrkdkasN/x0,l$q71^?Xh*X[8^PL*q%6cv58_S*1#
bX`0MITc080x^@ilsnlf[L7I%(3mLC(BZE5+?,P@)$+1#DqsJaN-U:/-u?68)p<9j7E%>i?Ia;-kH/D,v(mn8=0'.$Nu49@Bm*1#XYs5CvbQa86j-bS`c5m8RiE1#4l^&#pCa$UOs068Z1-E5RhYl$Oh%TW*)LTA
.#$]UnM;TK9(Dl%8`?6B<MiRWdaTl9'OGG.SAd[IOMvu>6E>WgZ3of$O1Up@XbS2L[OtL:aqtqK:M2j*_@-:)LF5F%sd40:3Fr_&]?'k9W@dF<A]r<J?&]lAL2j&-ntJ<(=skq7w^+Ls<q9et?<a^%+$Y98_&Zob
vsUqpARc=<ROu7c=ebbOHwO/6Pm@*Ww0]%X*io?<8IQ<iLwrMg?r?eO%E:^-aB#KWu<7b'tRLdt#'-;?A1^FEMk82bPU#-MT>uw&hMM8Ia^vr'sj#j1>f:^OuVh29)g09Tq-.78(S%?7NCU29fbbgNaEJ)<jB'4r
(OSP3WS698I9B.$?KS58QV`g$p=@`HW?U/5S&64;1n%:>7rU2`cGs_*,K)1#FCR(Q=Q7i8sDwUjLe.0M2('B$dwW88%vrv@bNOG%K8tMB`6XF-:/::;-:`6r0U=0$``Xp9Knp*-cUpeHl%w,;s+e<_3cs?-#s&r0
1B^]02M(wYNshr00dW_ZG]0a;Gx4ND*&9;-HM%VqIL6GDsDZki/S0K)KF,:pgT4,%4K]dM*x8h8WVl6`g#gG$mhmF,,i2v*wBS1MFM0`&,:PP:[J'LPk%']h5(Wxd>i9UXuQk&18(&H;%Ug'hT6S</?xNx1fa5u7
DW2QL',t`-Cx#okPutL<%cY'2pP)xPGHHiS^x6Z:R91ObU-iT9O(4m'+%2a*N9pP9(B.-bX%`XNve$U$Bht]=h6rJW<3VM%_j/'Q_7M3,ej5/-Ncf/sSl1]M(Q:['>v_Q93J04r7<,vNP%2d97xbEgJKQGVgB#r3
FO4;89*^EfckAhDPC)c<6v690M?r&(GDc^$Ki+58v&LnF)iRTRpw<8Up^,(dPss3,0Z)kT#1i]$-KIZ7LI)3S`ig1(p^^URhRi*N,%$c4L4udO(jG9%[vg2V7I(e&NY<dX,WKw#_&Yj9>hMBQ4T:r2`e0l:lJB$h
.`Pg+e]wY%jfNf$)']6K8lDKaC90I.p<3q7dQtA`Q`5?`(qQvc_6b,3O3)9gwixH-]$SR8vjMhdwMD$[V3xR&vCIpT$Vbk1f=RRblisvHe#Zq9Ske(9iR6XLQES29JsZIM-2>rDjiGr^89KO0-^'q718r_&61D58
w+r_&.lCW/6iG<-`*wQ%8DQ^OmBjE%9X`E.1EDS@#j/q'J5x8-cxfJssL4($Uflq7hxad=4O:C4K9+hER0^F9G+UQc8W:uQ&/0<A7t3fWC[J90AEOn%&:+kCUYXjd*c-^O.<(d%&0)1#XrV'POoOS$AX+q8IZ4)6
hxEFM,hSgN0aEW+x/;N3xJk0&j`S.;edM2i9D+*0GJlB$x:(29wPX&#?l%oS.cb&#`i[`*lQ/,MH@-##FEV6NTF6##$l84N1w?AO>'IAOl%4a*0Y^GM#RcGM9#ult_Ks$#B@`T.1rC$#`@`T.,Mc##T@`T.74i$#
WrG<-N2:W.2`($#irG<-<q@u-?Wd`*eC;,M6WYdM6WYdM*?`T.HEX&#`YYdM6WYdM6WYdM%w@(#.E-(#cPc-NZxtk24Y($Jt1^oD0ZKKFg`]oD+,2eGc:@bHcFx.>@nuc<)0o6D]bdUCFdg+>?Wae3;BR12A::MB
h]8;-?(DYGUq`uGNDw1B];8;-SHpw'r?Z.#K0f-#=ic+#E<x-#ZZO.#L)1/#wtu+#F:F&#;)1/#O<x-#YSq/#(ZO.#wc-2MVC/$$;^?w^Gm/aNs*p+M&fsD#*J(v#.c_V$2%@8%6=wo%:UWP&>n82'B0pi'FHPJ(
Ja1,)N#ic)R;ID*VS*&+Zla]+_.B>,cF#v,_REmh1W@`$0u68%47no%8ONP&<h/2'@*gi'DBGJ(HZ(,)Ls_c)P5@D*TMw%+XfW]+](9>,a@pu,eXPV-iq18.m3io.qKIP/ud*20#'bi0'?BJ19if,=(N^o232;D3
7Jr%4;cR]4?%4>5C=ku5GUKV6Kn,87O0do7SHDP8Wa%29[#]i9`;=J:dSt+;hlTc;l.6D<pFm%=t_M]=xw.>>&:fu>*RFV?.k'8@2-_o@6E?PA:^v1Bikur-m-VS.qE75/u^nl/#wNM0'90/1+Qgf1/jGG23,))3
7D``3;]@A4?uwx4C7XY5GO9;6Khpr6O*QS7hPn:ZKe0GMgc>c`Mq&Sn8h?YPLS-JUrBB`WH+,AO,Wm+MTm6fqOR(>PCeG`NqHn@k_;LfUox3creaQ(st)7VZ`5Quc4R>MBmeu:QS(f7eQfi:di++PfiuIoe1#Don
^w>igqOb1g:-r+DPn?VQ$(_.hbS8GDRI]1pm*m(EM(1DER=L`E2m(GVYq)>G['EYGbKAVHdW]rH0%]4ol2u4Jn>:PJsSUlJ(UFcVwl6MK%53JLTUxLpbwnWH#N#hF@0*K2,4IL2fCd<-gCd<-hCd<-iCd<-jCd<-
kCd<-lCd<-mCd<-nCd<-@Dd<-ADd<-BDd<-CDd<-DDd<-EJ)X-v0P]'Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2TmOh2Y#oTC$NXVCThmfGY4&>-K*IT-K*IT-K*IT-
K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-18ZI'Et,gjm,(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DP+-XlC#m8Atkt>Ns
]==]):gsub("\n", "")
local raw_data, raw_size = G.ImStd.ImFileLoadToMemory("fonts/ProggyClean.ttf", "rb")
local raw = io.Fonts:AddFontFromMemoryTTF(raw_data, raw_size, 13.0)
local comp = io.Fonts:AddFontFromMemoryCompressedBase85TTF(B85, 13.0)
check(comp ~= nil, "AddFontFromMemoryCompressedBase85TTF returned nil")
local comp_data = comp.Sources.Data[1].FontData
local same = comp.Sources.Data[1].FontDataSize == raw_size
for n = 1, raw_size do if not same then break end same = comp_data[n] == raw_data[n] end
check(same, "decompressed bytes differ from fonts/ProggyClean.ttf")
local TEXT = "The quick brown fox jumps over the lazy dog again and again"
local W = 100
local f = 0
game:GetService("RunService").Stepped:Connect(function()
f += 1
G.ImGui_ImplRoblox.NewFrame()
ImGui.NewFrame()
ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(400, 300))
ImGui.Begin("Fonts")
ImGui.PushFont(raw, 13.0)
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + W)
ImGui.TextWrapped(TEXT)
local wrapped_h = ImGui.GetItemRectSize().y
ImGui.PopTextWrapPos()
local line_h = ImGui.GetTextLineHeight()
ImGui.PopFont()
if f <= 3 then
ImGui.PushFont(comp, 13.0)
ImGui.Text("Hello \u{e9}")
ImGui.PopFont()
end
ImGui.End()
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if f == 3 then
-- CalcWordWrapPosition(): same line breaks as TextWrapped(). ProggyClean 13px = 7px per char -> 14 chars per 100px line
local te = #TEXT + 1
local s, lines, breaks = 1, 0, {}
while s < te do
local e = raw:CalcWordWrapPosition(13.0, TEXT, s, te, W)
lines += 1; table.insert(breaks, e)
s = e
while s < te and TEXT:sub(s, s) == " " do s += 1 end -- ImTextCalcWordWrapNextLineStart(): skip blanks
end
check(breaks[1] == 10, "first wrap after 'The quick': " .. tostring(breaks[1])) -- "The quick" | "brown fox" | ...
check(TEXT:sub(11, breaks[2] - 1) == "brown fox", "second line '" .. TEXT:sub(11, breaks[2] - 1) .. "'")
check(wrapped_h == lines * line_h, ("TextWrapped height %s vs %d lines * %s"):format(wrapped_h, lines, line_h))
-- Compressed font: same glyphs and metrics as the raw TTF
local br, bc = raw:GetFontBaked(13.0), comp:GetFontBaked(13.0)
check(br.Ascent == bc.Ascent and br.Descent == bc.Descent, "ascent/descent")
for _, c in { 0x48, 0x65, 0x6C, 0xE9, 0x41, 0x7E } do
local gr, gc = br:FindGlyph(c), bc:FindGlyph(c)
check(gc.Visible and gc.X1 > gc.X0 and gc.Y1 > gc.Y0, "compressed glyph empty " .. c)
check(gr.AdvanceX == gc.AdvanceX and gr.X0 == gc.X0 and gr.Y0 == gc.Y0 and gr.X1 == gc.X1 and gr.Y1 == gc.Y1, "glyph metrics " .. c)
end
local str = "Hello \u{e9}"
local sr, sc = raw:CalcTextSizeA(13.0, 1e30, 0, str, 1, #str + 1), comp:CalcTextSizeA(13.0, 1e30, 0, str, 1, #str + 1)
check(sr.x == sc.x and sr.y == sc.y and sc.x > 0, "CalcTextSizeA")
check(not comp:IsGlyphRangeUnused(0, 255) and comp:IsGlyphRangeUnused(0x4E00, 0x9FFF), "IsGlyphRangeUnused")
-- AddRemapCodepoint(): '$' loads the 'A' glyph (applies to glyphs loaded afterwards: new 20px baked)
comp:AddRemapCodepoint(0x24, 0x41)
local b20 = comp:GetFontBaked(20.0)
local gd, ga = b20:FindGlyph(0x24), raw:GetFontBaked(20.0):FindGlyph(0x41)
check(gd.X0 == ga.X0 and gd.Y0 == ga.Y0 and gd.X1 == ga.X1 and gd.Y1 == ga.Y1, "AddRemapCodepoint")
-- RemoveCustomRect()
local atlas = io.Fonts
local id = atlas:AddCustomRect(8, 8)
check(atlas:GetCustomRect(id, G.ImFontAtlasRect()) == true, "AddCustomRect")
atlas:RemoveCustomRect(id)
check(not atlas:GetCustomRect(id, G.ImFontAtlasRect()), "RemoveCustomRect")
-- RemoveFont()
local nfonts, nsrc = atlas.Fonts.Size, atlas.Sources.Size
atlas:RemoveFont(comp)
check(atlas.Fonts.Size == nfonts - 1 and atlas.Sources.Size == nsrc - 1 and not atlas.Fonts:contains(comp), "RemoveFont")
-- ImFontAtlasTextureBlockFill(), ImTextureDataGetXXXName()
local tex = G.ImTextureData(); tex:Create(G.ImTextureFormat.RGBA32, 4, 4)
G.ImFontAtlasTextureBlockFill(tex, 1, 2, 2, 1, G.IM_COL32(1, 2, 3, 4))
local px = table.concat(tex.Pixels, ",", 33, 48)
check(px == "0,0,0,0,1,2,3,4,1,2,3,4,0,0,0,0" and tex.Pixels[29] == 0 and tex.Pixels[53] == 0, "TextureBlockFill " .. px)
check(G.ImTextureDataGetStatusName(tex.Status) == "WantCreate" and G.ImTextureDataGetFormatName(tex.Format) == "RGBA32", "texture names")
end
if f == 5 and #failed == 0 then print("FONTS OK") end
end)
+301
View File
@@ -0,0 +1,301 @@
-- Small upstream helpers: ImGuiInputTextState selection API + ReloadUserBuf*, ImGuiInputTextCallbackData:SetSelection, ImDrawList AddImageQuad/PrimQuadUV/
-- CloneOutput, ImDrawData DeIndexAllBuffers/ScaleClipRects, ImBitVector/ImBitArray*, ImGuiPackedDate, ImParseFormatSanitizeForPrinting, drag cancel (ShortcutsForCancel),
-- log decorations, ImGuiDataType.S64/U64, BringWindowToDisplayBehind, ImGuiSizeCallbackData
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
local io = ImGui.GetIO()
local failed = {}
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
-- Pure helpers
check(G.ImParseFormatSanitizeForPrinting("%.3f%%", nil, 32) == "%.3f", "sanitize %.3f%%")
check(G.ImParseFormatSanitizeForPrinting("%'_5.2f units", nil, 32) == "%5.2f", "sanitize stb flags: " .. G.ImParseFormatSanitizeForPrinting("%'_5.2f units", nil, 32))
local d = G.ImGuiPackedDate(20260315)
check(d:IsValid() and d:Unpack() == 20260315, "PackedDate round trip " .. d:Unpack())
d:SubtractMonths(3)
check(d:Unpack() == 20251215, "PackedDate SubtractMonths " .. d:Unpack())
check(not G.ImGuiPackedDate():IsValid() and G.ImGuiPackedDate():Unpack() == 0, "PackedDate empty")
local bv = G.ImBitVector()
bv:Create(70)
check(bv.Storage.Size == 3, "ImBitVector size")
bv:SetBit(1); bv:SetBit(70); bv:SetBit(33)
check(bv:TestBit(1) and bv:TestBit(70) and bv:TestBit(33) and not bv:TestBit(2) and not bv:TestBit(69), "ImBitVector SetBit/TestBit")
bv:ClearBit(33)
check(not bv:TestBit(33) and bv:TestBit(70), "ImBitVector ClearBit")
local arr = {}
G.ImBitArrayClearAllBits(arr, 96)
G.ImBitArraySetBitRange(arr, 3, 70) -- [3..70)
local ok_range = true
for n = 1, 96 do if G.ImBitArrayTestBit(arr, n) ~= (n >= 3 and n < 70) then ok_range = false end end
check(ok_range, "ImBitArraySetBitRange")
check(G.ImBitArrayGetStorageSizeInBytes(70) == 12, "ImBitArrayGetStorageSizeInBytes")
check(G.ImModPositive(-1, 5) == 4 and G.ImRoundSigned64(-1.5) == -2 and G.ImCharIsXdigitA(string.byte("F")), "math helpers")
local buf = ImGui.NewTextBuffer("hello world", 64)
local cbuf = ImGui.NewTextBuffer("abcdef", 64)
local cb_calls = 0
local function cb(data)
cb_calls += 1
if cb_calls == 1 then data:SetSelection(1, 3) end
return 0
end
local df, rect_df = 10.0, nil
local s64_v = 2 ^ 40
local f = 0
local function center(r) return (r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2 end
local function vtx(dl, i) -- 1-based vertex
local b, o = dl.VtxBuffer.Buf, (i - 1) * G.VTX_STRIDE
return buffer.readf64(b, o), buffer.readf64(b, o + 8), buffer.readf64(b, o + 16), buffer.readf64(b, o + 24), buffer.readu32(b, o + 32)
end
game:GetService("RunService").Stepped:Connect(function()
f += 1
G.ImGui_ImplRoblox.NewFrame()
-- drag + Escape cancels and reverts (ShortcutsForCancel)
if f == 20 then io:AddMousePosEvent(center(rect_df)) end
if f == 21 then io:AddMouseButtonEvent(0, true) end
if f == 22 then local x, y = center(rect_df); io:AddMousePosEvent(x + 40, y) end
if f == 24 then io:AddKeyEvent(G.ImGuiKey.Escape, true) end
if f == 25 then io:AddKeyEvent(G.ImGuiKey.Escape, false) end
if f == 26 then io:AddMouseButtonEvent(0, false) end
ImGui.NewFrame()
ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(400, 300))
ImGui.Begin("H")
if f == 2 then ImGui.SetKeyboardFocusHere() end
ImGui.InputText("##t", buf, 64)
local id = ImGui.GetItemID()
local st = ImGui.GetInputTextState(id)
if f == 5 then
check(st ~= nil, "InputText active")
if st then
st:SetSelection(3, 6)
check(st:GetSelectionStart() == 3 and st:GetSelectionEnd() == 6 and st:GetCursorPos() == 6 and st:HasSelection(), "SetSelection/Get*")
st:ClearSelection()
check(st:GetSelectionStart() == 6 and st:GetSelectionEnd() == 6 and not st:HasSelection(), "ClearSelection")
check(st:GetPreferredOffsetX() == -1, "GetPreferredOffsetX")
-- modify the user buffer externally then reload
local s = "bye"; for i = 1, #s do buf[i] = string.byte(s, i) end; buf[#s + 1] = 0
st:ReloadUserBufAndSelectAll()
end
end
if f == 7 and st then
check(G.ImGui._ByteArrayToString(st:GetText(), 1, st.TextLen + 1) == "bye", "ReloadUserBufAndSelectAll text")
check(st:GetSelectionStart() == 1 and st:GetSelectionEnd() == 4, ("ReloadUserBufAndSelectAll sel %d..%d"):format(st:GetSelectionStart(), st:GetSelectionEnd()))
st:SetSelection(2, 3); st:ReloadUserBufAndKeepSelection()
end
if f == 9 and st then
check(st:GetSelectionStart() == 2 and st:GetSelectionEnd() == 3, "ReloadUserBufAndKeepSelection")
st:ReloadUserBufAndMoveToEnd()
end
if f == 11 and st then
check(st:GetCursorPos() == 4 and not st:HasSelection(), "ReloadUserBufAndMoveToEnd " .. st:GetCursorPos())
end
-- callback SetSelection (0-based callback positions -> 1-based stb)
if f == 12 then ImGui.SetKeyboardFocusHere() end
ImGui.InputText("##c", cbuf, 64, G.ImGuiInputTextFlags.CallbackAlways, cb)
if f == 15 then
local cst = ImGui.GetInputTextState(ImGui.GetItemID())
check(cst ~= nil and cb_calls > 0 and cst:GetSelectionStart() == 2 and cst:GetSelectionEnd() == 4, "callback SetSelection " .. (cst and (cst:GetSelectionStart() .. ".." .. cst:GetSelectionEnd()) or "nil"))
end
df = ImGui.DragFloat("##df", df, 1.0)
local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); rect_df = { ImVec2(a.x, a.y), ImVec2(b.x, b.y) }
if f == 23 then check(df ~= 10.0, "drag changed value " .. df) end
if f == 25 then check(df == 10.0 and ImGui.GetActiveID() == 0, "Escape reverts drag " .. df) end
if f == 30 then
local dl = ImGui.GetWindowDrawList()
local v0, i0 = dl.VtxBuffer.Size, dl.IdxBuffer.Size
local tex = G.ImTextureRef(1234)
dl:AddImageQuad(tex, ImVec2(10, 10), ImVec2(50, 12), ImVec2(48, 60), ImVec2(8, 58), ImVec2(0, 0), ImVec2(1, 0), ImVec2(1, 1), ImVec2(0, 1), 0xFF00FF00)
check(dl.VtxBuffer.Size == v0 + 4 and dl.IdxBuffer.Size == i0 + 6, "AddImageQuad counts")
local x, y, u, v, col = vtx(dl, v0 + 3)
check(x == 48 and y == 60 and u == 1 and v == 1 and col == 0xFF00FF00, ("PrimQuadUV vertex %s %s %s %s %x"):format(x, y, u, v, col))
local idx = dl.IdxBuffer.Data
check(idx[i0 + 1] == v0 + 1 and idx[i0 + 5] == v0 + 3 and idx[i0 + 6] == v0 + 4, "PrimQuadUV indices")
local cmin, cmax = dl:GetClipRectMin(), dl:GetClipRectMax()
check(cmax.x > cmin.x and cmax.y > cmin.y, "GetClipRectMin/Max")
local clone = dl:CloneOutput()
check(clone.VtxBuffer.Size == dl.VtxBuffer.Size and clone.IdxBuffer.Size == dl.IdxBuffer.Size and clone.CmdBuffer.Size == dl.CmdBuffer.Size
and clone.VtxBuffer.Buf ~= dl.VtxBuffer.Buf and clone.CmdBuffer.Data[1] ~= dl.CmdBuffer.Data[1], "CloneOutput copies buffers")
local cx = vtx(clone, v0 + 3)
check(cx == 48, "CloneOutput vertex data")
local dd = G.ImDrawData()
dd.CmdLists:push_back(clone)
local n_idx = clone.IdxBuffer.Size
local ex, ey = vtx(clone, clone.IdxBuffer.Data[n_idx])
dd:DeIndexAllBuffers()
check(clone.IdxBuffer.Size == 0 and clone.VtxBuffer.Size == n_idx and dd.TotalVtxCount == n_idx and dd.TotalIdxCount == 0, "DeIndexAllBuffers counts")
local lx, ly = vtx(clone, n_idx)
check(lx == ex and ly == ey, "DeIndexAllBuffers vertex data")
local cr = clone.CmdBuffer.Data[1].ClipRect
local x1, y1, z1, w1 = cr.x, cr.y, cr.z, cr.w
dd:ScaleClipRects(ImVec2(2, 3))
local cr2 = clone.CmdBuffer.Data[1].ClipRect
check(cr2.x == x1 * 2 and cr2.y == y1 * 3 and cr2.z == z1 * 2 and cr2.w == w1 * 3 and dl.CmdBuffer.Data[1].ClipRect.z == z1, "ScaleClipRects")
end
if f == 31 then
local g = ImGui.GetCurrentContext()
local style = ImGui.GetStyle()
local window = ImGui.GetCurrentWindow()
check(G.ImGuiKey["0"] == G.ImGuiKey.K0 and G.ImGuiKey["9"] == G.ImGuiKey.K9, "ImGuiKey digit aliases")
-- Style vars (enum values must match GStyleVarsInfo order)
local sv = G.ImGuiStyleVar
local b1, b2, b3 = style.SeparatorSize, style.SeparatorTextBorderSize, style.DockingSeparatorSize
ImGui.PushStyleVar(sv.SeparatorSize, 5); ImGui.PushStyleVar(sv.SeparatorTextBorderSize, 6); ImGui.PushStyleVar(sv.DockingSeparatorSize, 7)
check(style.SeparatorSize == 5 and style.SeparatorTextBorderSize == 6 and style.DockingSeparatorSize == 7, "PushStyleVar SeparatorSize/SeparatorTextBorderSize/DockingSeparatorSize")
ImGui.PushStyleVar(sv.SeparatorTextPadding, ImVec2(3, 4))
check(style.SeparatorTextPadding.x == 3 and style.SeparatorTextPadding.y == 4, "PushStyleVar SeparatorTextPadding")
ImGui.PopStyleVar(4)
check(style.SeparatorSize == b1 and style.SeparatorTextBorderSize == b2 and style.DockingSeparatorSize == b3, "PopStyleVar restores")
-- Log flags
ImGui.LogToBuffer()
check(g.LogEnabled and g.LogFlags == G.ImGuiLogFlags.OutputBuffer, "LogToBuffer sets ImGuiLogFlags.OutputBuffer")
ImGui.Text("logged")
check(string.find(g.LogBuffer:c_str(), "logged", 1, true) ~= nil, "LogToBuffer captures text")
ImGui.LogFinish()
check(g.LogFlags == G.ImGuiLogFlags.None and not g.LogEnabled, "LogFinish clears LogFlags")
-- Combo preview (no ImGuiComboFlags.CustomPreview needed since 1.93)
if ImGui.BeginCombo("##cb", "") then ImGui.EndCombo() end
local pd = g.ComboPreviewData
local work_max_x, content_max_x = window.WorkRect.Max.x, window.ContentRegionRect.Max.x
local opened = ImGui.BeginComboPreview()
check(opened and pd.WithinPreview and window.WorkRect.Max.x == pd.PreviewRect.Max.x - style.FramePadding.x and window.ContentRegionRect.Max.x == window.WorkRect.Max.x, "BeginComboPreview sets WorkRect")
if opened then
ImGui.Text("preview")
ImGui.EndComboPreview()
end
check(not pd.WithinPreview and window.WorkRect.Max.x == work_max_x and window.ContentRegionRect.Max.x == content_max_x, "EndComboPreview restores WorkRect")
if ImGui.BeginCombo("##cb2", "x", G.ImGuiComboFlags.NoPreview) then ImGui.EndCombo() end
check(not ImGui.BeginComboPreview(), "BeginComboPreview with NoPreview returns false")
-- ListClipper in a table: ImGuiListClipperFlags.NoSetTableRowCounters
local rows = {}
ImGui.SetNextWindowPos(ImVec2(500, 10)); ImGui.SetNextWindowSize(ImVec2(300, 400))
ImGui.Begin("Clip")
for pass = 1, 2 do
if ImGui.BeginTable("##clip" .. pass, 1, G.ImGuiTableFlags.ScrollY, ImVec2(0, 60)) then
local clipper = G.ImGuiListClipper()
if pass == 2 then clipper.Flags = G.ImGuiListClipperFlags.NoSetTableRowCounters end
clipper:Begin(1000)
while clipper:Step() do
for i = clipper.DisplayStart, clipper.DisplayEnd - 1 do ImGui.TableNextRow(); ImGui.TableNextColumn(); ImGui.Text("%d", i) end
end
clipper:End()
rows[pass] = ImGui.GetCurrentTable().CurrentRow
local tbl = ImGui.GetCurrentTable()
ImGui.EndTable()
if pass == 1 then ImGui.SetNextItemOpen(true); ImGui.DebugNodeTable(tbl) end -- DebugNodeTableGetSizingPolicyDesc()
end
end
ImGui.End()
check(rows[1] ~= nil and rows[2] ~= nil and rows[1] >= 990 and rows[2] < 100, ("clipper row counters %s / NoSetTableRowCounters %s"):format(tostring(rows[1]), tostring(rows[2])))
-- .ini LastUsed dates + CleanupIniSettings()
local pio = ImGui.GetPlatformIO()
pio.Platform_SessionDate = 20261015
g.SessionDate = G.ImGuiPackedDate(pio.Platform_SessionDate) -- (normally done by NewFrame())
io.ConfigIniSettingsAutoDiscardMonths = 6
ImGui.LoadIniSettingsFromMemory("[Window][Old]\nPos=1,1\nLastUsed=20250101\n\n[Window][NoDate]\nPos=2,2\n\n[Window][Recent]\nPos=3,3\nLastUsed=20260901\n\n")
io.ConfigIniSettingsAutoDiscardMonths = 0
local function ws(name) for _, st in g.SettingsWindows.Data do if st.Name == name then return st end end; return nil end
check(ws("Old").WantDelete and ws("NoDate").WantDelete and not ws("Recent").WantDelete and ws("Recent").LastUsedDate == 20260901, "ConfigIniSettingsAutoDiscardMonths discards old/undated entries")
local args = G.ImGuiSettingsCleanupArgs()
args.SetCurrentSessionDateToAll = true
args.TypeHashFilter = G.ImHashStr("Window")
ImGui.CleanupIniSettings(args)
check(ws("Recent").LastUsedDate == 20261015, "CleanupIniSettings SetCurrentSessionDateToAll")
local ini = ImGui.SaveIniSettingsToMemory()
check(not string.find(ini, "[Window][Old]", 1, true) and string.find(ini, "[Window][Recent]\nPos=3,3\nCollapsed=0\nLastUsed=20261015\n", 1, true) ~= nil
and string.find(ini, "[Window][H]\nPos=10,10\nSize=400,300\nCollapsed=0\nLastUsed=20261015\n", 1, true) ~= nil, "LastUsed written to .ini:\n" .. ini)
io.ConfigIniSettingsSaveLastUsedDate = false
check(not string.find(ImGui.SaveIniSettingsToMemory(), "LastUsed", 1, true), "ConfigIniSettingsSaveLastUsedDate = false")
io.ConfigIniSettingsSaveLastUsedDate = true
pio.Platform_SessionDate = 0
-- Log decorations: Selectable/Button/Checkbox/MenuItem shortcut
ImGui.LogToBuffer()
ImGui.Selectable("sel item")
ImGui.Button("btn")
ImGui.Checkbox("chk", true)
ImGui.MenuItem("mi", "Ctrl+M")
local log = g.LogBuffer:c_str()
ImGui.LogFinish()
check(string.find(log, "[sel item]", 1, true) == nil and string.find(log, "sel item", 1, true) ~= nil, "Selectable logs plain label: " .. log)
check(string.find(log, "[ btn ]", 1, true) ~= nil and string.find(log, "[x]", 1, true) ~= nil and string.find(log, "mi ( Ctrl+M )", 1, true) ~= nil, "LogSetNextTextDecoration [ ]: " .. log)
-- DragScalar S64/U64 (doubles) + DataTypeFormatString
local v64, ch = ImGui.DragScalar("##s64", G.ImGuiDataType.S64, 2 ^ 40, 1.0)
check(v64 == 2 ^ 40 and ch == false, "DragScalar S64 idle")
local b64 = {}
local n = ImGui.DataTypeFormatString(b64, 64, G.ImGuiDataType.S64, -(2 ^ 40), "%lld")
check(G.ImGui._ByteArrayToString(b64, 1, n + 1) == "-1099511627776", "DataTypeFormatString S64")
n = ImGui.DataTypeFormatString(b64, 64, G.ImGuiDataType.U64, 18446744073709551615, "%llu")
check(G.ImGui._ByteArrayToString(b64, 1, n + 1) == "18446744073709551615", "DataTypeFormatString U64 max")
n = ImGui.DataTypeFormatString(b64, 64, G.ImGuiDataType.S32, -5, "%X")
check(G.ImGui._ByteArrayToString(b64, 1, n + 1) == "FFFFFFFB", "DataTypeFormatString S32 hex")
local tb = ImGui.NewTextBuffer("-123456789012", 64)
local r64, c64 = ImGui.DataTypeApplyFromText(tb, G.ImGuiDataType.S64, 0, "%lld")
check(r64 == -123456789012 and c64, "DataTypeApplyFromText S64 " .. tostring(r64))
check(ImGui.DataTypeApplyOp(G.ImGuiDataType.S64, 43, 2 ^ 62, 2 ^ 62) == 9223372036854775807, "DataTypeApplyOp S64 clamp")
check(G.ImGuiDataType.Float == 8 and G.ImGuiDataType.COUNT == 12 and ImGui.DataTypeGetInfo(G.ImGuiDataType.Float).Name == "float", "ImGuiDataType layout")
end
-- DragScalar S64: drag with the mouse
local s64_rect
s64_v = ImGui.DragScalar("##drag_s64", G.ImGuiDataType.S64, s64_v, 1.0)
do local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); s64_rect = { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end
if f == 33 then io:AddMousePosEvent(center(s64_rect)) end
if f == 34 then io:AddMouseButtonEvent(0, true) end
if f == 35 then local x, y = center(s64_rect); io:AddMousePosEvent(x + 30, y) end
if f == 37 then io:AddMouseButtonEvent(0, false); check(s64_v > 2 ^ 40 and s64_v == math.floor(s64_v), "DragScalar S64 drag " .. s64_v) end
ImGui.End()
-- BringWindowToDisplayBehind
if f == 38 then
local g = ImGui.GetCurrentContext()
local wa, wb, wc = ImGui.FindWindowByName("WA"), ImGui.FindWindowByName("WB"), ImGui.FindWindowByName("WC")
local function idx(w) return ImGui.FindWindowDisplayIndex(w) end
ImGui.BringWindowToDisplayFront(wa); ImGui.BringWindowToDisplayFront(wb); ImGui.BringWindowToDisplayFront(wc)
local n0 = g.Windows.Size
ImGui.BringWindowToDisplayBehind(wc, wa) -- move forward-placed window behind: WC WA WB
check(idx(wc) + 1 == idx(wa) and idx(wa) + 1 == idx(wb) and g.Windows.Size == n0, ("BringWindowToDisplayBehind back %d %d %d"):format(idx(wa), idx(wb), idx(wc)))
ImGui.BringWindowToDisplayBehind(wa, wb) -- already directly behind: unchanged
check(idx(wc) + 1 == idx(wa) and idx(wa) + 1 == idx(wb), "BringWindowToDisplayBehind no-op")
ImGui.BringWindowToDisplayBehind(wc, wb) -- WA WC WB
check(idx(wa) + 1 == idx(wc) and idx(wc) + 1 == idx(wb) and g.Windows.Size == n0, ("BringWindowToDisplayBehind fwd %d %d %d"):format(idx(wa), idx(wb), idx(wc)))
end
for _, name in { "WA", "WB", "WC" } do
ImGui.SetNextWindowPos(ImVec2(820, 10)); ImGui.SetNextWindowSize(ImVec2(80, 60))
ImGui.Begin(name); ImGui.End()
end
-- Size callback (Step + AspectRatio)
ImGui.SetNextWindowPos(ImVec2(10, 420))
if f <= 3 then ImGui.SetNextWindowSize(ImVec2(237, 160)) end
ImGui.SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(G.FLT_MAX, G.FLT_MAX), function(data)
local step = data.UserData
check(data.Pos ~= nil and data.CurrentSize ~= nil and not rawequal(data.DesiredSize, data.CurrentSize), "ImGuiSizeCallbackData fields")
data.DesiredSize = ImVec2(math.floor(data.DesiredSize.x / step + 0.5) * step, math.floor(data.DesiredSize.y / step + 0.5) * step)
end, 100.0)
ImGui.Begin("Step"); local step_size = ImGui.GetWindowSize(); ImGui.End()
ImGui.SetNextWindowPos(ImVec2(300, 420))
if f <= 3 then ImGui.SetNextWindowSize(ImVec2(320, 100)) end
ImGui.SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(G.FLT_MAX, G.FLT_MAX), function(data)
data.DesiredSize.y = math.floor(data.DesiredSize.x / data.UserData)
end, 16 / 9)
ImGui.Begin("Aspect"); local asp_size = ImGui.GetWindowSize(); ImGui.End()
if f == 5 then
check(step_size.x == 200 and step_size.y == 200, ("size callback Step %gx%g"):format(step_size.x, step_size.y))
check(asp_size.x == 320 and asp_size.y == 180, ("size callback AspectRatio %gx%g"):format(asp_size.x, asp_size.y))
end
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if f == 40 and #failed == 0 then print("HELPERS OK") end
end)
+94
View File
@@ -0,0 +1,94 @@
-- Library-only draw-data checksum (no demo, run with NO_DEMO=1): widgets + raw ImDrawList primitives with scripted input.
-- Must stay identical across demo edits; changes only when library rendering changes. Prints "LIBHASH <n>".
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 frame, h = 0, 0
local function mix(v) h = (h * 31 + math.floor(v * 64 + 0.5)) % 2147483647 end
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) }
game:GetService("RunService").Stepped:Connect(function()
frame += 1
G.ImGui_ImplRoblox.NewFrame()
io.DeltaTime = 1 / 60
io:AddMousePosEvent(20 + (frame * 37) % 560, 20 + (frame * 53) % 560)
if frame % 7 == 0 then io:AddMouseButtonEvent(0, frame % 14 == 0) end
if frame % 11 == 0 then io:AddMouseWheelEvent(0, -1) end
ImGui.NewFrame()
ImGui.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()
ImGui.Render()
for _, dl in ImGui.GetDrawData().CmdLists:iter() do
local vb, id = dl.VtxBuffer.Buf, dl.IdxBuffer.Data
for i = 1, dl.VtxBuffer.Size do local o = (i - 1) * 36; mix(buffer.readf64(vb, o)); mix(buffer.readf64(vb, o + 8)); mix(buffer.readf64(vb, o + 16) * 4096); mix(buffer.readf64(vb, o + 24) * 4096); mix(buffer.readu32(vb, o + 32) / 64) end
for i = 1, dl.IdxBuffer.Size do mix(id[i]) end
for _, c in dl.CmdBuffer:iter() do mix(c.ClipRect.x); mix(c.ClipRect.y); mix(c.ClipRect.z); mix(c.ClipRect.w); mix(c.ElemCount) end
end
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if frame == ARG_FRAMES - 1 then print("LIBHASH", h) end
end)
+265
View File
@@ -0,0 +1,265 @@
-- Multi-select / box-select / typing-select: ImGuiSelectionBasicStorage + Selectable list (click, Ctrl+click, Shift+click, Ctrl+A,
-- Escape, box-select drag, keyboard nav + Shift, typing-select), ImGuiListClipper + IncludeItemByIndex(RangeSrcItem),
-- ImGuiSelectionExternalStorage on a bool array, checkboxes and tree nodes inside a multi-select scope.
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
local MSF = G.ImGuiMultiSelectFlags
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.NavEnableKeyboard)
local failed = {}
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
local NAMES = { "Apple", "Apricot", "Banana", "Blueberry", "Cherry", "Coconut", "Date", "Durian", "Elderberry", "Fig",
"Grape", "Guava", "Kiwi", "Lemon", "Lime", "Mango", "Melon", "Orange", "Papaya", "Peach" }
local N = #NAMES
-- Iterate with GetNextSelectedItem() (sorted by key) -> "a,b,c"
local function sel_str(sel)
local out, it, id, has_more = {}, nil, 0, false
while true do
has_more, it, id = sel:GetNextSelectedItem(it)
if not has_more then break end
table.insert(out, tostring(id))
end
return table.concat(out, ",")
end
local function bools_str(t, n)
local out = {}
for i = 1, n do if t[i] then table.insert(out, tostring(i - 1)) end end
return table.concat(out, ",")
end
local sel_list = G.ImGuiSelectionBasicStorage()
local sel_clip = G.ImGuiSelectionBasicStorage()
local sel_tree = G.ImGuiSelectionBasicStorage()
local ext_bools = {}
local sel_ext = G.ImGuiSelectionExternalStorage()
sel_ext.UserData = ext_bools
sel_ext.AdapterSetItemSelected = function(self, idx, selected) self.UserData[idx + 1] = selected end
local chk_bools = {}
local sel_chk = G.ImGuiSelectionExternalStorage()
sel_chk.UserData = chk_bools
sel_chk.AdapterSetItemSelected = function(self, idx, selected) self.UserData[idx + 1] = selected end
local rects = {} -- [name] = { min, max } (copies)
local ids = {} -- [name] = item id
local function rect() local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); return { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end
local function center(name) local r = rects[name]; return (r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2 end
local function key(k) io:AddKeyEvent(k, true); io:AddKeyEvent(k, false) end
local function mods(ctrl, shift) io:AddKeyEvent(G.ImGuiMod_Ctrl, ctrl == true); io:AddKeyEvent(G.ImGuiMod_Shift, shift == true) end
-- Schedule: each step = { input = fn (before NewFrame), check = fn (after Render) }. Steps run on consecutive frames.
local steps = {}
local function step(input, chk) table.insert(steps, { input = input, check = chk }) end
local function idle(n) for _ = 1, n or 1 do step(nil, nil) end end
-- click: press (+mods) on frame A, release on frame B, then one idle frame for EndMultiSelect requests to be applied
local function click(name, ctrl, shift)
step(function() mods(ctrl, shift); io:AddMousePosEvent(center(name)); io:AddMouseButtonEvent(0, true) end)
step(function() io:AddMouseButtonEvent(0, false) end)
step(function() mods(false, false) end)
end
local typed_focus_idx = -1
local clip_scroll_request = false
local f = 0
idle(3)
-- 1. List: click selects one
click("L2"); step(nil, function() check(sel_str(sel_list) == "2", "click: " .. sel_str(sel_list)) end)
-- 2. Ctrl+click toggles
click("L5", true); step(nil, function() check(sel_str(sel_list) == "2,5", "ctrl+click add: " .. sel_str(sel_list)) end)
click("L2", true); step(nil, function() check(sel_str(sel_list) == "5", "ctrl+click remove: " .. sel_str(sel_list)) end)
-- 3. Shift+click range (from RangeSrc = 2, the last Ctrl+clicked item)
click("L8", false, true); step(nil, function() check(sel_str(sel_list) == "2,3,4,5,6,7,8", "shift+click range: " .. sel_str(sel_list)) end)
-- 4. Ctrl+A
step(function() io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.A, true) end)
step(function() io:AddKeyEvent(G.ImGuiKey.A, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end)
step(nil, function() check(sel_list.Size == N, "ctrl+A size " .. sel_list.Size) end)
-- 5. Escape clears (ClearOnEscape)
step(function() key(G.ImGuiKey.Escape) end)
step(nil, function() check(sel_list.Size == 0 and sel_str(sel_list) == "", "escape clear: " .. sel_str(sel_list)) end)
-- 6. Box-select drag (BoxSelect1d) from item 2 to item 5
step(function() io:AddMousePosEvent(center("L2")); io:AddMouseButtonEvent(0, true) end)
step(function() local x, y = center("L2"); io:AddMousePosEvent(x + 2, y + 10) end)
step(function() local x, y = center("L4"); io:AddMousePosEvent(x + 4, y) end)
step(function() local x, y = center("L5"); io:AddMousePosEvent(x + 4, y) end,
function() check(ImGui.GetCurrentContext().BoxSelectState.IsActive, "box-select active while dragging") end)
step(nil)
step(function() io:AddMouseButtonEvent(0, false) end)
step(nil, function() check(sel_str(sel_list) == "2,3,4,5", "box-select: " .. sel_str(sel_list)); check(not ImGui.GetCurrentContext().BoxSelectState.IsActive, "box-select ended") end)
-- 7. Keyboard nav: click 3, Down -> 4 (auto-select), Shift+Down x2 -> 4..6
click("L3"); step(nil, function() check(sel_str(sel_list) == "3", "click before nav: " .. sel_str(sel_list)) end)
step(function() key(G.ImGuiKey.DownArrow) end); idle(2)
step(nil, function() check(sel_str(sel_list) == "4", "nav down auto-select: " .. sel_str(sel_list)) end)
step(function() io:AddKeyEvent(G.ImGuiMod_Shift, true); key(G.ImGuiKey.DownArrow) end); idle(1)
step(function() key(G.ImGuiKey.DownArrow) end); idle(1)
step(function() io:AddKeyEvent(G.ImGuiMod_Shift, false) end); idle(1)
step(nil, function() check(sel_str(sel_list) == "4,5,6", "shift+nav range: " .. sel_str(sel_list)) end)
-- 8. Typing-select: "ch" -> Cherry (5), then single-char mode "b" "b" -> Banana (2) then Blueberry (3)
step(function() io:AddInputCharacter(string.byte("c")); io:AddInputCharacter(string.byte("h")) end); idle(2)
step(nil, function() check(ImGui.GetCurrentContext().NavId == ids.L4, "typing 'ch' -> Cherry"); check(sel_str(sel_list) == "4", "typing focus selects: " .. sel_str(sel_list)) end)
idle(120) -- TYPING_SELECT_RESET_TIMER
step(function() io:AddInputCharacter(string.byte("b")) end); idle(2)
step(nil, function() check(ImGui.GetCurrentContext().NavId == ids.L2, "typing 'b' -> Banana") end)
step(function() io:AddInputCharacter(string.byte("b")) end); idle(2)
step(nil, function() check(ImGui.GetCurrentContext().NavId == ids.L3, "typing 'bb' -> Blueberry") end)
idle(120)
-- 9. Clipper (1000 items): click 5, scroll down, Shift+click item 40 -> 5..40 (RangeSrcItem 5 kept submitted by IncludeItemByIndex)
click("C5")
-- move NavId away with Ctrl+Down (no selection change, RangeSrc stays 5) so the clipper doesn't keep item 5 alive as the nav item
step(function() io:AddKeyEvent(G.ImGuiMod_Ctrl, true); key(G.ImGuiKey.DownArrow) end); idle(1)
step(function() key(G.ImGuiKey.DownArrow) end); idle(1)
step(function() key(G.ImGuiKey.DownArrow) end); idle(1)
step(function() io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end)
step(nil, function() check(sel_str(sel_clip) == "5", "clipper ctrl+nav keeps selection: " .. sel_str(sel_clip)) end)
step(function() clip_scroll_request = true end); idle(2)
step(nil, function() check(rects.C40 ~= nil and rects.C5 == nil, "clipper scrolled (item 5 not visible)") end)
click("C40", false, true)
step(nil, function() check(sel_clip.Size == 36, "clipper shift+click range size " .. sel_clip.Size); check(sel_clip:Contains(5) and sel_clip:Contains(40) and not sel_clip:Contains(4) and not sel_clip:Contains(41), "clipper range bounds") end)
-- 10. External storage on bool array: click 1, Shift+click 3, Ctrl+click 2
click("E1"); click("E3", false, true)
step(nil, function() check(bools_str(ext_bools, 10) == "1,2,3", "external storage range: " .. bools_str(ext_bools, 10)) end)
click("E2", true)
step(nil, function() check(bools_str(ext_bools, 10) == "1,3", "external storage ctrl+click: " .. bools_str(ext_bools, 10)) end)
-- 11. Checkboxes (NoAutoSelect|NoAutoClear): click 1 toggles on, Shift+click 4 -> 1..4 on, click 2 toggles off
click("K1"); step(nil, function() check(bools_str(chk_bools, 8) == "1", "checkbox click: " .. bools_str(chk_bools, 8)) end)
click("K4", false, true); step(nil, function() check(bools_str(chk_bools, 8) == "1,2,3,4", "checkbox shift+click: " .. bools_str(chk_bools, 8)) end)
click("K2"); step(nil, function() check(bools_str(chk_bools, 8) == "1,3,4", "checkbox toggle off: " .. bools_str(chk_bools, 8)) end)
-- 12. Tree nodes: click 0, Ctrl+click 2, Shift+click 4 (range from 2)
click("T0"); click("T2", true)
step(nil, function() check(sel_str(sel_tree) == "0,2", "tree ctrl+click: " .. sel_str(sel_tree)) end)
click("T4", false, true)
step(nil, function() check(sel_str(sel_tree) == "2,3,4", "tree shift+click: " .. sel_str(sel_tree)) end)
-- 13. Metrics window renders the TypingSelect / MultiSelect debug nodes
local show_metrics = false
step(function() show_metrics = true end); idle(3)
step(nil, function() if #failed == 0 then print("MULTISELECT OK") end end)
local function list_window()
ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(200, 420))
ImGui.Begin("L")
local flags = bit32.bor(MSF.ClearOnEscape, MSF.BoxSelect1d)
local ms_io = ImGui.BeginMultiSelect(flags, sel_list.Size, N)
sel_list:ApplyRequests(ms_io)
-- typing-select
local focus_idx = -1
if ImGui.IsWindowFocused() then
local req = ImGui.GetTypingSelectRequest(G.ImGuiTypingSelectFlags.AllowSingleCharMode)
local nav_idx = (type(ms_io.NavIdItem) == "number") and ms_io.NavIdItem or -1
focus_idx = ImGui.TypingSelectFindMatch(req, N, function(_, idx) return NAMES[idx + 1] end, nil, nav_idx)
end
for n = 0, N - 1 do
if n == focus_idx then ImGui.SetKeyboardFocusHere() end
ImGui.SetNextItemSelectionUserData(n)
ImGui.Selectable(NAMES[n + 1], sel_list:Contains(n))
rects["L" .. n] = rect(); ids["L" .. n] = ImGui.GetItemID()
end
ms_io = ImGui.EndMultiSelect()
sel_list:ApplyRequests(ms_io)
ImGui.End()
end
local function clipper_window()
ImGui.SetNextWindowPos(ImVec2(220, 10)); ImGui.SetNextWindowSize(ImVec2(200, 300))
ImGui.Begin("C")
if clip_scroll_request then ImGui.SetScrollY(ImGui.GetTextLineHeightWithSpacing() * 30); clip_scroll_request = false end
local COUNT = 1000
local ms_io = ImGui.BeginMultiSelect(MSF.ClearOnEscape, sel_clip.Size, COUNT)
sel_clip:ApplyRequests(ms_io)
local clipper = G.ImGuiListClipper()
clipper:Begin(COUNT)
if ms_io.RangeSrcItem ~= -1 then clipper:IncludeItemByIndex(ms_io.RangeSrcItem) end -- Ensure RangeSrc item is not clipped.
for k in rects do if string.sub(k, 1, 1) == "C" then rects[k] = nil end end
while clipper:Step() do
for n = clipper.DisplayStart, clipper.DisplayEnd - 1 do
ImGui.SetNextItemSelectionUserData(n)
ImGui.Selectable(("Object %04d"):format(n), sel_clip:Contains(n))
if ImGui.IsItemVisible() then rects["C" .. n] = rect() end
end
end
ms_io = ImGui.EndMultiSelect()
sel_clip:ApplyRequests(ms_io)
ImGui.End()
end
local function external_window()
ImGui.SetNextWindowPos(ImVec2(430, 10)); ImGui.SetNextWindowSize(ImVec2(180, 220))
ImGui.Begin("E")
local ms_io = ImGui.BeginMultiSelect(MSF.None, -1, 10)
sel_ext:ApplyRequests(ms_io)
for n = 0, 9 do
ImGui.SetNextItemSelectionUserData(n)
ImGui.Selectable("Ext " .. n, ext_bools[n + 1] == true)
rects["E" .. n] = rect()
end
ms_io = ImGui.EndMultiSelect()
sel_ext:ApplyRequests(ms_io)
ImGui.End()
end
local function checkbox_window()
ImGui.SetNextWindowPos(ImVec2(430, 240)); ImGui.SetNextWindowSize(ImVec2(180, 200))
ImGui.Begin("K")
local ms_io = ImGui.BeginMultiSelect(bit32.bor(MSF.NoAutoSelect, MSF.NoAutoClear, MSF.ClearOnEscape), -1, 8)
sel_chk:ApplyRequests(ms_io)
for n = 0, 7 do
ImGui.SetNextItemSelectionUserData(n)
local _, v = ImGui.Checkbox("Check " .. n, chk_bools[n + 1] == true)
rects["K" .. n] = rect()
local _ = v -- the selection is updated through requests (ApplyRequests), like upstream's demo
end
ms_io = ImGui.EndMultiSelect()
sel_chk:ApplyRequests(ms_io)
ImGui.End()
end
local function tree_window()
ImGui.SetNextWindowPos(ImVec2(220, 320)); ImGui.SetNextWindowSize(ImVec2(200, 160))
ImGui.Begin("T")
local ms_io = ImGui.BeginMultiSelect(MSF.None, sel_tree.Size, 5)
sel_tree:ApplyRequests(ms_io)
for n = 0, 4 do
local flags = bit32.bor(G.ImGuiTreeNodeFlags.Leaf, G.ImGuiTreeNodeFlags.NoTreePushOnOpen, G.ImGuiTreeNodeFlags.SpanAvailWidth)
if sel_tree:Contains(n) then flags = bit32.bor(flags, G.ImGuiTreeNodeFlags.Selected) end
ImGui.SetNextItemSelectionUserData(n)
ImGui.TreeNodeEx("Node " .. n, flags)
rects["T" .. n] = rect()
end
ms_io = ImGui.EndMultiSelect()
sel_tree:ApplyRequests(ms_io)
ImGui.End()
end
game:GetService("RunService").Stepped:Connect(function()
f += 1
G.ImGui_ImplRoblox.NewFrame()
local s = steps[f]
if s and s.input then s.input() end
ImGui.NewFrame()
list_window()
clipper_window()
external_window()
checkbox_window()
tree_window()
if show_metrics then
ImGui.SetNextWindowPos(ImVec2(620, 10)); ImGui.SetNextWindowSize(ImVec2(400, 600))
ImGui.ShowMetricsWindow()
local ctx = ImGui.GetCurrentContext()
ImGui.Begin("DebugNodes")
ImGui.DebugNodeTypingSelectState(ctx.TypingSelectState)
for n = 0, ctx.MultiSelectStorage:GetMapSize() - 1 do
local st = ctx.MultiSelectStorage:GetByIndex(n)
if st then ImGui.SetNextItemOpen(true); ImGui.DebugNodeMultiSelectState(st) end
end
ImGui.End()
end
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if s and s.check then s.check() end
end)
print(("t_multiselect: %d steps"):format(#steps))
+133
View File
@@ -0,0 +1,133 @@
-- Backend pixel oracle: composites the ScreenGui (like t_composite) at checkpoints and prints "PIX <frame> <hash>".
-- Phases: resize (bottom-right, then top-left), move, scroll, hover, typing, new font sizes (glyph baking), colour drag.
-- Each phase ends with a few still frames, then a checkpoint. Run with RUN_FLAGS="ARG_FORCE=true" for a full redraw
-- every frame: incremental output must match it exactly (and match the previous backend version).
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)
if ARG_TILELIMIT then ImGui.GetIO().BackendPlatformUserData.TileLimit = ARG_TILELIMIT end
local io = ImGui.GetIO()
local W, H = 1280, 760
local col = ImVec4(0.4, 0.7, 0.2, 0.5)
local buf = ImGui.NewTextBuffer("type here", 128)
local slider = 0.3
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)
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 >= cy0 and sy < cy1 then
for tx = 0, sz - 1 do
local sx = x + tx
if sx >= cx0 and sx < cx1 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)
local h = 0
for i = 0, W * H * 3 - 4, 4 do h = (h * 31 + buffer.readu32(screen, i)) % 2147483647 end
return h
end
-- phase list: name, frames of action (then 6 still frames + checkpoint)
local PHASES = { { "start", 4 }, { "resize_br", 24 }, { "resize_tl", 24 }, { "move", 24 }, { "scroll", 30 }, { "hover", 30 },
{ "type", 24 }, { "fonts", 24 }, { "color", 20 } }
local STILL = ARG_STILL or (ARG_TILELIMIT and 40 or 6) -- tile limit: deferred tiles need more frames to settle
local PT = {}
local pi, pf = 1, 0
local pos, size, scroll, fsize = ImVec2(40, 40), ImVec2(460, 520), 0, 13
local frame = 0
local typing = "Hello, glyphs! 0123 xyz"
game:GetService("RunService").Stepped:Connect(function()
local ph = PHASES[pi]
if not ph then return end
frame += 1; pf += 1
local name, n = ph[1], ph[2]
local act = pf <= n
G.ImGui_ImplRoblox.NewFrame()
io.DeltaTime = 1 / 60
io:AddMousePosEvent(1200, 740)
if act then
if name == "resize_br" then size = ImVec2(460 + pf * 7, 520 + pf * 5)
elseif name == "resize_tl" then pos = ImVec2(40 + pf * 3, 40 + pf * 2); size = ImVec2(size.x - 3, size.y - 2)
elseif name == "move" then pos = ImVec2(pos.x + 5, pos.y + 3)
elseif name == "scroll" then scroll += 9
elseif name == "hover" then io:AddMousePosEvent(pos.x + 30 + (pf * 17) % 300, pos.y + 40 + (pf * 23) % 400)
elseif name == "type" then
if pf == 1 then io:AddMousePosEvent(pos.x + 120, pos.y + 37); io:AddMouseButtonEvent(0, true)
elseif pf == 2 then io:AddMouseButtonEvent(0, false)
else io:AddInputCharacter(string.byte(typing, (pf - 3) % #typing + 1)) end
elseif name == "fonts" then fsize = 13 + pf * 0.75
elseif name == "color" then
local p = ImVec2(700 + pf * 4, 120 + pf * 3)
io:AddMousePosEvent(p.x, p.y); io:AddMouseButtonEvent(0, pf < n)
end
end
ImGui.NewFrame()
ImGui.SetNextWindowPos(pos, G.ImGuiCond.Always); ImGui.SetNextWindowSize(size, G.ImGuiCond.Always)
ImGui.Begin("Stress")
ImGui.InputText("##in", buf, 128)
if name == "scroll" or scroll > 0 then ImGui.SetScrollY(scroll) end
for i = 1, 12 do ImGui.Button("Button " .. i); ImGui.SameLine(); slider = ImGui.SliderFloat("##s" .. i, slider, 0, 1) end
if ImGui.BeginTable("tbl", 4, bit32.bor(G.ImGuiTableFlags.Borders, G.ImGuiTableFlags.RowBg)) then
for r = 1, 30 do ImGui.TableNextRow(); for c = 1, 4 do ImGui.TableNextColumn(); ImGui.Text("r%d c%d", r, c) end end
ImGui.EndTable()
end
ImGui.PushFont(nil, fsize); ImGui.Text("Font size %.2f AaBbCc", fsize); ImGui.PopFont()
for i = 1, 30 do ImGui.TextWrapped("Line %d: the quick brown fox jumps over the lazy dog.", i) end
ImGui.End()
ImGui.SetNextWindowPos(ImVec2(660, 60), G.ImGuiCond.Always); ImGui.SetNextWindowSize(ImVec2(500, 620), G.ImGuiCond.Always)
ImGui.Begin("Pickers")
ImGui.ColorPicker4("bar", col, G.ImGuiColorEditFlags.PickerHueBar)
ImGui.End()
ImGui.Render()
local clk = os.realclock or os.clock
local t0 = clk()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if act then PT[name] = (PT[name] or 0) + (clk() - t0) * 1000 end
if pf == n then -- mid-action (not settled): only exact without a redraw budget
print("PIXA", name, frame, composite(game:GetService("Players").LocalPlayer.PlayerGui:FindFirstChild("ImGui")))
end
if pf == n + STILL then
local gui = game:GetService("Players").LocalPlayer.PlayerGui:FindFirstChild("ImGui")
print("PIX", name, frame, composite(gui))
pi += 1; pf = 0
if not PHASES[pi] then
for _, q in PHASES do print(string.format("PIXT %-10s %7.2f ms/frame", q[1], PT[q[1]] / q[2])) end
print("PIXELS DONE")
end
end
end)
+138
View File
@@ -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)
+14
View File
@@ -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)
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "4dbug/imgui-luau" name = "4dbug/imgui-luau"
version = "0.1.3" version = "0.3.0"
registry = "https://github.com/UpliftGames/wally-index" registry = "https://github.com/UpliftGames/wally-index"
realm = "shared" realm = "shared"
license = "MIT" license = "MIT"