20 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
42 changed files with 57442 additions and 27243 deletions
+4 -6
View File
@@ -116,7 +116,7 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
### 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.2.0"` 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.
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,7 @@ 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.SetRenderScale(scale)` | Change it at runtime. |
| `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default `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.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. |
@@ -179,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.
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.
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. Optionally capped with `SetRenderRate(hz)` / `SetBusyRenderRate(hz)`. 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).
@@ -205,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.
[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
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
+4 -16
View File
@@ -35,35 +35,27 @@ jobs:
run: |
mkdir -p dist
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 --no-demo dist/love-nodemo
# 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-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-nodemo/imgui_love.lua dist/imgui_love-nodemo.min.lua
- name: Check
run: |
python3 tools/sync_github_readme.py --check
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
luajit -bl dist/love/imgui_love.lua > /dev/null
luajit -bl dist/love-nodemo/imgui_love.lua > /dev/null
python3 tools/minify.py --test
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-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
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
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
grep -q "README OK" /tmp/rm.txt
python3 tools/test/run.py --main tools/test/scripts/t_fixes.luau --frames 25 --quiet --roblox --timeout 300 | tee /tmp/f.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
luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic | tee /tmp/h.txt
@@ -78,16 +70,14 @@ jobs:
V="${GITHUB_REF_NAME}"
cd dist
cp roblox/ImGui.luau ImGui.luau
cp roblox-nodemo/ImGui.luau ImGui-nodemo.luau
cp love/imgui_love.lua imgui_love.lua
cp love-nodemo/imgui_love.lua imgui_love-nodemo.lua
# 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/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 .)
# 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/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 .)
ls -la
@@ -97,9 +87,7 @@ jobs:
with:
files: |
dist/ImGui.luau
dist/ImGui-nodemo.luau
dist/imgui_love.lua
dist/imgui_love-nodemo.lua
dist/*.min.luau
dist/*.min.lua
dist/*.zip
@@ -127,4 +115,4 @@ jobs:
gh release delete latest --yes --cleanup-tag || true
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." \
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
+4 -6
View File
@@ -113,7 +113,7 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
### 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.2.0"` 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.
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,7 @@ 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.SetRenderScale(scale)` | Change it at runtime. |
| `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default `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.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. |
@@ -176,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.
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.
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. Optionally capped with `SetRenderRate(hz)` / `SetBusyRenderRate(hz)`. 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).
@@ -202,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.
[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
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
+4
View File
@@ -431,6 +431,8 @@ function ImGui_ImplLOVE_Init(opts)
local io = ImGui.GetIO()
local bd = { Time = love.timer.getTime(), Root = opts.root or "", Textures = {}, TextureCount = 0, Meshes = {}, FrameNo = 0 }
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)
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 _, t in pairs(bd.Textures) do t:release() end
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)))
end
+6 -2
View File
@@ -1353,9 +1353,11 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
local bd = {
Time = os.clock(), Gui = gui, Connections = {},
Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1,
Layers = {}, Overlays = {}, OverlayHolders = {}, OverlayShown = {}, FrameNo = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 0, BusyRenderRate = 0, RedrawBudget = 0, HB = 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.BackendPlatformName = "imgui_impl_roblox"
io.BackendRendererName = "imgui_impl_roblox"
io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset)
@@ -1475,6 +1477,8 @@ function ImGui_ImplRoblox_Shutdown()
bd.Gui:Destroy()
io.BackendPlatformUserData = nil
io.BackendPlatformName = nil
io.BackendRendererName = nil
io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor(
ImGuiBackendFlags.RendererHasTextures,
ImGuiBackendFlags.RendererHasVtxOffset
@@ -1601,7 +1605,7 @@ function ImGui_ImplRoblox_SetRedrawBudget(ms)
ImGui_ImplRoblox_GetBackendData().RedrawBudget = (ms or 0) / 1000
end
--- Max tile redraws per second (default 0 = every frame, limited by the redraw budget instead). imgui logic still runs every frame.
--- 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)
ImGui_ImplRoblox_GetBackendData().RenderRate = hz or 0
end
+1 -1
View File
@@ -1 +1 @@
[[46, "imgui.lua"], [21389, "imgui_demo.lua"], [29807, "imgui_draw.lua"], [35745, "imgui_h.lua"], [38412, "imgui_impl_roblox.lua"], [40060, "imgui_impl_roblox_fonts.lua"], [46463, "imgui_internal.lua"], [49932, "imgui_tables.lua"], [54991, "imgui_widgets.lua"], [65631, "imstb_rectpack.lua"], [66295, "imstb_textedit.lua"], [67837, "imstb_truetype.lua"], [70625, "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"]]
+18054 -8348
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 = {
AcceptDragDropPayload: (type: string, flags: number?) -> any,
ActivateItemByID: (...any) -> ...any,
AddContextHook: (...any) -> ...any,
AddDrawListToDrawDataEx: (...any) -> ...any,
AddSettingsHandler: (...any) -> ...any,
AddUpdateViewport: (...any) -> ...any,
@@ -13,6 +14,7 @@ export type ImGuiAPI = {
ArrowButton: (str_id: string, dir: number) -> boolean,
ArrowButtonEx: (...any) -> ...any,
Begin: (name: string, open: boolean?, flags: number?) -> (boolean, boolean?),
BeginBoxSelect: (...any) -> ...any,
BeginChild: (...any) -> ...any,
BeginChildEx: (...any) -> ...any,
BeginColumns: (...any) -> ...any,
@@ -36,6 +38,7 @@ export type ImGuiAPI = {
BeginMenu: (label: string, enabled: boolean?) -> boolean,
BeginMenuBar: () -> boolean,
BeginMenuEx: (...any) -> ...any,
BeginMultiSelect: (flags: number, selection_size: number?, items_count: number?) -> any,
BeginPopup: (str_id: string, flags: number?) -> boolean,
BeginPopupContextItem: (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,
CalcWindowNextAutoFitSize: (...any) -> ...any,
CalcWrapWidthForPos: (...any) -> ...any,
CallContextHooks: (...any) -> ...any,
Checkbox: (label: string, v: boolean?) -> (boolean, boolean?),
CheckboxFlags: (...any) -> ...any,
CleanupIniSettings: (...any) -> ...any,
@@ -109,26 +113,47 @@ export type ImGuiAPI = {
DataTypeGetInfo: (...any) -> ...any,
DataTypeIsZero: (...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,
DebugLocateItem: (...any) -> ...any,
DebugLocateItemOnHover: (...any) -> ...any,
DebugLocateItemResolveWithLastItem: (...any) -> ...any,
DebugLog: (fmt: string, ...any) -> (),
DebugLogV: (fmt: string, ...any) -> (),
DebugNodeColumns: (...any) -> ...any,
DebugNodeDockNode: (...any) -> ...any,
DebugNodeDrawCmdShowMeshAndBoundingBox: (...any) -> ...any,
DebugNodeDrawList: (...any) -> ...any,
DebugNodeFont: (...any) -> ...any,
DebugNodeFontGlyph: (...any) -> ...any,
DebugNodeFontGlyphsForSrcMask: (...any) -> ...any,
DebugNodeInputTextState: (...any) -> ...any,
DebugNodeMultiSelectState: (...any) -> ...any,
DebugNodePlatformMonitor: (...any) -> ...any,
DebugNodeStorage: (...any) -> ...any,
DebugNodeTabBar: (...any) -> ...any,
DebugNodeTable: (...any) -> ...any,
DebugNodeTableSettings: (...any) -> ...any,
DebugNodeTexture: (...any) -> ...any,
DebugNodeTypingSelectState: (...any) -> ...any,
DebugNodeViewport: (...any) -> ...any,
DebugNodeWindow: (...any) -> ...any,
DebugNodeWindowSettings: (...any) -> ...any,
DebugNodeWindowsList: (...any) -> ...any,
DebugNodeWindowsListByBeginStackParent: (...any) -> ...any,
DebugRenderKeyboardPreview: (...any) -> ...any,
DebugRenderViewportThumbnail: (...any) -> ...any,
DebugStartItemPicker: () -> (),
DebugTextEncoding: (str: string) -> (),
DebugTextUnformattedWithLocateItem: (...any) -> ...any,
DebugTextureIDToU64: (...any) -> ...any,
DemoMarker: (...any) -> ...any,
DestroyContext: (ctx: any) -> ...any,
DestroyPlatformWindow: (...any) -> ...any,
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,
Dummy: (size: ImVec2) -> (),
End: () -> (),
EndBoxSelect: (...any) -> ...any,
EndChild: () -> (),
EndColumns: (...any) -> ...any,
EndCombo: () -> (),
@@ -235,12 +261,15 @@ export type ImGuiAPI = {
EndMainMenuBar: () -> (),
EndMenu: () -> (),
EndMenuBar: () -> (),
EndMultiSelect: () -> any,
EndPopup: () -> (),
EndTabBar: () -> (),
EndTabItem: () -> (),
EndTable: () -> (),
EndTooltip: () -> (),
ErrorCheckEndFrameFinalizeErrorTooltip: (...any) -> ...any,
ErrorCheckEndFrameSanityChecks: (...any) -> ...any,
ErrorCheckNewFrameSanityChecks: (...any) -> ...any,
ErrorCheckUsingSetCursorPosToExtendParentBoundaries: (...any) -> ...any,
ErrorLog: (...any) -> ...any,
ErrorRecoveryStoreState: (...any) -> ...any,
@@ -259,6 +288,7 @@ export type ImGuiAPI = {
FindRenderedTextEnd: (...any) -> ...any,
FindSettingsHandler: (...any) -> ...any,
FindViewportByID: (viewport_id: number) -> any,
FindViewportByPlatformHandle: (platform_handle: any) -> ...any,
FindWindowByID: (...any) -> ...any,
FindWindowByName: (...any) -> ...any,
FindWindowDisplayIndex: (...any) -> ...any,
@@ -273,7 +303,9 @@ export type ImGuiAPI = {
GcAwakeTransientWindowBuffers: (...any) -> ...any,
GcCompactTransientMiscBuffers: (...any) -> ...any,
GcCompactTransientWindowBuffers: (...any) -> ...any,
GetActiveID: (...any) -> ...any,
GetBackgroundDrawList: (viewport: any) -> ...any,
GetBoxSelectState: (...any) -> ...any,
GetClipboardText: () -> any,
GetColorU32: (...any) -> ...any,
GetColumnIndex: () -> number,
@@ -286,6 +318,9 @@ export type ImGuiAPI = {
GetContentRegionAvail: () -> ImVec2,
GetContentRegionMax: (...any) -> ...any,
GetCurrentContext: () -> any,
GetCurrentFocusScope: (...any) -> ...any,
GetCurrentTabBar: (...any) -> ...any,
GetCurrentTable: (...any) -> ...any,
GetCurrentWindow: (...any) -> ...any,
GetCurrentWindowRead: (...any) -> ...any,
GetCursorPos: () -> ImVec2,
@@ -297,7 +332,9 @@ export type ImGuiAPI = {
GetDragDropPayload: () -> any,
GetDrawData: () -> any,
GetDrawListSharedData: () -> any,
GetFocusID: (...any) -> ...any,
GetFont: () -> any,
GetFontBaked: () -> any,
GetFontSize: () -> number,
GetFontTexUvWhitePixel: () -> ImVec2,
GetForegroundDrawList: (viewport: any) -> ...any,
@@ -315,6 +352,7 @@ export type ImGuiAPI = {
GetItemRectMax: () -> ImVec2,
GetItemRectMin: () -> ImVec2,
GetItemRectSize: () -> ImVec2,
GetItemStatusFlags: (...any) -> ...any,
GetKeyChordName: (...any) -> ...any,
GetKeyData: (...any) -> ...any,
GetKeyMagnitude2d: (...any) -> ...any,
@@ -331,7 +369,9 @@ export type ImGuiAPI = {
GetMouseDragDelta: (button: number?, lock_threshold: number?) -> ImVec2,
GetMousePos: () -> ImVec2,
GetMousePosOnOpeningCurrentPopup: () -> ImVec2,
GetMultiSelectState: (...any) -> ...any,
GetNavTweakPressedAmount: (...any) -> ...any,
GetPixelDensity: (...any) -> ...any,
GetPlatformIO: () -> any,
GetPopupAllowedExtentRect: (...any) -> ...any,
GetRoundedFontSize: (...any) -> ...any,
@@ -353,6 +393,7 @@ export type ImGuiAPI = {
GetTopMostPopupModal: (...any) -> ...any,
GetTreeNodeToLabelSpacing: () -> number,
GetTypematicRepeatRate: (...any) -> ...any,
GetTypingSelectRequest: (...any) -> ...any,
GetVersion: () -> any,
GetViewportPlatformMonitor: (...any) -> ...any,
GetWindowAlwaysWantOwnTabBar: (...any) -> ...any,
@@ -360,10 +401,13 @@ export type ImGuiAPI = {
GetWindowContentRegionMax: (...any) -> ...any,
GetWindowContentRegionMin: (...any) -> ...any,
GetWindowDockID: () -> number,
GetWindowDockNode: (...any) -> ...any,
GetWindowDpiScale: () -> number,
GetWindowDrawList: () -> any,
GetWindowHeight: () -> number,
GetWindowPos: () -> ImVec2,
GetWindowResizeBorderID: (...any) -> ...any,
GetWindowResizeCornerID: (...any) -> ...any,
GetWindowScrollbarID: (...any) -> ...any,
GetWindowScrollbarRect: (...any) -> ...any,
GetWindowSize: () -> ImVec2,
@@ -405,6 +449,7 @@ export type ImGuiAPI = {
IsInNavFocusRoute: (...any) -> ...any,
IsItemActivated: () -> boolean,
IsItemActive: () -> boolean,
IsItemActiveAsInputText: (...any) -> ...any,
IsItemClicked: (mouse_button: number?) -> boolean,
IsItemDeactivated: () -> boolean,
IsItemDeactivatedAfterEdit: () -> boolean,
@@ -421,6 +466,7 @@ export type ImGuiAPI = {
IsKeyReleased: (...any) -> ...any,
IsKeyboardKey: (...any) -> ...any,
IsLRModKey: (...any) -> ...any,
IsLegacyKey: (...any) -> ...any,
IsMouseClicked: (button: number, is_repeat: boolean?) -> boolean,
IsMouseClickedEx: (...any) -> ...any,
IsMouseDoubleClicked: (...any) -> ...any,
@@ -479,6 +525,10 @@ export type ImGuiAPI = {
MenuItem: (...any) -> ...any,
MenuItemEx: (...any) -> ...any,
MouseButtonToKey: (...any) -> ...any,
MultiSelectAddSetAll: (...any) -> ...any,
MultiSelectAddSetRange: (...any) -> ...any,
MultiSelectItemFooter: (...any) -> ...any,
MultiSelectItemHeader: (...any) -> ...any,
NavApplyItemToResult: (...any) -> ...any,
NavBiasScoringRect: (...any) -> ...any,
NavCalcPreferredRefPos: (...any) -> ...any,
@@ -556,6 +606,8 @@ export type ImGuiAPI = {
RadioButton: (...any) -> ...any,
RadioButtonEx: (...any) -> ...any,
RegisterFontAtlas: (...any) -> ...any,
RegisterUserTexture: (...any) -> ...any,
RemoveContextHook: (...any) -> ...any,
RemoveSettingsHandler: (...any) -> ...any,
Render: () -> (),
RenderArrow: (...any) -> ...any,
@@ -591,6 +643,7 @@ export type ImGuiAPI = {
ScaleRatioFromValueT: (...any) -> ...any,
ScaleValueFromRatioT: (...any) -> ...any,
ScaleWindowsInViewport: (...any) -> ...any,
ScrollToBringRectIntoView: (...any) -> ...any,
ScrollToItem: (...any) -> ...any,
ScrollToRect: (...any) -> ...any,
ScrollToRectEx: (...any) -> ...any,
@@ -608,6 +661,7 @@ export type ImGuiAPI = {
SetColorEditOptions: (...any) -> ...any,
SetColumnOffset: (column_index: number, offset: number) -> (),
SetColumnWidth: (column_index: number, width: number) -> (),
SetContextName: (...any) -> ...any,
SetCurrentContext: (ctx: any) -> ...any,
SetCurrentFont: (...any) -> ...any,
SetCurrentViewport: (...any) -> ...any,
@@ -621,6 +675,7 @@ export type ImGuiAPI = {
SetItemDefaultFocus: () -> (),
SetItemKeyOwner: (...any) -> ...any,
SetItemTooltip: (fmt: string, ...any) -> (),
SetItemTooltipV: (fmt: string, ...any) -> (),
SetKeyOwner: (...any) -> ...any,
SetKeyOwnersForKeyChord: (...any) -> ...any,
SetKeyboardFocusHere: (offset: number?) -> (),
@@ -639,6 +694,7 @@ export type ImGuiAPI = {
SetNextItemColorMarker: (...any) -> ...any,
SetNextItemOpen: (is_open: boolean, cond: number?) -> (),
SetNextItemRefVal: (...any) -> ...any,
SetNextItemSelectionUserData: (selection_user_data: any) -> (),
SetNextItemShortcut: (key_chord: any, flags: number?) -> (),
SetNextItemStorageID: (storage_id: number) -> (),
SetNextItemWidth: (item_width: number) -> (),
@@ -657,6 +713,7 @@ export type ImGuiAPI = {
SetNextWindowSize: (size: ImVec2, cond: number?) -> (),
SetNextWindowSizeConstraints: (size_min: ImVec2, size_max: ImVec2, custom_callback: any, custom_callback_user_data: any) -> ...any,
SetNextWindowViewport: (id: number) -> (),
SetPixelDensity: (...any) -> ...any,
SetScrollFromPosX: (...any) -> ...any,
SetScrollFromPosY: (...any) -> ...any,
SetScrollHereX: (center_x_ratio: number?) -> (),
@@ -667,6 +724,7 @@ export type ImGuiAPI = {
SetStateStorage: (tree: any) -> ...any,
SetTabItemClosed: (label: string) -> (),
SetTooltip: (fmt: string, ...any) -> (),
SetTooltipV: (fmt: string, ...any) -> (),
SetWindowClipRectBeforeSetChannel: (...any) -> ...any,
SetWindowCollapsed: (...any) -> ...any,
SetWindowDock: (...any) -> ...any,
@@ -674,6 +732,7 @@ export type ImGuiAPI = {
SetWindowFontScale: (...any) -> ...any,
SetWindowHiddenAndSkipItemsForCurrentFrame: (...any) -> ...any,
SetWindowHitTestHole: (...any) -> ...any,
SetWindowParentWindowForFocusRoute: (...any) -> ...any,
SetWindowPos: (...any) -> ...any,
SetWindowSize: (...any) -> ...any,
SetWindowViewport: (...any) -> ...any,
@@ -822,6 +881,7 @@ export type ImGuiAPI = {
TempInputScalar: (...any) -> ...any,
TempInputText: (...any) -> ...any,
TestKeyOwner: (...any) -> ...any,
TestShortcutRouting: (...any) -> ...any,
Text: (fmt: string, ...any) -> (),
TextAligned: (...any) -> ...any,
TextBufferToString: (buf: {number}) -> string,
@@ -848,11 +908,17 @@ export type ImGuiAPI = {
TreePop: () -> (),
TreePush: (...any) -> ...any,
TreePushOverrideID: (...any) -> ...any,
TypingSelectFindBestLeadingMatch: (...any) -> ...any,
TypingSelectFindMatch: (...any) -> ...any,
TypingSelectFindNextSingleCharMatch: (...any) -> ...any,
Unindent: (indent_w: number?) -> (),
UnregisterFontAtlas: (...any) -> ...any,
UnregisterUserTexture: (...any) -> ...any,
UpdateAliasKey: (...any) -> ...any,
UpdateCurrentFontSize: (...any) -> ...any,
UpdateDebugToolFlashStyleColor: (...any) -> ...any,
UpdateDebugToolItemPathQuery: (...any) -> ...any,
UpdateDebugToolItemPicker: (...any) -> ...any,
UpdateFontsEndFrame: (...any) -> ...any,
UpdateFontsNewFrame: (...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,
Value: (...any) -> ...any,
WindowPosAbsToRel: (...any) -> ...any,
WindowPosRelToAbs: (...any) -> ...any,
WindowRectAbsToRel: (...any) -> ...any,
WindowRectRelToAbs: (...any) -> ...any,
WindowSelectViewport: (...any) -> ...any,
WindowSettingsAddSettingsHandler: (...any) -> ...any,
WindowSyncOwnedViewport: (...any) -> ...any,
_ByteArrayToString: (...any) -> ...any,
_CalcItemSizeXY: (...any) -> ...any,
_GetTypeFunc: (...any) -> ...any,
_SetCurrentContext_Internal: (...any) -> ...any,
_SetCurrentContext_Tables: (...any) -> ...any,
_SetCurrentContext_Widgets: (...any) -> ...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 ImDrawListFlags = { None: number, AntiAliasedLines: number, AntiAliasedLinesUseTex: number, AntiAliasedFill: number, AllowVtxOffset: number, TextNoPixelSnap: 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, 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 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 }
@@ -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 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 ImGuiListClipperFlags = { None: number, NoSetTableRowCounters: 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 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 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 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 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 }
@@ -943,10 +1014,13 @@ export type ImGuiModule = {
ImGuiItemFlags: ImGuiItemFlags,
ImGuiItemStatusFlags: ImGuiItemStatusFlags,
ImGuiKey: ImGuiKey,
ImGuiListClipperFlags: ImGuiListClipperFlags,
ImGuiMouseSource: ImGuiMouseSource,
ImGuiMultiSelectFlags: ImGuiMultiSelectFlags,
ImGuiNextItemDataFlags: ImGuiNextItemDataFlags,
ImGuiPopupFlags: ImGuiPopupFlags,
ImGuiSelectableFlags: ImGuiSelectableFlags,
ImGuiSelectionRequestType: ImGuiSelectionRequestType,
ImGuiSliderFlags: ImGuiSliderFlags,
ImGuiStyleVar: ImGuiStyleVar,
ImGuiTreeNodeFlags: ImGuiTreeNodeFlags,
+1 -1
View File
@@ -1 +1 @@
[[183, "imgui.lua"], [21526, "imgui_demo.lua"], [29944, "imgui_draw.lua"], [35882, "imgui_h.lua"], [38549, "imgui_impl_love.lua"], [39089, "imgui_internal.lua"], [42558, "imgui_tables.lua"], [47617, "imgui_widgets.lua"], [58257, "imstb_rectpack.lua"], [58921, "imstb_textedit.lua"], [60463, "imstb_truetype.lua"], [63251, "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"]]
+18051 -8345
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`).
- 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.
+1982 -2021
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
====================================================
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
(missing.py also lists names the port defines differently, e.g. the *V printf variants and DragFloat2..4 / DragInt2..4,
which exist as ImGui.DragFloat2 = DragFloatN(2) etc. Those are NOT gaps.)
Re-check with: python3 tools/compare_upstream.py (writes docs/PORT_STATUS.txt: [ ] = not ported, [=] = replaced by a
Lua equivalent, "missing:" = enum values / struct fields) and python3 tools/test/undefined.py --no-demo
FEATURES NOT PORTED
-------------------
- Multi-select / box-select: BeginMultiSelect, EndMultiSelect, MultiSelectItemHeader/Footer, MultiSelectAddSetAll,
MultiSelectAddSetRange, SetNextItemSelectionUserData, BeginBoxSelect/EndBoxSelect, DebugNodeMultiSelectState.
(Selectable/TreeNode call sites are stubbed with TODOs; the demo "Selection State & Multi-Select" section is not ported.)
- Typing-select (type to jump in lists): GetTypingSelectRequest, TypingSelectFind*, DebugNodeTypingSelectState.
- Examples > Assets Browser (needs multi-select + box-select).
- Metrics > Tools > Item Picker (UpdateDebugToolItemPicker).
- DebugFlashStyleColor / UpdateDebugToolFlashStyleColor (Style Editor "?" flash button).
- DebugNodeInputTextState (Metrics > Internal state > InputText).
- BringWindowToDisplayBehind: stub, does nothing.
- ColorEdit4 / ColorPicker4: drag & drop target (dropping a color onto a color widget).
- ImFontAtlas:AddFontFromMemoryCompressedTTF: stub (binary_to_compressed_c fonts can't be loaded).
- 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).
NOT PORTED / PARTIAL
--------------------
- ImFont::AddRemapCodepointToGlyphIndex / ImGui_ImplStbTrueType_FontSrcGetGlyphIndexFromCodepoint (the port's font
loader still checks glyphs by codepoint; 1.93 split this into codepoint -> glyph index -> glyph).
- ImGui_GetDefaultCompressedFontDataProggyClean/ProggyForever: AddFontFromMemoryCompressedTTF works, but the default
fonts are loaded from fonts/ instead of embedded compressed data.
- ImFontAtlasDebugLogTextureRequests, ImHashSkipUncontributingPrefix.
- DebugLogSkippedErrors / DebugDrawIdConflicts* (error-recovery and ID-conflict debug tools).
LUA LIMITATIONS
---------------
- ImGuiDataType.S64/U64: values are Luau doubles, exact only within +-2^53 (LLONG_MAX/ULLONG_MAX round up).
- ImDrawList::AddCallback(userdata_size > 0): the "copy" is a shallow copy of a table userdata (no raw bytes).
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).
- 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
------------
- 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).
PERFORMANCE IDEAS (not done)
----------------------------
- Cheaper visible ColorEdit4 rows; cheaper Begin() for child windows.
PLANNED EXTENSIONS
------------------
- ImGuizmo, ImNodes, ImGuiColorTextEdit, ImPlot, ImPlot3D (as separate extensions/, built with a --with flag).
- 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
counter-clockwise corners are reflex, so CCW and duplicate-point polygons triangulate differently from upstream.
+1 -1
View File
@@ -17,7 +17,7 @@ setfenv(1, setmetatable({}, {
local MainScale = 1.0
local RenderScale = 1.0
local RenderRate = 0 -- 0 = every frame (tile redraws are limited by the redraw budget instead)
local RenderRate = 60 -- max tile redraws per second (0 = every frame, limited by the redraw budget instead)
local ThumbnailSize = 420
local FontSize = 13.0
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)
+1505 -219
View File
File diff suppressed because it is too large Load Diff
+10424 -6984
View File
File diff suppressed because it is too large Load Diff
+3094 -705
View File
File diff suppressed because it is too large Load Diff
+397 -67
View File
@@ -75,6 +75,14 @@ function ImU32(val) return bitAnd(val, 0xFFFFFFFF) end
--- @return ImS32
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
--- @class double : number
@@ -155,15 +163,17 @@ ImGuiDataType =
U16 = 3, -- unsigned short
S32 = 4, -- int
U32 = 5, -- unsigned int
Float = 6, -- float
Double = 7, -- double
Bool = 8, -- bool (provided for user convenience, not supported by scalar widgets)
String = 9, -- string (provided for user convenience, not supported by scalar widgets)
COUNT = 10
S64 = 6, -- long long / __int64 -- [Luau] stored in a double: exact only within +-2^53
U64 = 7, -- unsigned long long / unsigned __int64 -- [Luau] stored in a double: exact only within 0..2^53
Float = 8, -- float
Double = 9, -- double
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_
ImGuiDataType.Pointer = 11 -- [internal]
ImGuiDataType.ID = 12 -- [internal]
ImGuiDataType.Pointer = ImGuiDataType.COUNT -- [internal]
ImGuiDataType.ID = ImGuiDataType.COUNT + 1 -- [internal]
ImTextureID_Invalid = -1
@@ -194,7 +204,8 @@ ImFontAtlasFlags =
None = 0,
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)
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
@@ -498,8 +509,8 @@ end
--- @field UserCallbackData any
--- @field UserCallbackDataSize int
--- @field UserCallbackDataOffset int
local IM_DRAWCMD = {}
IM_DRAWCMD.__index = IM_DRAWCMD
MT.ImDrawCmd = {}
MT.ImDrawCmd.__index = MT.ImDrawCmd
--- @return ImDrawCmd
--- @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.
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.
}, IM_DRAWCMD)
}, MT.ImDrawCmd)
end
--- @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
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!")
@@ -605,7 +616,7 @@ function ImDrawListSplitter() return setmetatable({ _Current = 0, _Count = 0, _C
--- @field CmdBuffer ImVector<ImDrawCmd>
--- @field IdxBuffer ImVector<ImDrawIdx>
--- @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 _Data ImDrawListSharedData # Pointes to shared draw data
--- @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 _TextureStack ImVector<ImTextureRef>
--- @field _CallbacksDataBuf any
--- @field _DrawFlagsStack ImVector<ImDrawFlags>
--- @field _FringeScale float
--- @field _InvFringeScale float
--- @field _FringeScaleIsInteger bool
--- @field _OwnerName string
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!)
_ClipRectStack = ImVector(), -- [Internal]
_TextureStack = ImVector(), -- [Internal]
_CallbacksDataBuf = nil, -- [Internal]
_FringeScale = 0, -- [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content
_CallbacksDataBuf = ImVector(), -- [Internal] [Lua] copies of AddCallback() userdata (userdata_size > 0)
_DrawFlagsStack = ImVector(), -- [Internal] ImSmallStack_<ImDrawFlags,3>: capacity checked by PushDrawFlag()
_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
}, 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.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.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.Format = 0 -- w r -- ImTextureFormat.RGBA32 (default) or ImTextureFormat.Alpha8
this.Width = 0 -- w r -- Texture width
@@ -769,11 +786,14 @@ end
--- @param x int
--- @param y int
function MT.ImTextureData:GetPixels() IM_ASSERT(self.Pixels ~= nil); return self.Pixels end
function MT.ImTextureData:GetPixelsAt(x, y)
return self.Pixels, (x + y * self.Width) * self.BytesPerPixel + 1
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: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
--- @param tex_id ImTextureID
@@ -793,8 +813,8 @@ function MT.ImTextureData:SetStatus(status) self.Status = status if (status == I
--- @class ImTextureRef
--- @field _TexData? ImTextureData
--- @field _TexID ImTextureID
local IM_TEXTURE_REF = {}
IM_TEXTURE_REF.__index = IM_TEXTURE_REF
MT.ImTextureRef = {}
MT.ImTextureRef.__index = MT.ImTextureRef
--- @return ImTextureRef
--- @nodiscard
@@ -803,7 +823,14 @@ function ImTextureRef(tex_id)
-- 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.
_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
-------------------------------------------------------------------------------
@@ -1132,6 +1159,34 @@ function ImFontGlyph()
}
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
-------------------------------------------------------------------------------
@@ -1190,6 +1245,8 @@ function ImGuiIO()
-- 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()
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.
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.
@@ -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.
-- Font system
FontAllowUserScaling = false, -- = false -- Allow user scaling text of individual window with Ctrl+Wheel.
-- 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)
-- (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.
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.
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
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.
@@ -1229,6 +1291,25 @@ function ImGuiIO()
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.
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]
-- - 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).
@@ -1253,6 +1334,16 @@ function ImGuiIO()
-- - 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.
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)
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')
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)
MouseReleased = {[0] = false, [1] = false, [2] = 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
MouseClickedLastCount = {[0] = 0, [1] = 0, [2] = 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)
MouseDownDurationPrev = {[0] = -1, [1] = -1, [2] = -1}, -- Previous time the mouse button has been down
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, [3] = false, [4] = false}, -- Mouse button went 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, [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, [3] = -1, [4] = -1}, -- Duration the mouse button has been down (0.0f == just clicked)
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
MouseDragMaxDistanceSqr = {[0] = 0, [1] = 0, [2] = 0}, -- Squared maximum distance of how much mouse has traveled from the clicking point (used for moving thresholds)
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, [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.
MouseDownOwnedUnlessPopupClose = {[0] = nil, [1] = nil, [2] = 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)
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).
MouseClickedPos = {[0] = ImVec2(), [1] = ImVec2(), [2] = ImVec2()}, -- Position at time of clicking
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, [3] = nil, [4] = nil}, -- Track if button was clicked inside a dear imgui window.
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, [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(), [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
-- (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().
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.
}, MT.ImGuiViewport)
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)
Platform_GetClipboardTextFn = nil, -- Should return nil on failure (e.g. clipboard data is not text).
Platform_SetClipboardTextFn = nil,
Platform_ClipboardUserData = nil,
-- 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)
Platform_OpenInShellFn = 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
-------------------------------------------------------------------------------
@@ -1511,6 +1615,12 @@ function ImGuiPlatformIO()
-- Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure.
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
-- - 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.
@@ -1522,10 +1632,6 @@ function ImGuiPlatformIO()
-- (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.
-- 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.
}
@@ -1687,35 +1793,90 @@ ImGuiNextItemDataFlags = {
--- @enum ImDrawFlags
ImDrawFlags = {
None = 0,
-- - Rounding default to ImDrawFlags.RoundCornersAll when 'rounding > 0'.
-- - So you only need to use the ImDrawFlags.RoundCornersXXX flags if you want a special configuration (e.g. a rectangle with one rounded corner).
--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'.
-- 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?
RoundCornersTopLeft = 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).
RoundCornersBottomLeft = 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).
RoundCornersNone = bitLShift(1, 8), -- OK -- -- Disable rounding even when 'rounding > 0.0f'. This value is NOT zero, it is NOT an implicit flag!
RoundTopLeft = bitLShift(1, 4), -- OK -- -- Round top-left 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).
RoundBottomLeft = bitLShift(1, 6), -- OK -- -- Round bottom-left 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).
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?
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)
ImDrawFlags.RoundCornersBottom = bitOr(ImDrawFlags.RoundCornersBottomLeft, ImDrawFlags.RoundCornersBottomRight)
ImDrawFlags.RoundCornersLeft = bitOr(ImDrawFlags.RoundCornersBottomLeft, ImDrawFlags.RoundCornersTopLeft)
ImDrawFlags.RoundCornersRight = bitOr(ImDrawFlags.RoundCornersBottomRight, ImDrawFlags.RoundCornersTopRight)
ImDrawFlags.RoundCornersAll = bitOr(ImDrawFlags.RoundCornersTopLeft, ImDrawFlags.RoundCornersTopRight, ImDrawFlags.RoundCornersBottomLeft, ImDrawFlags.RoundCornersBottomRight) -- (Default!!)
ImDrawFlags.RoundCornersDefault_ = ImDrawFlags.RoundCornersAll
ImDrawFlags.RoundCornersMask_ = bitOr(ImDrawFlags.RoundCornersAll, ImDrawFlags.RoundCornersNone) -- [Internal]
ImDrawFlags.InvalidMask_ = 0x8000000F -- [Internal] == 0x8000000F. Reserved to detect misuses.
-- [Internal]
ImDrawFlags.RoundMask_ = bitOr(ImDrawFlags.RoundAll, ImDrawFlags.RoundNone) -- [Internal]
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.AllowInFrameScope_ = bitOr(ImDrawFlags.AllowInPushScope_, ImDrawFlags.AllowTexForRoundCorners_)
ImDrawFlags.StrokeMask_ = bitLShift(0x07, 17) -- [Internal]
ImDrawFlags.InvalidMask_ = 0x8000000F -- [Internal] == ~0x7FFFFFF0 == 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
ImDrawListFlags = {
None = 0,
AntiAliasedLines = bitLShift(1, 0),
AntiAliasedLinesUseTex = bitLShift(1, 1),
AntiAliasedFill = bitLShift(1, 2),
AllowVtxOffset = bitLShift(1, 3),
TextNoPixelSnap = bitLShift(1, 4)
None = ImDrawFlags.None,
AntiAliasedFill = ImDrawFlags.AAFill,
AntiAliasedLines = ImDrawFlags.AALines,
AntiAliasedLinesUseTex = ImDrawFlags.AALines, -- No effect, anti-aliased rendering always uses textures from 1.93+
AllowVtxOffset = ImDrawFlags.UseVtxOffset,
TextNoPixelSnap = ImDrawFlags.TextNoPixelSnap,
}
-- Font flags
@@ -1857,10 +2018,12 @@ ImGuiStyleVar = {
DragDropTargetRounding = 37, -- float DragDropTargetRounding
ButtonTextAlign = 38, -- ImVec2 ButtonTextAlign
SelectableTextAlign = 39, -- ImVec2 SelectableTextAlign
SeparatorTextBorderSize = 40, -- float SeparatorTextBorderSize
SeparatorTextAlign = 41, -- ImVec2 SeparatorTextAlign
SeparatorTextPadding = 42, -- ImVec2 SeparatorTextPadding
COUNT = 43
SeparatorSize = 40, -- float SeparatorSize
SeparatorTextBorderSize = 41, -- float SeparatorTextBorderSize
SeparatorTextAlign = 42, -- ImVec2 SeparatorTextAlign
SeparatorTextPadding = 43, -- ImVec2 SeparatorTextPadding
DockingSeparatorSize = 44, -- float DockingSeparatorSize
COUNT = 45
}
-- Flags for ImGui.IsItemHovered(), ImGui.IsWindowHovered()
@@ -1949,8 +2112,9 @@ ImGuiKey = {
RightCtrl = 531, RightShift = 532, RightAlt = 533, RightSuper = 534,
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,
["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,
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.
-- 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
IM_COL32_R_SHIFT = 0
IM_COL32_G_SHIFT = 8
@@ -2173,7 +2341,7 @@ ImGuiComboFlags = {
}
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()
--- @enum ImGuiSelectableFlags
@@ -2295,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.
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)
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?
}
end
@@ -2418,7 +2587,8 @@ function MT.ImGuiInputTextCallbackData:InsertChars(pos, new_text)
self.SelectionStart = self.CursorPos; self.SelectionEnd = self.CursorPos
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:HasSelection() return self.SelectionStart ~= self.SelectionEnd end
@@ -2617,6 +2787,17 @@ ImGuiTableBgTarget = { None = 0, RowBg0 = 1, RowBg1 = 2, CellBg = 3 }
--- @enum ImGuiSortDirection
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()
@@ -2640,6 +2821,13 @@ function ImGuiPayload_IsDelivery(p) return p.Delivery end
-- [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
-- (this could be called 'ImGuiTextBuilder' / 'ImGuiStringBuilder')
--- @class ImGuiTextBuffer # string builder: Buf is a list of string chunks
@@ -2653,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: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
--- Fns take (ctx, handler, ...) like upstream. ReadOpenFn(ctx, handler, name) returns entry; ReadLineFn(ctx, handler, entry, line);
--- WriteAllFn(ctx, handler, buf: ImGuiTextBuffer)
+557 -54
View File
@@ -258,12 +258,18 @@ end
function IM_ROUNDUP_TO_EVEN(n) return (ImCeil((n) / 2) * 2) end
function ImRsqrt(x) return (1 / ImSqrt(x)) end
-- Converge to more precise solution using single step of Newton-Raphson method, repeating increases precision
function ImRsqrtPrecise(x) return (1 / ImSqrt(x)) end -- [Luau] non-SSE upstream variant
function ImIsTruncated(x) return x % 1 == 0 end -- (float)(int)x == x
-- Returns true if all parameters are whole numbers.
function ImIsTruncated4(x, y, z, w) return x % 1 == 0 and y % 1 == 0 and z % 1 == 0 and w % 1 == 0 end
function IM_TRUNC(VAL) return math.floor(VAL) end -- Positive values only!
function IM_ROUND(VAL) return math.floor(VAL + 0.5) end -- Positive values only!
--@hoist
function ImFloor(f) if f >= 0 or math.floor(f) == f then return math.floor(f) else return math.floor(f) - 1 end end -- Decent replacement for floorf()
function ImFloor(f) return math.floor(f) end -- Decent replacement for floorf() -- [Luau] upstream: (int)f truncates, -1 for negatives == math.floor
-- Helpers: Bit manipulation
--- @param v int
@@ -386,6 +392,16 @@ end
--@hoist
function ImSaturate(f) return ((f < 0.0 and 0.0) or (f > 1.0 and 1.0) or f) end
--- @param x float
--- @return float # Sign operator - returns -1, 0 or 1 based on sign of argument
function ImSign(x) if x < 0.0 then return -1.0 elseif x > 0.0 then return 1.0 end return 0.0 end
function ImLinearRemapClamp(s0, s1, d0, d1, x) return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0 end
--- @param c int # byte
--- @return int
function ImToUpper(c) if c >= 97 and c <= 122 then return bit32.band(c, bit32.bnot(32)) end return c end
IM_F32_TO_INT8_UNBOUND = function(val) return math.floor(val * 255.0 + (val >= 0 and 0.5 or -0.5)) end -- Unsaturated, for display purpose
IM_F32_TO_INT8_SAT = function(val) return math.floor(ImSaturate(val) * 255.0 + 0.5) end -- Saturated, always output 0..255
@@ -445,6 +461,18 @@ function ImMul(lhs, rhs)
return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y)
end
--- @param a ImVec2
--- @param b ImVec2
--- @param c ImVec2
--- @return float
function ImTriangleArea(a, b, c) return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5 end
--- @param a ImVec2
--- @param b ImVec2
--- @param c ImVec2
--- @return boolean
function ImTriangleIsClockwise(a, b, c) return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) > 0.0 end
--- @param str char[]
--- @param begin? int
function ImStd.ImStrlen(str, begin)
@@ -499,6 +527,13 @@ IMGUI_WINDOW_HARD_MIN_SIZE = 4.0
IM_DRAWLIST_ARCFAST_TABLE_SIZE = 48 -- Number of samples in adaptive arc drawing lookup table.
IM_DRAWLIST_ARCFAST_SAMPLE_MAX = IM_DRAWLIST_ARCFAST_TABLE_SIZE -- Sample index _PathArcToFastEx() for 360 angle.
IM_DRAWLIST_TEX_LINES_WIDTH_MAX = 32 -- The maximum line width to bake anti-aliased textures for.
IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH = 4 -- Calculate detailed textures for width [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]
IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT = 4 -- How many samples per integer thickness level.
IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX = 16
IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX = 4 -- 0: fill, 1-3: strokes thickness
IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT = ((IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH - 1) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 1)
-- Macros used by Recoverable Error handling
-- - Only dispatch error if _EXPR: evaluate as assert (similar to an assert macro).
-- - The message will always be a string literal, in order to increase likelihood of being display by an assert handler.
@@ -658,6 +693,12 @@ end
--- @nodiscard
function ImGui.WindowPosAbsToRel(window, p) local off = window.DC.CursorStartPos return ImVec2(p.x - off.x, p.y - off.y) end
--- @param window ImGuiWindow
--- @param p ImVec2
--- @return ImVec2
--- @nodiscard
function ImGui.WindowPosRelToAbs(window, p) local off = window.DC.CursorStartPos return ImVec2(p.x + off.x, p.y + off.y) end
--- @param c char
--- @return bool # True if this character is a ' ' or '\t'
function ImCharIsBlankA(c) return c == 32 or c == 9 end
@@ -665,6 +706,27 @@ function ImCharIsBlankA(c) return c == 32 or c == 9 end
--- @param c char
function ImCharIsBlankW(c) return c == 32 or c == 9 or c == 0x3000 end
--- @param c char
function ImCharIsXdigitA(c) return (c >= 48 and c <= 57) or (c >= 65 and c <= 70) or (c >= 97 and c <= 102) end
--- @param lhs ImVec2
--- @param fail_value float
function ImInvLength(lhs, fail_value) local d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if d > 0.0 then return ImRsqrt(d) end; return fail_value end
function ImRoundPositive64(f) return _Trunc(f + 0.5) end -- Positive values only.
function ImRoundSigned64(f) if f >= 0.0 then return _Trunc(f + 0.5) else return _Trunc(f - 0.5) end end
-- [Luau] C '%' truncates toward zero; math.fmod matches it
function ImModPositive(a, b) return math.fmod(a + b, b) end
function ImIsFloatAboveGuaranteedIntegerPrecision(f) return f <= -16777216 or f >= 16777216 end
-- Helper: ImVec2i (2D vector, integer) / ImVec2ih (2D vector, half-size integer, for long-term packed storage)
-- [Luau] both are plain ImVec2 holding whole numbers
function ImVec2i(x, y) return ImVec2(x or 0, y or 0) end
--- @overload fun(rhs: ImVec2): ImVec2
function ImVec2ih(x, y)
if type(x) == "table" then return ImVec2(_Trunc(x.x), _Trunc(x.y)) end
return ImVec2(x or 0, y or 0)
end
--- @enum ImGuiNavMoveFlags
ImGuiNavMoveFlags =
{
@@ -880,6 +942,9 @@ function IM_RECT:Add(p)
end
end
function IM_RECT:AddX(x) if self.Min.x > x then self.Min.x = x end if self.Max.x < x then self.Max.x = x end end
function IM_RECT:AddY(y) if self.Min.y > y then self.Min.y = y end if self.Max.y < y then self.Max.y = y end end
--- @param amount float|ImVec2
@native
function IM_RECT:Expand(amount)
@@ -960,6 +1025,28 @@ function ImBitArraySetBit(arr, n)
arr[idx] = bitOr(arr[idx], mask)
end
-- [Luau] bit arrays are ImU32[] tables with 1-based word indices; bit indices 'n' are 1-based like the functions above
--- @param bitcount int
function ImBitArrayGetStorageSizeInBytes(bitcount) return bitLShift(bitRShift(bitcount + 31, 5), 2) end
--- @param arr ImU32[]
--- @param bitcount int
function ImBitArrayClearAllBits(arr, bitcount) for i = 1, bitRShift(bitcount + 31, 5) do arr[i] = 0 end end
function ImBitArrayTestBit(arr, n) local mask = bitLShift(1, bitAnd(n - 1, 31)); return bitAnd(arr[bitRShift(n - 1, 5) + 1], mask) ~= 0 end
function ImBitArrayClearBit(arr, n) local mask = bitLShift(1, bitAnd(n - 1, 31)); local idx = bitRShift(n - 1, 5) + 1; arr[idx] = bitAnd(arr[idx], bitNot(mask)) end
-- Works on range [n..n2)
function ImBitArraySetBitRange(arr, n, n2)
n = n - 1; n2 = n2 - 1 -- [Luau] to 0-based
n2 = n2 - 1
while n <= n2 do
local a_mod = bitAnd(n, 31)
local b_mod = ((n2 > bitOr(n, 31)) and 31 or bitAnd(n2, 31)) + 1
local mask = bitAnd(2 ^ b_mod - 1, bitNot(2 ^ a_mod - 1))
local idx = bitRShift(n, 5) + 1
arr[idx] = bitOr(arr[idx], mask)
n = bitAnd(n + 32, bitNot(31))
end
end
-- Bit array for named keys
--- @alias ImBitArrayForNamedKeys ImBitArray
@@ -986,15 +1073,34 @@ function ImBitArray(BITCOUNT, OFFSET)
--- @param n int # 1-based
this.SetBit = function(self, n) n = n + OFFSET; IM_ASSERT(n >= 1 and n <= BITCOUNT); ImBitArraySetBit(self.Data, n); end
--- @param n int # 1-based
this.ClearBit = function(self, n) n = n + OFFSET; IM_ASSERT(n >= 1 and n <= BITCOUNT); ImBitArrayClearBit(self.Data, n); end
--- @param n int # 1-based, range [n..n2)
this.SetBitRange = function(self, n, n2) n = n + OFFSET; n2 = n2 + OFFSET; IM_ASSERT(n >= 1 and n <= BITCOUNT and n2 > n and n2 <= BITCOUNT + 1); ImBitArraySetBitRange(self.Data, n, n2); end
this:ClearAllBits()
return this
end
-- Helper: ImBitVector
-- Store 1-bit per value.
--- @class ImBitVector
--- @field Storage ImVector<ImU32>
MT.ImBitVector = {}
MT.ImBitVector.__index = MT.ImBitVector
function ImBitVector() return setmetatable({ Storage = ImVector() }, MT.ImBitVector) end
function MT.ImBitVector:Create(sz) self.Storage:resize(bitRShift(sz + 31, 5)); for i = 1, self.Storage.Size do self.Storage.Data[i] = 0 end end
function MT.ImBitVector:Clear() self.Storage:clear() end
--- @param n int # 1-based
function MT.ImBitVector:TestBit(n) IM_ASSERT(n <= bitLShift(self.Storage.Size, 5)); return IM_BITARRAY_TESTBIT(self.Storage.Data, n) end
function MT.ImBitVector:SetBit(n) IM_ASSERT(n <= bitLShift(self.Storage.Size, 5)); ImBitArraySetBit(self.Storage.Data, n) end
function MT.ImBitVector:ClearBit(n) IM_ASSERT(n <= bitLShift(self.Storage.Size, 5)); ImBitArrayClearBit(self.Storage.Data, n) end
-- Stateful path API, add points then finish with PathFillConvex() or PathStroke()
-- - Important: filled shapes must always use clockwise winding order! The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing.
-- so e.g. 'PathArcTo(center, radius, PI * -0.5, PI)' is ok, whereas 'PathArcTo(center, radius, PI, PI * -0.5)' won't have correct anti-aliasing when followed by PathFillConvex().
function MT.ImDrawList:PathClear() self._Path:clear() end
function MT.ImDrawList:PathClear() self._Path.Size = 0 end
--- @param pos ImVec2
@native
@@ -1015,9 +1121,11 @@ function MT.ImDrawList:PathLineToMergeDuplicate(pos)
end
end
--- @param col ImU32
--- @param flags? ImDrawFlags
@native
function MT.ImDrawList:PathFillConvex(col)
self:AddConvexPolyFilled(self._Path.Data, self._Path.Size, col)
function MT.ImDrawList:PathFillConvex(col, flags)
self:AddConvexPolyFilled(self._Path.Data, self._Path.Size, col, flags)
self._Path.Size = 0
end
@@ -1028,10 +1136,23 @@ function MT.ImDrawList:PathStroke(col, thickness, flags)
if not thickness then thickness = 1.0 end
if not flags then flags = 0 end
self:AddPolyline(self._Path.Data, self._Path.Size, col, thickness, flags)
self:_AddPolyline(self._Path.Data, self._Path.Size, col, thickness, flags, FLT_MAX)
self._Path.Size = 0
end
-- General polygon
-- - Only simple polygons are supported by filling functions (no self-intersections, no holes).
-- - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library.
--- @param points ImVec2[] # 1-based
--- @param num_points int
--- @param col ImU32
--- @param thickness float
--- @param flags? ImDrawFlags
function MT.ImDrawList:AddPolyline(points, num_points, col, thickness, flags)
if not flags then flags = 0 end
self:_AddPolyline(points, num_points, col, thickness, flags, FLT_MAX)
end
--- @param col ImU32
function ImGui.SetNextItemColorMarker(col)
local g = GImGui
@@ -1049,15 +1170,15 @@ end
-- You may want to create your own instance of you try to ImDrawList completely without ImGui. In that case, watch out for future changes to this structure.
--- @class ImDrawListSharedData
--- @field TexUvWhitePixel ImVec2 # UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel)
--- @field TexUvLines ImVec4[] # UV of anti-aliased lines in the atlas (== FontAtlas->Builder->TexUvLines)
--- @field TexUvLines ImVec4[] # 0-based. UV of anti-aliased lines in the atlas (== FontAtlas->Builder->TexUvLines)
--- @field TexUvCorners ImVec4[] # 0-based. UV of rounded corner (== FontAtlas->Builder->TexUvCorners)
--- @field FontAtlas ImFontAtlas # Current font atlas
--- @field Font ImFont # Current font (used for simplified AddText overload)
--- @field FontSize float # Current font size (used for simplified AddText overload)
--- @field FontScale float # Current font scale (== FontSize / Font->FontSize)
--- @field CurveTessellationTol float
--- @field CurveTessellationMaxError float # Tessellation tolerance when using PathBezierCurveTo()
--- @field CircleTessellationMaxError float # Number of circle segments to use per pixel of radius for AddCircle() etc
--- @field InitialFringeScale float
--- @field InitialFlags ImDrawListFlags
--- @field InitialDrawFlags ImDrawFlags # Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards)
--- @field ClipRectFullscreen? ImVec4 # Value for PushClipRectFullscreen()
--- @field TempBuffer ImVector<ImVec2> # Temporary write buffer
--- @field DrawLists ImVector<ImDrawList> # All draw lists associated to this ImDrawListSharedData
@@ -1074,17 +1195,16 @@ function ImDrawListSharedData()
local this = setmetatable({
TexUvWhitePixel = nil, -- UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel)
TexUvLines = nil, -- UV of anti-aliased lines in the atlas (== FontAtlas->Builder->TexUvLines)
TexUvCorners = nil, -- UV of rounded corner (== FontAtlas->Builder->TexUvCorners)
FontAtlas = nil, -- Current font atlas
Font = nil, -- Current font (used for simplified AddText overload)
FontSize = 0, -- Current font size (used for simplified AddText overload)
FontScale = 0, -- Current font scale (== FontSize / Font->FontSize)
CurveTessellationTol = 0,
CurveTessellationMaxError = 0, -- Tessellation tolerance when using PathBezierCurveTo()
CircleTessellationMaxError = 0, -- Number of circle segments to use per pixel of radius for AddCircle() etc
InitialFringeScale = 1,
InitialFlags = 0,
InitialDrawFlags = 0, -- Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards)
ClipRectFullscreen = nil, -- Value for PushClipRectFullscreen()
TempBuffer = ImVector(ImVec2), -- Temporary write buffer
DrawLists = ImVector(), -- All draw lists associated to this ImDrawListSharedData
@@ -1127,6 +1247,10 @@ end
--- @field BakedDiscardedCount int
--- @field PackIdMouseCursors ImFontAtlasRectId
--- @field PackIdLinesTexData ImFontAtlasRectId
--- @field PackIdLineFractTexData ImFontAtlasRectId
--- @field PackIdCornersTexData ImFontAtlasRectId
--- @field TexUvLines ImVec4[] # 0-based. UVs for baked anti-aliased lines (u0, u1, v, 1/thickness)
--- @field TexUvCorners ImVec4[] # 0-based. UVs for baked anti-aliased corners (0= fill, 1> stroke thickness)
MT.ImFontAtlasBuilder = {}
MT.ImFontAtlasBuilder.__index = MT.ImFontAtlasBuilder
@@ -1159,6 +1283,14 @@ function ImFontAtlasBuilder()
-- Custom rectangle identifiers
this.PackIdMouseCursors = -1 -- White pixel + mouse cursors. Also happen to be fallback in case of packing failure.
this.PackIdLinesTexData = -1
this.PackIdLineFractTexData = -1
this.PackIdCornersTexData = -1
-- Cached UV coordinates
this.TexUvLines = {} -- [IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT] UVs for baked anti-aliased lines (u0, u1, v, 1/thickness) -- 0-based
this.TexUvCorners = {} -- [IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX] UVs for baked anti-aliased corners (0= fill, 1> stroke thickness) -- 0-based
for n = 0, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT do this.TexUvLines[n] = ImVec4() end
for n = 0, IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX - 1 do this.TexUvCorners[n] = ImVec4() end
return this
end
@@ -1196,9 +1328,13 @@ end
--- @param key string
--- @return ImGuiStyleVarInfo
--- @nodiscard
MT.ImGuiStyleVarInfo = {}
MT.ImGuiStyleVarInfo.__index = MT.ImGuiStyleVarInfo
function ImGuiStyleVarInfo(count, data_type, key)
return { Count = count, DataType = data_type, Key = key }
return setmetatable({ Count = count, DataType = data_type, Key = key }, MT.ImGuiStyleVarInfo)
end
-- [Luau] no pointers: returns the field value (number, or the live ImVec2 table) of 'parent' (e.g. ImGuiStyle)
function MT.ImGuiStyleVarInfo:GetVarPtr(parent) return parent[self.Key] end
-- Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable.
--- @class ImGuiStyleMod
@@ -1313,11 +1449,11 @@ end
--- @field RefVal any
--- @field StorageId ImGuiID
--- @field ColorMarker ImU32
local IMGUI_NEXT_ITEM_DATA = {}
IMGUI_NEXT_ITEM_DATA.__index = IMGUI_NEXT_ITEM_DATA
MT.ImGuiNextItemData = {}
MT.ImGuiNextItemData.__index = MT.ImGuiNextItemData
-- Also cleared manually by ItemAdd()!
function IMGUI_NEXT_ITEM_DATA:ClearFlags()
function MT.ImGuiNextItemData:ClearFlags()
self.HasFlags = ImGuiNextItemDataFlags.None
self.ItemFlagsSet = ImGuiItemFlags.None
end
@@ -1340,7 +1476,7 @@ function ImGuiNextItemData()
RefVal = 0, -- Not exposed yet, for ImGuiInputTextFlags.ParseEmptyAsRefVal
StorageId = 0, -- Set by SetNextItemStorageID()
ColorMarker = 0 -- Set by SetNextItemColorMarker(). Not exposed yet, supported by DragScalar,SliderScalar and for ImGuiSliderFlags.ColorMarkers.
}, IMGUI_NEXT_ITEM_DATA)
}, MT.ImGuiNextItemData)
end
-- Storage for SetNexWindow** functions
@@ -1363,10 +1499,10 @@ end
--- @field BgAlphaVal number?
--- @field MenuBarOffsetMinVal ImVec2?
--- @field RefreshFlagsVal int?
local IMGUI_NEXT_WINDOW_DATA = {}
IMGUI_NEXT_WINDOW_DATA.__index = IMGUI_NEXT_WINDOW_DATA
MT.ImGuiNextWindowData = {}
MT.ImGuiNextWindowData.__index = MT.ImGuiNextWindowData
function IMGUI_NEXT_WINDOW_DATA:ClearFlags()
function MT.ImGuiNextWindowData:ClearFlags()
self.HasFlags = ImGuiNextWindowDataFlags.None
end
@@ -1405,7 +1541,7 @@ function ImGuiNextWindowData()
RefreshFlagsVal = nil,
CacheKeySet = false, -- [Port] SetNextWindowCached()
CacheKeyVal = nil
}, IMGUI_NEXT_WINDOW_DATA)
}, MT.ImGuiNextWindowData)
end
--- @enum ImGuiWindowBgClickFlags
@@ -1442,6 +1578,30 @@ ImGuiLayoutType =
Vertical = 1
}
--- @enum ImGuiLogFlags
ImGuiLogFlags =
{
None = 0,
OutputTTY = bitLShift(1, 0),
OutputFile = bitLShift(1, 1),
OutputBuffer = bitLShift(1, 2),
OutputClipboard = bitLShift(1, 3),
}
ImGuiLogFlags.OutputMask_ = bitOr(ImGuiLogFlags.OutputTTY, ImGuiLogFlags.OutputFile, ImGuiLogFlags.OutputBuffer, ImGuiLogFlags.OutputClipboard)
-- Flags for internal's BeginColumns(). This is an obsolete API. Prefer using BeginTable() nowadays!
--- @enum ImGuiOldColumnFlags
ImGuiOldColumnFlags =
{
None = 0,
NoBorder = bitLShift(1, 0), -- Disable column dividers
NoResize = bitLShift(1, 1), -- Disable resizing columns when clicking on the dividers
NoPreserveWidths = bitLShift(1, 2), -- Disable column width preservation when adjusting columns
NoForceWithinWindow = bitLShift(1, 3), -- Disable forcing columns to fit within window
GrowParentContentsSize = bitLShift(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.
}
--- @enum ImGuiSeparatorFlags
ImGuiSeparatorFlags =
{
@@ -1507,11 +1667,10 @@ function ImGuiStyle()
DisplayWindowPadding = ImVec2(19, 19), -- Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows.
-- Rendering & Tessellation
AntiAliasedLines = true, -- Enable anti-aliased lines/borders. Used at the beginning of the frame to set ImDrawListFlags.AntiAliasedLines in all draw-lists.
AntiAliasedLinesUseTex = true, -- Enable anti-aliased lines/borders using textures for legacy strokes. Require backend to render with bilinear filtering (NOT point/nearest filtering).
AntiAliasedFill = true, -- Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Used at the beginning of the frame to set ImDrawListFlags.AntiAliasedFill in all draw-lists.
CurveTessellationTol = 1.25, -- Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality.
AntiAliasedLines = true, -- Enable anti-aliased lines/borders. Used at the beginning of the frame to set ImDrawFlags_AALines in all draw-lists.
AntiAliasedLineEnds = false, -- Enable anti-aliased lines/borders ends. Nicer for thick lines but more expensive. Used at the beginning of the frame to set ImDrawFlags_AALineEnds in all draw-lists.
AntiAliasedFill = true, -- Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Used at the beginning of the frame to set ImDrawFlags_AAFill in all draw-lists.
CurveTessellationMaxError = 1.12, -- Maximum error (in pixels) when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality.
CircleTessellationMaxError = 0.30, -- Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry.
-- Behaviors
@@ -1564,7 +1723,11 @@ function ImGuiStyle()
-- [Internal]
_MainScale = 1.0,
_NextFrameFontSizeBase = 0.0
_NextFrameFontSizeBase = 0.0,
-- Obsolete names (#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS)
CurveTessellationTol = 0.0, -- [OBSOLETE] Old CurveTessellationTol = CurveTessellationMaxError*CurveTessellationMaxError.
AntiAliasedLinesUseTex = true, -- [OBSOLETE] Enable anti-aliased lines/borders using textures for legacy strokes. Require backend to render with bilinear filtering (NOT point/nearest filtering).
}
-- Default theme
@@ -1586,7 +1749,8 @@ function ImGuiWindowStackData()
ParentLastItemDataBackup = ImGuiLastItemData(),
StackSizesInBegin = nil, -- Store size of various stacks for asserting
DisabledOverrideReenable = nil, -- Non-child window override disabled flag
DisabledOverrideReenableAlphaBackup = nil
DisabledOverrideReenableAlphaBackup = nil,
ParentLastComboPreviewRect = ImRect(),
}
end
@@ -1608,7 +1772,10 @@ local function ImGuiComboPreviewData()
BackupCursorMaxPos = ImVec2(),
BackupCursorPosPrevLine = ImVec2(),
BackupPrevLineTextBaseOffset = 0.0,
BackupLayout = 0
BackupLayout = 0,
BackupWorkRectMaxX = 0.0,
BackupContentRectMaxX = 0.0,
WithinPreview = false,
}
end
@@ -1665,21 +1832,30 @@ local function ImGuiInputTextState()
SelectedAllMouseLock = false, -- after a double-click to select all, we ignore further mouse drags to update selection
EditedBefore = false, -- edited since activated
EditedThisFrame = false, -- edited this frame
ValidatedThisFrame = false, -- validated this frame (e.g. Enter pressed)
WantReloadUserBuf = false, -- force a reload of user buf so it may be modified externally. may be automatic in future version.
LastMoveDirectionLR = ImGuiDir.Left, -- ImGuiDir_Left or ImGuiDir_Right. track last movement direction so when cursor cross over a word-wrapping boundaries we can display it on either line depending on last move.s
ReloadSelectionStart = 0,
ReloadSelectionEnd = 0,
}
return setmetatable(this, MT.ImGuiInputTextState)
end
function MT.ImGuiInputTextState:ClearText() self.TextLen = 0; self.TextA.Data[1] = 0; self:CursorClamp() end
function MT.ImGuiInputTextState:ClearFreeMemory() self.TextA:clear(); self.TextToRevertTo:clear() end
function MT.ImGuiInputTextState:GetText() if self.TextA.Data then return self.TextA.Data else return { 0 } end end -- [Luau] byte table (char[])
-- Internal temporary state for deactivating InputText() instances.
-- Store as part of ImGuiDeactivatedItemData?
--- @class ImGuiInputTextDeactivatedState
--- @field ID ImGuiID # widget id owning the text state (which just got deactivated)
--- @field ElapseFrame int
--- @field TextA ImVector<char> # text buffer
local IMGUI_INPUT_TEXT_DEACTIVATED_STATE = {}
IMGUI_INPUT_TEXT_DEACTIVATED_STATE.__index = IMGUI_INPUT_TEXT_DEACTIVATED_STATE
MT.ImGuiInputTextDeactivatedState = {}
MT.ImGuiInputTextDeactivatedState.__index = MT.ImGuiInputTextDeactivatedState
function IMGUI_INPUT_TEXT_DEACTIVATED_STATE:ClearFreeMemory()
function MT.ImGuiInputTextDeactivatedState:ClearFreeMemory()
self.ID = 0
self.ElapseFrame = 0
self.TextA:clear()
@@ -1692,7 +1868,7 @@ local function ImGuiInputTextDeactivatedState()
ID = 0, -- widget id owning the text state (which just got deactivated)
ElapseFrame = 0,
TextA = ImVector() -- text buffer
}, IMGUI_INPUT_TEXT_DEACTIVATED_STATE)
}, MT.ImGuiInputTextDeactivatedState)
end
--- @alias ImGuiKeyRoutingIndex ImS16
@@ -1725,10 +1901,10 @@ end
--- @field Index ImGuiKeyRoutingIndex[] # Index of first entry in Entries[]
--- @field Entries ImVector<ImGuiKeyRoutingData>
--- @field EntriesNext ImVector<ImGuiKeyRoutingData> # Double-buffer to avoid reallocation (could use a shared buffer)
local _ImGuiKeyRoutingTable = {}
_ImGuiKeyRoutingTable.__index = _ImGuiKeyRoutingTable
MT.ImGuiKeyRoutingTable = {}
MT.ImGuiKeyRoutingTable.__index = MT.ImGuiKeyRoutingTable
function _ImGuiKeyRoutingTable:Clear()
function MT.ImGuiKeyRoutingTable:Clear()
for n = 1, ImGuiKey.NamedKey_COUNT do self.Index[n] = -1 end
self.Entries:clear()
self.EntriesNext:clear()
@@ -1740,7 +1916,7 @@ local function ImGuiKeyRoutingTable()
Index = {}, -- Index of first entry in Entries[]
Entries = ImVector(),
EntriesNext = ImVector() -- Double-buffer to avoid reallocation (could use a shared buffer)
}, _ImGuiKeyRoutingTable)
}, MT.ImGuiKeyRoutingTable)
this:Clear()
@@ -1757,12 +1933,14 @@ IMGUI_TEXT_INDEX.__index = IMGUI_TEXT_INDEX
function IMGUI_TEXT_INDEX:clear() self.Offsets:clear(); self.EndOffset = 0; end
function IMGUI_TEXT_INDEX:size() return self.Offsets.Size end
-- [Port] 'n' is a 0-based line number like upstream (Offsets.Data is 1-based), 'base' the 1-based position of the buffer start; returns 1-based positions
function IMGUI_TEXT_INDEX:get_line_begin(base, n)
return base + (self.Offsets.Size ~= 0 and self.Offsets[n] or 0)
return base + (self.Offsets.Size ~= 0 and self.Offsets.Data[n + 1] or 0)
end
function IMGUI_TEXT_INDEX:get_line_end(base, n)
return base + ((n + 1 < self.Offsets.Size) and (self.Offsets[n + 1] - 1) or self.EndOffset)
return base + ((n + 1 < self.Offsets.Size) and (self.Offsets.Data[n + 2] - 1) or self.EndOffset)
end
--- @return ImGuiTextIndex
@@ -1808,8 +1986,23 @@ end
local function ImGuiMetricsConfig()
return
{
ShowDebugLog = false,
ShowIDStackTool = false,
ShowWindowsRects = false,
ShowWindowsBeginOrder = false,
ShowTablesRects = false,
ShowDrawCmdMesh = true,
ShowDrawCmdBoundingBoxes = true,
ShowTextEncodingViewer = false,
ShowTextureUsedRect = false,
ShowDockingNodes = false,
ShowWindowsRectsType = -1,
ShowTablesRectsType = -1,
HighlightMonitorIdx = -1,
HighlightViewportID = 0,
SettingsDiscardMonths = 6,
SettingsHighlightOldEntries = false,
ShowFontPreview = true,
}
end
@@ -1824,10 +2017,10 @@ end
--- @field DistBox float # Move -- Best candidate box distance to current NavId
--- @field DistCenter float # Move -- Best candidate center distance to current NavId
--- @field DistAxial float # Move -- Best candidate axial distance to current NavId
local IMGUI_NAV_ITEM_DATA = {}
IMGUI_NAV_ITEM_DATA.__index = IMGUI_NAV_ITEM_DATA
MT.ImGuiNavItemData = {}
MT.ImGuiNavItemData.__index = MT.ImGuiNavItemData
function IMGUI_NAV_ITEM_DATA:Clear()
function MT.ImGuiNavItemData:Clear()
self.Window = nil
self.ID = 0
self.FocusScopeId = 0
@@ -1840,12 +2033,170 @@ end
--- @return ImGuiNavItemData
local function ImGuiNavItemData()
local this = setmetatable({ }, IMGUI_NAV_ITEM_DATA)
local this = setmetatable({ }, MT.ImGuiNavItemData)
this.RectRel = ImRect()
this:Clear()
return this
end
-------------------------------------------------------------------------------
-- [SECTION] Typing-select support
-------------------------------------------------------------------------------
-- Flags for GetTypingSelectRequest()
--- @enum ImGuiTypingSelectFlags
ImGuiTypingSelectFlags = {
None = 0,
AllowBackspace = bitLShift(1, 0), -- Backspace to delete character inputs. If using: ensure GetTypingSelectRequest() is not called more than once per frame (filter by e.g. focus state)
AllowSingleCharMode = bitLShift(1, 1), -- Allow "single char" search mode which is activated when pressing the same character multiple times.
}
-- Returned by GetTypingSelectRequest(), designed to eventually be public.
--- @class ImGuiTypingSelectRequest
--- @field Flags ImGuiTypingSelectFlags # Flags passed to GetTypingSelectRequest()
--- @field SearchBufferLen int
--- @field SearchBuffer string # Search buffer contents (use full string. unless SingleCharMode is set, in which case use SingleCharSize).
--- @field SelectRequest bool # Set when buffer was modified this frame, requesting a selection.
--- @field SingleCharMode bool # Notify when buffer contains same character repeated, to implement special mode. In this situation it preferred to not display any on-screen search indication.
--- @field SingleCharSize int # Length in bytes of first letter codepoint (1 for ascii, 2-4 for UTF-8). If (SearchBufferLen==RepeatCharSize) only 1 letter has been input.
local function ImGuiTypingSelectRequest()
return { Flags = 0, SearchBufferLen = 0, SearchBuffer = "", SelectRequest = false, SingleCharMode = false, SingleCharSize = 0 }
end
-- Storage for GetTypingSelectRequest()
-- [Port] SearchBuffer is an immutable Lua string (C++: char[64], 63 bytes max + zero terminator). SearchBuffer[0] != 0 -> #SearchBuffer ~= 0.
--- @class ImGuiTypingSelectState
--- @field Request ImGuiTypingSelectRequest # User-facing data
--- @field SearchBuffer string # Search buffer: no need to make dynamic as this search is very transient.
--- @field FocusScope ImGuiID
--- @field LastRequestFrame int
--- @field LastRequestTime float
--- @field SingleCharModeLock bool # After a certain single char repeat count we lock into SingleCharMode. Two benefits: 1) buffer never fill, 2) we can provide an immediate SingleChar mode without timer elapsing.
MT.ImGuiTypingSelectState = {}
MT.ImGuiTypingSelectState.__index = MT.ImGuiTypingSelectState
--- @return ImGuiTypingSelectState
function ImGuiTypingSelectState()
return setmetatable({ Request = ImGuiTypingSelectRequest(), SearchBuffer = "", FocusScope = 0, LastRequestFrame = 0, LastRequestTime = 0.0, SingleCharModeLock = false }, MT.ImGuiTypingSelectState)
end
function MT.ImGuiTypingSelectState:Clear() self.SearchBuffer = ""; self.SingleCharModeLock = false end -- We preserve remaining data for easier debugging
-------------------------------------------------------------------------------
-- [SECTION] Box-select support
-------------------------------------------------------------------------------
--- @class ImGuiBoxSelectState
--- @field ID ImGuiID
--- @field IsActive bool
--- @field IsStarting bool
--- @field IsStartedFromVoid bool # Starting click was not from an item.
--- @field IsStartedSetNavIdOnce bool
--- @field RequestClear bool
--- @field KeyMods ImGuiKeyChord # Latched key-mods for box-select logic.
--- @field StartPosRel ImVec2 # Start position in window-contents relative space (to support scrolling)
--- @field EndPosRel ImVec2 # End position in window-contents relative space
--- @field ScrollAccum ImVec2 # Scrolling accumulator (to behave at high-frame spaces)
--- @field Window ImGuiWindow?
--- @field UnclipMode bool # (Temp/Transient, here in hot area). Set/cleared by the BeginMultiSelect()/EndMultiSelect() owning active box-select.
--- @field UnclipRect ImRect # Rectangle where ItemAdd() clipping may be temporarily disabled. Need support by multi-select supporting widgets.
--- @field UnclipRects ImRect[] # Per-axis versions. [Port] 1-based: [1] = upstream [0] (X), [2] = upstream [1] (Y)
--- @field BoxSelectRectPrev ImRect # Selection rectangle in absolute coordinates (derived every frame from BoxSelectStartPosRel and MousePos)
--- @field BoxSelectRectCurr ImRect
--- @return ImGuiBoxSelectState
function ImGuiBoxSelectState()
return {
-- Active box-selection data (persistent, 1 active at a time)
ID = 0, IsActive = false, IsStarting = false, IsStartedFromVoid = false, IsStartedSetNavIdOnce = false, RequestClear = false,
KeyMods = 0,
StartPosRel = ImVec2(), EndPosRel = ImVec2(), ScrollAccum = ImVec2(),
Window = nil,
-- Temporary/Transient data
UnclipMode = false,
UnclipRect = ImRect(),
UnclipRects = { ImRect(), ImRect() },
BoxSelectRectPrev = ImRect(),
BoxSelectRectCurr = ImRect(),
}
end
-------------------------------------------------------------------------------
-- [SECTION] Multi-select support
-------------------------------------------------------------------------------
-- We always assume that -1 is an invalid value (which works for indices and pointers)
-- (ImGuiSelectionUserData_Invalid is defined above with the ImGuiSelectionUserData alias)
-- Temporary storage for multi-select
--- @class ImGuiMultiSelectTempData
--- @field IO ImGuiMultiSelectIO # MUST BE FIRST FIELD. Requests are set and returned by BeginMultiSelect()/EndMultiSelect() + written to by user during the loop.
--- @field Storage ImGuiMultiSelectState?
--- @field FocusScopeId ImGuiID # Copied from g.CurrentFocusScopeId (unless another selection scope was pushed manually)
--- @field Flags ImGuiMultiSelectFlags
--- @field ScopeRectMin ImVec2
--- @field BackupCursorMaxPos ImVec2
--- @field BoxSelectId ImGuiID
--- @field KeyMods ImGuiKeyChord
--- @field LoopRequestSetAll int # -1: no operation, 0: clear all, 1: select all.
--- @field IsEndIO bool # Set when switching IO from BeginMultiSelect() to EndMultiSelect() state.
--- @field IsFocused bool # Set if currently focusing the selection scope (any item of the selection). May be used if you have custom shortcut associated to selection.
--- @field IsKeyboardSetRange bool # Set by BeginMultiSelect() when using Shift+Navigation. Because scrolling may be affected we can't afford a frame of lag with Shift+Navigation.
--- @field NavIdPassedBy bool
--- @field RangeSrcPassedBy bool # Set by the item that matches RangeSrcItem.
--- @field RangeDstPassedBy bool # Set by the item that matches NavJustMovedToId when IsSetRange is set.
--- @field IsSoleOrUnknownSelectionSize bool
MT.ImGuiMultiSelectTempData = {}
MT.ImGuiMultiSelectTempData.__index = MT.ImGuiMultiSelectTempData
--- @return ImGuiMultiSelectTempData
function ImGuiMultiSelectTempData()
local this = setmetatable({ IO = ImGuiMultiSelectIO(), ScopeRectMin = ImVec2(), BackupCursorMaxPos = ImVec2() }, MT.ImGuiMultiSelectTempData)
this:Clear()
return this
end
-- Zero-clear except IO as we preserve IO.Requests[] buffer allocation.
function MT.ImGuiMultiSelectTempData:Clear()
self:ClearIO()
self.Storage = nil
self.FocusScopeId = 0
self.Flags = 0
ImVec2_CopyV(self.ScopeRectMin, 0.0, 0.0)
ImVec2_CopyV(self.BackupCursorMaxPos, 0.0, 0.0)
self.BoxSelectId = 0
self.KeyMods = 0
self.LoopRequestSetAll = 0
self.IsEndIO = false
self.IsFocused = false
self.IsKeyboardSetRange = false
self.NavIdPassedBy = false
self.RangeSrcPassedBy = false
self.RangeDstPassedBy = false
self.IsSoleOrUnknownSelectionSize = false
end
function MT.ImGuiMultiSelectTempData:ClearIO() local io = self.IO; io.Requests:resize(0); io.RangeSrcItem = ImGuiSelectionUserData_Invalid; io.NavIdItem = ImGuiSelectionUserData_Invalid; io.NavIdSelected = false; io.RangeSrcReset = false end
-- Persistent storage for multi-select (as long as selection is alive)
--- @class ImGuiMultiSelectState
--- @field Window ImGuiWindow?
--- @field ID ImGuiID
--- @field LastFrameActive int # Last used frame-count, for GC.
--- @field LastSelectionSize int # Set by BeginMultiSelect() based on optional info provided by user. May be -1 if unknown.
--- @field RangeSelected int # -1 (don't have) or true/false
--- @field NavIdSelected int # -1 (don't have) or true/false
--- @field RangeSrcItem ImGuiSelectionUserData #
--- @field NavIdItem ImGuiSelectionUserData # SetNextItemSelectionUserData() value for NavId (if part of submitted items)
--- @return ImGuiMultiSelectState
function ImGuiMultiSelectState()
return { Window = nil, ID = 0, LastFrameActive = 0, LastSelectionSize = 0, RangeSelected = -1, NavIdSelected = -1, RangeSrcItem = ImGuiSelectionUserData_Invalid, NavIdItem = ImGuiSelectionUserData_Invalid }
end
--- @param id ImGuiID
--- @return ImGuiBoxSelectState?
function ImGui.GetBoxSelectState(id) local g = GImGui; if id ~= 0 and g.BoxSelectState.ID == id and g.BoxSelectState.IsActive then return g.BoxSelectState end return nil end
--- @param id ImGuiID
--- @return ImGuiMultiSelectState?
function ImGui.GetMultiSelectState(id) local g = GImGui; return g.MultiSelectStorage:GetByKey(id) end
-------------------------------------------------------------------------------
-- [SECTION] ImGuiContext (main Dear ImGui context)
-------------------------------------------------------------------------------
@@ -1865,7 +2216,7 @@ end
--- @field FontSize float
--- @field FontSizeBase float
--- @field FontBakedScale float
--- @field FontRasterizerDensity float
--- @field CurrentPixelDensity float
--- @field DrawListSharedData ImDrawListSharedData
--- @field NextItemData ImGuiNextItemData
--- @field LastItemData ImGuiLastItemData
@@ -2052,6 +2403,7 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
KeysRoutingTable = ImGuiKeyRoutingTable(),
InputEventsQueue = ImVector(), -- Input events which will be trickled/written into IO structure.
InputEventsTrail = ImVector(), -- Past input events processed in NewFrame(). This is to allow domain-specific application to access e.g mouse/pen trail.
InputEventsNextMouseSource = ImGuiMouseSource.Mouse,
InputEventsNextEventId = 1,
@@ -2092,6 +2444,7 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
ActiveIdPreviousFrame = 0,
ActiveIdTimer = 0.0,
ActiveIdSource = ImGuiInputSource.None, -- Activating source: ImGuiInputSource.Mouse OR ImGuiInputSource.Keyboard OR ImGuiInputSource.Gamepad
LastActiveId = 0, -- Store the last non-zero ActiveId, useful for animation.
LastActiveIdTimer = 0.0, -- Store the last non-zero ActiveId timer since the beginning of activation, useful for animation.
@@ -2127,6 +2480,7 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
NavHighlightItemUnderNav = false, -- Disable mouse hovering highlight. Highlight navigation focused item instead of mouse hovered item.
NavIdIsAlive = false, -- Nav widget has been seen this frame ~~ NavRectRel is valid
NavInputSource = ImGuiInputSource.Keyboard, -- Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource.Keyboard or ImGuiInputSource.Gamepad
NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid, -- Last valid data passed to SetNextItemSelectionUser(), or -1. For current window. Not reset when focusing an item that doesn't have selection data.
NavWindowingTarget = nil, NavWindowingTargetAnim = nil, NavWindowingListWindow = nil, -- Target window when doing Ctrl+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most!
NavWindowingTimer = 0.0, NavWindowingHighlightAlpha = 0.0,
NavWindowingToggleLayer = false, -- Set while Alt or GamepadMenu is held, may be cleared by other operations, and processed when releasing the key.
@@ -2166,7 +2520,16 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
NavActivateId = 0, NavActivateDownId = 0, NavActivatePressedId = 0, -- ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey.Space) || IsKeyDown(ImGuiKey.Enter) || IsKeyPressed(ImGuiKey.NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItemByID()
NavActivateFlags = ImGuiActivateFlags.None,
FrameCount = -1,
-- Navigation: record of last move request
NavJustMovedFromFocusScopeId = 0, -- Just navigated from this focus scope id (result of a successfully MoveRequest).
NavJustMovedToId = 0, -- Just navigated to this id (result of a successfully MoveRequest).
NavJustMovedToFocusScopeId = 0, -- Just navigated to this focus scope id (result of a successfully MoveRequest).
NavJustMovedToKeyMods = 0,
NavJustMovedToIsTabbing = false, -- Copy of ImGuiNavMoveFlags.IsTabbing. Maybe we should store whole flags.
NavJustMovedToIsInit = false, -- Copy of ImGuiNavMoveFlags.IsInit. Maybe we should store whole flags.
NavJustMovedToHasSelectionData = false, -- Copy of move result's ItemFlags & ImGuiItemFlags.HasSelectionUserData). Maybe we should just store ImGuiNavItemData.
FrameCount = 0,
FrameCountEnded = -1,
FrameCountPlatformEnded = -1,
@@ -2197,7 +2560,7 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
FontRefSize = 0.0,
FontRasterizerDensity = 1.0,
CurrentPixelDensity = 1.0,
FontAtlases = ImVector(), -- List of font atlases used by the context (generally only contains g.IO.Fonts aka the main font atlas)
@@ -2226,6 +2589,8 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
MouseCursor = ImGuiMouseCursor.Arrow,
MouseStationaryTimer = 0.0, -- Time the mouse has been stationary (with some loose heuristic)
TypingSelectState = ImGuiTypingSelectState(), -- State for GetTypingSelectRequest()
InputTextState = ImGuiInputTextState(),
InputTextLineIndex = ImGuiTextIndex(), -- Temporary storage
InputTextDeactivatedState = ImGuiInputTextDeactivatedState(),
@@ -2244,9 +2609,16 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
DockContext = ImGuiDockContext(),
DockNodeWindowMenuHandler = nil,
DebugHoveredDockNode = nil, -- Hovered dock node.
CurrentTabBarStack = ImVector(), -- holds ImGuiTabBar references directly (no ImGuiPtrOrIndex needed in Lua)
CurrentTabBarStack = ImVector(), -- ImVector<ImGuiPtrOrIndex>
ShrinkWidthBuffer = ImVector(), -- ImGuiShrinkWidthItem
-- Multi-Select state
BoxSelectState = ImGuiBoxSelectState(),
CurrentMultiSelect = nil,
MultiSelectTempDataStacked = 0, -- Temporary multi-select data size (because we leave previous instances undestructed, we generally don't use MultiSelectTempData.Size)
MultiSelectTempData = ImVector(), -- ImVector<ImGuiMultiSelectTempData>, 1-based
MultiSelectStorage = ImPool(ImGuiMultiSelectState), -- ImPool<ImGuiMultiSelectState>
WindowResizeBorderExpectedRect = ImRect(), -- Expected border rect, switch to relative edit if moving
WindowResizeRelativeMode = false,
@@ -2269,6 +2641,7 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
DisabledAlphaBackup = 0.0, -- Backup for style.Alpha for BeginDisabled()
DisabledStackSize = 0,
MixedValueLabel = "-", -- Value replacement when displaying a mixed value.
ItemFlagsStack = ImVector(), -- Stack for PushItemFlag()/PopItemFlag() - inherited by Begin()
GroupStack = ImVector(), -- Stack for BeginGroup()/EndGroup() - not inherited by Begin()
@@ -2290,6 +2663,8 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
-- Hooks
Hooks = ImVector(), -- Hooks for extensions (e.g. test engine)
HookIdNext = 0, -- Next available HookId
SessionDate = ImGuiPackedDate(), -- Packed copy of platform_io.Platform_SessionDate, when valid.
DemoMarkerCallback = nil, -- [Luau] fun(file, line, section)
-- Drag and Drop
DragDropActive = false,
@@ -2337,10 +2712,29 @@ function ImGuiContext(shared_font_atlas) -- TODO: tidy up / complete this struct
LocalizationTable = {},
DebugLogBuf = ImGuiTextBuffer(),
DebugLogLines = { "" }, -- [Luau] upstream DebugLogIndex: Lua array of complete lines (without '\n'), last one being filled
DebugLogFlags = bitOr(ImGuiDebugLogFlags.EventError, ImGuiDebugLogFlags.OutputToTTY),
DebugFlashStyleColorIdx = nil,
DebugFlashStyleColorIdx = ImGuiCol.COUNT, -- (Keep close to ColorStack to share cache line)
DebugBeginReturnValueCullDepth = -1, -- Cycle between 0..9 then wrap around.
DebugLocateId = 0, -- Storage for DebugLocateItemOnHover() feature: this is read by ItemAdd() so we keep it in a hot/cached location
DebugLogAutoDisableFlags = ImGuiDebugLogFlags.None,
DebugLogAutoDisableFrames = 0,
DebugLocateFrames = 0, -- For DebugLocateItemOnHover(). This is used together with DebugLocateId which is in a hot/cached spot above.
DebugItemPickerActive = false, -- Item picker is active (started with DebugStartItemPicker())
DebugItemPickerMouseButton = ImGuiMouseButton.Left,
DebugItemPickerBreakId = 0, -- Will call IM_DEBUG_BREAK() when encountering this ID
DebugFlashStyleColorTime = 0.0,
DebugFlashStyleColorBackup = ImVec4(0, 0, 0, 0),
-- Same as DebugBreakClearData(). Those fields are scattered in their respective subsystem to stay in hot-data locations
DebugBreakInLocateId = false, -- Debug break in ItemAdd() call for g.DebugLocateId.
DebugBreakKeyChord = ImGuiKey.Pause, -- = ImGuiKey_Pause
DebugBreakInShortcutRouting = ImGuiKey.None, -- Set to break in SetShortcutRouting()/Shortcut() calls.
DebugBreakCallback = nil, -- [Luau] fun(ctx): called by IM_DEBUG_BREAK() (Lua can't break into a debugger)
DebugMetricsConfig = ImGuiMetricsConfig(),
DebugItemPathQuery = ImGuiDebugItemPathQuery(),
DebugIDStackTool = ImGuiIDStackTool(),
DebugHookIdInfoId = 0, -- Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool
DebugAllocInfo = ImGuiDebugAllocInfo(),
@@ -2399,10 +2793,10 @@ end
--- @field WantApply bool # Set when loaded from .ini data
--- @field WantDelete bool # Set to invalidate/delete the settings entry
--- @field Name string # Window name
local IMGUI_WINDOW_SETTINGS = {}
IMGUI_WINDOW_SETTINGS.__index = IMGUI_WINDOW_SETTINGS
MT.ImGuiWindowSettings = {}
MT.ImGuiWindowSettings.__index = MT.ImGuiWindowSettings
function IMGUI_WINDOW_SETTINGS:GetName()
function MT.ImGuiWindowSettings:GetName()
return self.Name
end
@@ -2421,8 +2815,21 @@ function ImGuiWindowSettings()
DockId = 0, -- ID of last known DockNode (even if the DockNode is invisible because it has only 1 active window), or 0 if none.
DockOrder = -1, -- Order of the last time the window was visible within its DockNode. This is used to reorder windows that are reappearing on the same frame. Same value between windows that were active and windows that were none are possible.
ClassId = 0, -- ID of window class if specified
LastUsedDate = 0,
}, IMGUI_WINDOW_SETTINGS)
LastUsedDate = 0, -- [Luau] stored unpacked (YYYYMMDD int, 0 = invalid) instead of ImGuiPackedDate
}, MT.ImGuiWindowSettings)
end
--- @class ImGuiSettingsCleanupArgs
function ImGuiSettingsCleanupArgs()
return {
TypeHashFilter = 0, -- Set to restrict cleanup to a given .ini type, e.g. == ImHashStr("Window"), ImHashStr("Table"). Otherwise every types supporting Cleanup will be affected.
DiscardOlderThanMonths = 0, -- Enable to discard entries older than XX months.
DiscardWhenMissingDate = false, -- Enable to discard entries missing a date.
DiscardAll = false, -- Enable to discard all entries = same as calling ClearIniSettings() except it may be filtered.
SetCurrentSessionDateToAll = false, -- Enable to write current SessionDate to all supporting entries. // Let us know in #9460 if you use this.
SetCurrentSessionDateWhenMissingDate = false, -- Enable to write current SessionDate to all supporting entries missing a date. // Let us know in #9460 if you use this.
_DiscardOlderThanDate = 0, -- [Internal]
}
end
-- Simple column measurement, currently used for MenuItem() only.. This is very short-sighted/throw-away code and NOT a generic helper.
@@ -2798,6 +3205,7 @@ end
--- @field LastAlpha float
--- @field LastFocusedHadNavWindow bool
--- @field PlatformMonitor short
--- @field BgFgDrawListsLastFrameActive [int, int] # 1-based
--- @field BgFgDrawListsLastTimeActive [int, int] # 1-based
--- @field BgFgDrawLists [ImDrawList, ImDrawList] # 1-based
--- @field DrawDataP ImDrawData
@@ -2877,6 +3285,7 @@ function ImGuiViewportP()
this.LastFocusedHadNavWindow = false -- Instead of maintaining a LastFocusedWindow (which may harder to correctly maintain), we merely store weither NavWindow ~= nil last time the viewport was focused.
this.PlatformMonitor = -1
this.BgFgDrawListsLastFrameActive = {-1, -1} -- Last frame the background (1) and foreground (2) draw lists were used.
this.BgFgDrawListsLastTimeActive = {-1, -1} -- Timestamps for Gc.
this.BgFgDrawLists = {nil, nil} -- Convenience background [1] and foreground [2] draw lists. We use them to draw software mouser cursor when io.MouseDrawCursor is set and to draw most debug overlays.
this.DrawDataP = ImDrawData()
@@ -3138,6 +3547,8 @@ function ImGuiInputEvent()
end
-- [Internal] Key ranges
ImGuiKey_LegacyNativeKey_BEGIN = 0
ImGuiKey_LegacyNativeKey_END = 512
ImGuiKey_Keyboard_BEGIN = ImGuiKey.NamedKey_BEGIN
ImGuiKey_Keyboard_END = ImGuiKey.GamepadStart
ImGuiKey_Gamepad_BEGIN = ImGuiKey.GamepadStart
@@ -3462,5 +3873,97 @@ end
--- @nodiscard
function ImGuiFocusScopeData() return { ID = nil, WindowID = nil } end
-------------------------------------------------------------------------------
-- [Luau] Inline struct helpers / constructors from imgui.h + imgui_internal.h
-------------------------------------------------------------------------------
-- Helper for running a block of code not more than once a frame
-- [Luau] no operator bool(): call the object, e.g. 'if once() then ... end'
MT.ImGuiOnceUponAFrame = {}
MT.ImGuiOnceUponAFrame.__index = MT.ImGuiOnceUponAFrame
MT.ImGuiOnceUponAFrame.__call = function(self) local current_frame = ImGui.GetFrameCount(); if self.RefFrame == current_frame then return false end; self.RefFrame = current_frame; return true end
function ImGuiOnceUponAFrame() return setmetatable({ RefFrame = -1 }, MT.ImGuiOnceUponAFrame) end
-- [Internal] ImGuiTextFilter item
-- [Luau] stores the item text as a Lua string (upstream: Begin pointer + Len)
--- @param buf string
--- @param b int # 1-based
--- @param e int # exclusive
function ImGuiTextFilterItem(buf, b, e) IM_ASSERT(e > b); return { Text = string.sub(buf, b, e - 1), CountInclude = 0, MatchIncludesMask = 0 } end
-- A date in YYYYMMDD format packed into 16-bits
--- @class ImGuiPackedDate
MT.ImGuiPackedDate = {}
MT.ImGuiPackedDate.__index = MT.ImGuiPackedDate
--- @param yyyymmdd? int
function ImGuiPackedDate(yyyymmdd)
local this = setmetatable({ Year = 0, Month = 0, Day = 0 }, MT.ImGuiPackedDate)
if yyyymmdd ~= nil then -- Pack
this.Year = bitAnd(math.floor(yyyymmdd / 10000) - 2000, 0x7F) -- Year since 2000 (7 bits)
this.Month = bitAnd(math.floor(yyyymmdd / 100) % 100, 0x0F) -- Month (1-12) (4 bits)
this.Day = bitAnd(yyyymmdd % 100, 0x1F) -- Day (1-31) (5 bits)
end
return this
end
function MT.ImGuiPackedDate:IsValid() return self.Year ~= 0 and self.Month ~= 0 and self.Day ~= 0 end
function MT.ImGuiPackedDate:Unpack() if self.Year ~= 0 and self.Month ~= 0 and self.Day ~= 0 then return ((self.Year + 2000) * 10000) + (self.Month * 100) + self.Day end; return 0 end -- Unpack
function MT.ImGuiPackedDate:SubtractMonths(m) while m > 0 do if self.Month == 1 then self.Year = bitAnd(self.Year - 1, 0x7F) end; self.Month = (self.Month == 1) and 12 or self.Month - 1; m = m - 1 end end -- FIXME-OPT: Stupid but enough for what we do with it.
-- Pointer or index, e.g. g.CurrentTabBarStack[]
-- [Luau] overload by argument type: number = index, otherwise pointer
function ImGuiPtrOrIndex(ptr_or_index)
if type(ptr_or_index) == "number" then return { Ptr = nil, Index = ptr_or_index } end
return { Ptr = ptr_or_index, Index = -1 }
end
-- Note that Max is exclusive, so perhaps should be using a Begin/End convention.
--- @class ImGuiListClipperRange
MT.ImGuiListClipperRange = {}
function MT.ImGuiListClipperRange.FromIndices(min, max) return { Min = min, Max = max, PosToIndexConvert = false, PosToIndexOffsetMin = 0, PosToIndexOffsetMax = 0 } end
function MT.ImGuiListClipperRange.FromPositions(y1, y2, off_min, off_max) return { Min = _Trunc(y1), Max = _Trunc(y2), PosToIndexConvert = true, PosToIndexOffsetMin = off_min, PosToIndexOffsetMax = off_max } end
-- Temporary clipper data, buffers shared/reused between instances
--- @class ImGuiListClipperData
MT.ImGuiListClipperData = {}
MT.ImGuiListClipperData.__index = MT.ImGuiListClipperData
function ImGuiListClipperData() return setmetatable({ ListClipper = nil, LossynessOffset = 0.0, StepNo = 0, ItemsFrozen = 0, Ranges = ImVector() }, MT.ImGuiListClipperData) end
function MT.ImGuiListClipperData:Reset(clipper) self.ListClipper = clipper; self.StepNo = 0; self.ItemsFrozen = 0; self.Ranges:resize(0) end
-- [Luau] Desc holds the description string (upstream: DescOffset into ImGuiDebugItemPathQuery::ResultsDescBuf)
function ImGuiStackLevelInfo() return { ID = 0, QueryFrameCount = 0, QuerySuccess = false, DataType = -1, Desc = nil } end
-- [Luau] Results is a 1-based Lua array of ImGuiStackLevelInfo; ResultsDescBuf/ResultPathBuf are replaced by Lua strings.
function ImGuiDebugItemPathQuery() return { MainID = 0, Active = false, Complete = false, Step = 0, Results = {} } end
function ImGuiIDStackTool() return { OptHexEncodeNonAsciiChars = true, OptCopyToClipboardOnCtrlC = false, LastActiveFrame = -1, CopyToClipboardLastTime = -FLT_MAX } end
--- @enum ImGuiContextHookType
ImGuiContextHookType = { NewFramePre = 0, NewFramePost = 1, EndFramePre = 2, EndFramePost = 3, RenderPre = 4, RenderPost = 5, Shutdown = 6, PendingRemoval_ = 7 }
--- @class ImGuiContextHook
--- @field HookId ImGuiID -- A unique ID assigned by AddContextHook()
--- @field Type ImGuiContextHookType
--- @field Owner ImGuiID
--- @field Callback fun(ctx: ImGuiContext, hook: ImGuiContextHook)?
--- @field UserData any
function ImGuiContextHook() return { HookId = 0, Type = ImGuiContextHookType.NewFramePre, Owner = 0, Callback = nil, UserData = nil } end
-------------------------------------------------------------------------------
-- [Luau] ImGui:: inline helpers from imgui_internal.h
-------------------------------------------------------------------------------
function ImGui.GetCurrentTable() local g = GImGui; return g.CurrentTable end
--- @param window ImGuiWindow
--- @param parent_window ImGuiWindow?
function ImGui.SetWindowParentWindowForFocusRoute(window, parent_window) window.ParentWindowForFocusRoute = parent_window end -- You may also use SetNextWindowClass()'s FocusRouteParentWindowId field.
function ImGui.GetPixelDensity() return GImGui.CurrentPixelDensity end
function ImGui.ScrollToBringRectIntoView(window, rect) ImGui.ScrollToRect(window, rect, ImGuiScrollFlags.KeepVisibleEdgeY) end
function ImGui.GetItemStatusFlags() local g = GImGui; return g.LastItemData.StatusFlags end
function ImGui.GetActiveID() local g = GImGui; return g.ActiveId end
function ImGui.GetFocusID() local g = GImGui; return g.NavId end
function ImGui.IsLegacyKey(key) return key >= ImGuiKey_LegacyNativeKey_BEGIN and key < ImGuiKey_LegacyNativeKey_END end
function ImGui.GetWindowDockNode() local g = GImGui; return g.CurrentWindow.DockNode end
function ImGui.GetCurrentFocusScope() local g = GImGui; return g.CurrentFocusScopeId end -- Focus scope we are outputting into, set by PushFocusScope()
function ImGui.GetCurrentTabBar() local g = GImGui; return g.CurrentTabBar end
-- This may be useful to apply workaround that a based on distinguish whenever an item is active as a text input field.
function ImGui.IsItemActiveAsInputText() local g = GImGui; return g.ActiveId ~= 0 and g.ActiveId == g.LastItemData.ID and g.InputTextState.ID == g.LastItemData.ID end
return true -- [Roblox] ModuleScripts must return exactly one value
+57 -24
View File
@@ -331,9 +331,13 @@ end
-- Storage for a table .ini settings
--- @class ImGuiTableSettings
MT.ImGuiTableSettings = {}
MT.ImGuiTableSettings.__index = MT.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
-- [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
--- @class ImGuiOldColumnData
@@ -348,16 +352,6 @@ function ImGuiOldColumns()
HostBackupClipRect = ImRect(), HostBackupParentWorkRect = ImRect(), Columns = ImVector(), Splitter = ImDrawListSplitter() }
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)
local function Rect(r) return ImRect(r.Min.x, r.Min.y, r.Max.x, r.Max.y) end
@@ -1539,7 +1533,7 @@ function ImGui.TableUpdateLayout(table_)
local inner_window = table_.InnerWindow
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)
end
@@ -4237,6 +4231,7 @@ function ImGui.TableSaveSettings(table_)
table_.SettingsOffset = SettingsTablesOffsetFromPtr(settings)
end
settings.ColumnsCount = table_.ColumnsCount
settings.LastUsedDate = GImGui.SessionDate:Unpack()
-- Serialize ImGuiTable/ImGuiTableColumn into ImGuiTableSettings/ImGuiTableColumnSettings
IM_ASSERT(settings.ID == table_.ID)
@@ -4305,6 +4300,7 @@ function ImGui.TableLoadSettings(table_)
table_.SettingsLoadedFlags = settings.SaveFlags
table_.RefScale = settings.RefScale
settings.LastUsedDate = GImGui.SessionDate:Unpack()
-- TableUpdateLayout() will then call TableLoadSettingsForColumns() to apply the data.
end
@@ -4394,6 +4390,17 @@ function ImGui.TableLoadSettingsForColumn(column, column_settings, load_flags)
column.SortDirection = column_settings.SortDirection
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)
function ImGui.TableFixDisplayOrder(table_)
if band(table_.Flags, ImGuiTableFlags.Reorderable) == 0 then
@@ -4408,17 +4415,7 @@ function ImGui.TableFixDisplayOrder(table_)
fdo_columns[n + 1] = n
end
-- Sort by DisplayOrder and then Index
-- Assume ImGuiTableColumnIdx == signed short, to turn -1 into a large value so that it always sort after.
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)
table.sort(fdo_columns, function(lhs_idx, rhs_idx) return TableFixDisplayOrderComparer(table_, lhs_idx, rhs_idx) end)
for n = 0, table_.ColumnsCount - 1 do
table_.Columns[fdo_columns[n + 1]].DisplayOrder = n
end
@@ -4427,6 +4424,27 @@ function ImGui.TableFixDisplayOrder(table_)
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)
for i = 0, g.Tables:GetMapSize() - 1 do
local t = g.Tables:GetByIndex(i)
@@ -4470,7 +4488,7 @@ local function TableSettingsHandler_ReadLine(ctx, handler, settings, line)
local f = string.match(line, "^RefScale=([%-%d%.eE+]+)")
if f then settings.RefScale = tonumber(f); return end
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+)(.*)$")
if column_n then
@@ -4552,6 +4570,7 @@ function ImGui.TableSettingsAddSettingsHandler()
ini_handler.ReadLineFn = TableSettingsHandler_ReadLine
ini_handler.ApplyAllFn = TableSettingsHandler_ApplyAll
ini_handler.WriteAllFn = TableSettingsHandler_WriteAll
ini_handler.CleanupFn = TableSettingsHandler_Cleanup
ImGui.AddSettingsHandler(ini_handler)
end
@@ -4615,6 +4634,20 @@ function ImGui.TableGcCompactSettings()
ForEachTable(g, function(t) t.SettingsOffset = -1 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.
-- (This is a legacy API, prefer using BeginTable/EndTable!)
+1815 -221
View File
File diff suppressed because it is too large Load Diff
+31 -14
View File
@@ -3,7 +3,8 @@
-- Supports:
-- - %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)
--
-- 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_d = _byte'd'
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_x, CHAR_X = _byte'x', _byte'X'
@@ -37,10 +41,15 @@ local function hex_digit_to_int(c)
end
-- 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 val > INT_MAX then return INT_MAX end
if val < INT_MIN then return INT_MIN end
local max = is_64 and 9223372036854775807 or INT_MAX
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
return val
@@ -104,22 +113,27 @@ local function sscanf(buffer, buffer_begin, format, result)
width = math.huge
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 base
local is_float = false
if spec == CHAR_x or spec == CHAR_X then
base = 16
elseif spec == CHAR_d then
elseif spec == CHAR_d or spec == CHAR_u then
base = 10
elseif spec == CHAR_f or spec == CHAR_F then
is_float = true
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
is_float = true -- TODO: distinguish %lf from %f?
else
return items_matched
end
@@ -197,11 +211,14 @@ local function sscanf(buffer, buffer_begin, format, result)
val = val * base + sign * hex_digit_to_int(c)
local old_val = val
val = clamp_if_overflow(spec, val)
val = clamp_if_overflow(spec, val, is_64)
if old_val ~= val then
break
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
result[assigned + 1] = val
+107 -21
View File
@@ -10,6 +10,7 @@
# 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
@@ -21,6 +22,62 @@ LUA_CLASS_ALIAS = {"IM_RECT": "ImRect", "IM_VECTOR": "ImVector", "IM_POOL": "ImP
"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
# ---------------------------------------------------------------------------------------------------------------------
@@ -63,6 +120,7 @@ def cpp_tokens(src):
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_):
@@ -173,7 +231,8 @@ def parse_cpp(fname):
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:
names = [x for x in pre if re.match(r"[A-Za-z_]\w*$", x) and x not in ("struct", "class", "union", "IMGUI_API", "typedef", "final", "public", "private", "alignas")]
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)
@@ -187,11 +246,12 @@ def parse_cpp(fname):
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", "IM_MSVC_RUNTIME_CHECKS_OFF", "IM_MSVC_RUNTIME_CHECKS_RESTORE"):
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
@@ -268,6 +328,10 @@ def parse_lua():
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]):
@@ -372,6 +436,9 @@ def main():
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)
@@ -385,7 +452,9 @@ def main():
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: return 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")]
@@ -422,16 +491,24 @@ def main():
else: structs_all[k] = v
decls_by_file[f] = decls
tot = {"n": 0, "have": 0, "stub": 0, "size": 0, "size_have": 0, "simw": 0.0, "size_sim": 0}
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}
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
st["n"] += 1; st["size"] += len(un)
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
@@ -470,34 +547,41 @@ def main():
else: missing.append(v)
enum_rows.append((nm, have, counted, missing))
field_rows, field_n, field_have = [], 0, 0
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]]
if not fl: continue
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_rows.append((nm, len(fl) - len(missing), len(fl), missing))
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_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" % ("upstream file", "funcs", "ported", "by size", "similarity", "stubs"))
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" % (f, s["n"], pct(s["have"], s["n"]), pct(s["size_have"], s["size"]),
pct(s["simw"], s["size_sim"]), s["stub"]))
head.append("%-22s %9d %9s %9s %11s %9d" % ("TOTAL", tot["n"], pct(tot["have"], tot["n"]), pct(tot["size_have"], tot["size"]),
pct(tot["simw"], tot["size_sim"]), tot["stub"]))
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 = []
@@ -509,6 +593,7 @@ def main():
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")
@@ -516,7 +601,7 @@ def main():
L.append("")
L += head
L.append("")
L.append("Legend: [x] ported (sim %) [~] stub [ ] not ported [-] excluded (c++-only / obsolete / os-specific / #if 0 / test-engine)")
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)
@@ -530,7 +615,7 @@ def main():
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)")
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))
@@ -547,10 +632,11 @@ def main():
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). Field names are matched globally, so common names may over-count.")
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 in field_rows:
L.append(" %-40s %4d / %4d%s" % (nm, h, n, (" missing: " + ", ".join(missing)) if missing else ""))
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")
+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")/../../.."
python3 tools/bundle.py --target love >/dev/null
nix-shell -p luajit --run "luajit tools/test/love/t_drawhash.lua 150 Widgets,Basic && luajit tools/test/love/t_drawhash.lua 150 all && luajit tools/test/love/t_examples.lua"
echo "expected (Luau, tools/test/scripts/t_drawhash.luau): 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.
for _, ex in ipairs({ "examples/example_love", "examples/example_shared" }) do
-- Runs examples/example_love main.lua headless under LuaJIT (mock love) for some frames.
for _, ex in ipairs({ "examples/example_love" }) do
dofile("tools/test/love/mock_love.lua")
love.filesystem.getSource = function() return ex end
love.filesystem.load = function(p) return assert(loadfile(ex .. "/" .. p)) end
+1 -1
View File
@@ -7,7 +7,7 @@ io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.DockingEnable)
G.ImGui_ImplRoblox.Init()
local names = { "ShowExampleAppConsole", "ShowExampleAppLog", "ShowExampleAppLayout", "ShowExampleAppPropertyEditor", "ShowExampleAppLongText",
"ShowExampleAppAutoResize", "ShowExampleAppConstrainedResize", "ShowExampleAppSimpleOverlay", "ShowExampleAppWindowTitles",
"ShowExampleAppCustomRendering", "ShowExampleAppDocuments", "ShowExampleAppDockSpace", "ShowExampleAppFullscreen", "ShowExampleAppAssetsBrowser", "ShowExampleAppMainMenuBar" }
"ShowExampleAppCustomRendering", "ShowExampleAppDocuments", "ShowExampleAppDockSpace", "ShowExampleAppFullscreen", "ShowExampleAppAssetsBrowser", "ShowExampleAppMainMenuBar", "ShowExampleAppImageViewer" }
local tools = { "ShowMetricsWindow", "ShowDebugLogWindow", "ShowIDStackToolWindow", "ShowAboutWindow" }
local open = {}
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)
+88 -3
View File
@@ -1,7 +1,12 @@
-- Regressions vs upstream: clipboard paste filter (InputTextEx), ImParseFormatTrimDecorations (Ctrl+click temp input), ShrinkWidths tie order
-- 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(); G.ImGui_ImplRoblox.Init()
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
@@ -14,6 +19,12 @@ 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)
@@ -30,6 +41,12 @@ local function click(name, ctrl)
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
@@ -42,16 +59,84 @@ game:GetService("RunService").Stepped:Connect(function()
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 == 20 and #failed == 0 then print("FIXES OK") 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;[email protected]+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))
+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]
name = "4dbug/imgui-luau"
version = "0.2.0"
version = "0.3.0"
registry = "https://github.com/UpliftGames/wally-index"
realm = "shared"
license = "MIT"