15 Commits
Author SHA1 Message Date
4DBug ce82582e37 0.1.4
Build & Release / build (push) Waiting to run
2026-10-14 10:32:10 -05:00
4DBug 090525076f InputText: fix clipboard paste (filter was inverted, then off-by-one in the filtered buffer); ImParseFormatTrimDecorations: return [start, end) like upstream (Ctrl+click temp input showed decorations / crashed on "%d%%"); ShrinkWidths: tie order by Index descending like upstream; t_fixes test 2026-10-14 10:32:10 -05:00
4DBug 95fdf94912 Comments: mirror upstream imgui.cpp/.h, imgui_internal.h, widgets/draw/tables/demo and imstb_* comments (comment-only, code tokens unchanged)
Build & Release / build (push) Waiting to run
2026-10-14 10:23:55 -05:00
4DBug e502866075 CI: minified builds (ImGui.min.luau, ImGui-nodemo.min.luau, imgui_love.min.lua, imgui_love-nodemo.min.lua) via tools/minify.py; checks + release assets; run.py MINIFY=1 2026-10-14 10:23:55 -05:00
4DBug 1431d2e19d 0.1.3
Build & Release / build (push) Waiting to run
2026-10-14 06:26:28 -05:00
4DBug 7eb7d5fcbb Docking: set node.LastBgColor (closing dock hosts drew white bodies), host skips own bg, fix SetNextWindowBgAlpha; Roblox backend: frames skipped by the render rate cap are drawn later (UI could freeze when the app stopped rendering); t_dock_close test 2026-10-14 06:26:28 -05:00
4DBug 2558c30d32 0.1.2
Build & Release / build (push) Canceled after 0s
2026-10-09 02:24:21 -05:00
4DBug e228ed8638 Roblox backend: text proxy is opt-in (SetTextProxy), remove SetTextProxyDebug; new example main.client.luau 2026-10-09 02:22:30 -05:00
4DBug 28dc08e043 README: move 'How it works' below Getting Started
Build & Release / build (push) Canceled after 0s
2026-10-08 20:57:31 -05:00
4DBug f63a37f07d README: Wally package link; 0.1.1
Build & Release / build (push) Canceled after 0s
2026-10-08 20:54:31 -05:00
4DBug 08356b4f1f Wally: scope 4dbug (must match the GitHub account)
Build & Release / build (push) Canceled after 0s
2026-10-08 20:52:52 -05:00
4DBug 14e6da3b29 SetNextWindowCached(key): opt-in cached windows on top of the refresh policy (skip contents while idle), t_cached_window test, README snippet
Build & Release / build (push) Canceled after 0s
2026-10-08 20:43:56 -05:00
4DBug df4cf4cd80 Luau types: generated typed entry module build/ImGuiTyped.luau (requires the bundle as child Impl); Rojo/Wally use it 2026-10-08 20:36:27 -05:00
4DBug 2bef2ad43a Wally package (bug/imgui-luau), published by CI on v* tags 2026-10-08 20:30:22 -05:00
4DBug df9f194d87 README: AI disclosure wording; .gitignore 2026-10-08 20:29:29 -05:00
34 changed files with 32685 additions and 8306 deletions
+40 -24
View File
@@ -10,7 +10,7 @@ Dear ImGui for Roblox (and more)
A Lua/Luau port of [Dear ImGui](https://github.com/ocornut/imgui) (docking branch, 1.93 WIP). **Roblox is the primary target** (rendered with EditableImages); the same sources also run on [LÖVE](https://love2d.org) (LuaJIT, GPU renderer), and the backend layout leaves room for more. It follows the C++ source function by function, so upstream documentation, examples and the demo apply almost directly. A Lua/Luau port of [Dear ImGui](https://github.com/ocornut/imgui) (docking branch, 1.93 WIP). **Roblox is the primary target** (rendered with EditableImages); the same sources also run on [LÖVE](https://love2d.org) (LuaJIT, GPU renderer), and the backend layout leaves room for more. It follows the C++ source function by function, so upstream documentation, examples and the demo apply almost directly.
| [The Pitch](#the-pitch) - [Usage](#usage) - [How it works](#how-it-works) - [Demo](#demo) - [Getting Started & Integration](#getting-started--integration) - [Backends](#backends) | | [The Pitch](#the-pitch) - [Usage](#usage) - [Demo](#demo) - [Getting Started & Integration](#getting-started--integration-roblox) - [How it works](#how-it-works-roblox-backend) - [Backends](#backends) |
:----------------------------------------------------------: | :----------------------------------------------------------: |
| [Differences from C++](#differences-from-c-dear-imgui) - [Performance](#performance) - [Testing](#testing) - [Not ported yet](#not-ported-yet) | | [Differences from C++](#differences-from-c-dear-imgui) - [Performance](#performance) - [Testing](#testing) - [Not ported yet](#not-ported-yet) |
| [FAQ](#faq) - [Credits](#credits) - [License](#license) | | [FAQ](#faq) - [Credits](#credits) - [License](#license) |
@@ -79,27 +79,6 @@ Dear ImGui allows you to **create elaborate tools** as well as very short-lived
Every snippet in this README is run by [`tools/test/scripts/t_readme.luau`](/tools/test/scripts/t_readme.luau), and every image was rendered by this port with [`tools/screenshots/make.py`](/tools/screenshots/make.py). Every snippet in this README is run by [`tools/test/scripts/t_readme.luau`](/tools/test/scripts/t_readme.luau), and every image was rendered by this port with [`tools/screenshots/make.py`](/tools/screenshots/make.py).
### How it works (Roblox backend)
Dear ImGui itself never draws anything: every frame it produces `ImDrawData`, a list of textured, coloured triangles plus clip rectangles, and expects the backend to put them on screen. On desktop that is a few GPU draw calls. Roblox has no API to draw arbitrary triangles in 2D, so the Roblox backend ([backends/imgui_impl_roblox.lua](/backends/imgui_impl_roblox.lua)) does what a GPU would do, in Luau, and pushes the result into **EditableImages**.
**Why software rasterization?** The alternatives don't fit Dear ImGui:
- *One GUI Instance per widget/shape* (what most Roblox UI libraries do) means re-creating, re-parenting or diffing thousands of Instances every frame, can't express anti-aliased curves, per-vertex gradients or font-atlas glyphs, and would no longer be Dear ImGui's renderer output.
- *Rasterizing the draw data* keeps the output identical to Dear ImGui's: the same fonts, anti-aliasing, rounding and gradients, and every widget (including custom `ImDrawList` drawing) works without backend support.
The cost is CPU time in Luau, so the backend is built around **not drawing**:
1. **Layers per window.** Every draw list (one per top-level window, popup, tooltip) gets its own `Frame` with `ZIndex` following Dear ImGui's draw order. Its content is rasterized relative to the window, so **moving a window only changes the Frame's `Position`**; no pixel is redrawn or uploaded.
2. **Tiles.** Each layer is covered by 128×128 EditableImage tiles (`ImageLabel`s). Every primitive is hashed (positions relative to the layer, UVs, colours, texture, clip) into the tiles it touches. **Only tiles whose hash changed are re-rasterized and uploaded**, and only the dirty region of those (`WritePixelsBuffer` with a sub-rectangle). A static UI uploads nothing.
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. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
**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). Games can't read the clipboard, so while a text field is active a hidden `TextBox` holds focus: the OS 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).
**Instances inside windows.** The backend can place your own Instances (e.g. a `ViewportFrame`) inside a window's layer, so they stack, clip and move with it; see the `ImGui.Embed` widget in [examples/example_roblox/main.client.luau](/examples/example_roblox/main.client.luau).
### Demo ### Demo
Calling `ImGui.ShowDemoWindow()` creates a demo window that showcases a variety of features and examples. The code is in [imgui_demo.lua](/imgui_demo.lua), like upstream's `imgui_demo.cpp`. **It's strongly recommended to keep it at hand as a reference**: you can find the code for any widget you see by searching for its label. Calling `ImGui.ShowDemoWindow()` creates a demo window that showcases a variety of features and examples. The code is in [imgui_demo.lua](/imgui_demo.lua), like upstream's `imgui_demo.cpp`. **It's strongly recommended to keep it at hand as a reference**: you can find the code for any widget you see by searching for its label.
@@ -137,7 +116,9 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
### Getting Started & Integration (Roblox) ### Getting Started & Integration (Roblox)
1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out). With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/[email protected]"` 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`. 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`.
3. A minimal LocalScript (full version: [examples/example_roblox/main.client.luau](/examples/example_roblox/main.client.luau)): 3. A minimal LocalScript (full version: [examples/example_roblox/main.client.luau](/examples/example_roblox/main.client.luau)):
@@ -182,6 +163,27 @@ io.Fonts:AddFontDefault()
io.Fonts:AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0) io.Fonts:AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0)
``` ```
### How it works (Roblox backend)
Dear ImGui itself never draws anything: every frame it produces `ImDrawData`, a list of textured, coloured triangles plus clip rectangles, and expects the backend to put them on screen. On desktop that is a few GPU draw calls. Roblox has no API to draw arbitrary triangles in 2D, so the Roblox backend ([backends/imgui_impl_roblox.lua](/backends/imgui_impl_roblox.lua)) does what a GPU would do, in Luau, and pushes the result into **EditableImages**.
**Why software rasterization?** The alternatives don't fit Dear ImGui:
- *One GUI Instance per widget/shape* (what most Roblox UI libraries do) means re-creating, re-parenting or diffing thousands of Instances every frame, can't express anti-aliased curves, per-vertex gradients or font-atlas glyphs, and would no longer be Dear ImGui's renderer output.
- *Rasterizing the draw data* keeps the output identical to Dear ImGui's: the same fonts, anti-aliasing, rounding and gradients, and every widget (including custom `ImDrawList` drawing) works without backend support.
The cost is CPU time in Luau, so the backend is built around **not drawing**:
1. **Layers per window.** Every draw list (one per top-level window, popup, tooltip) gets its own `Frame` with `ZIndex` following Dear ImGui's draw order. Its content is rasterized relative to the window, so **moving a window only changes the Frame's `Position`**; no pixel is redrawn or uploaded.
2. **Tiles.** Each layer is covered by 128×128 EditableImage tiles (`ImageLabel`s). Every primitive is hashed (positions relative to the layer, UVs, colours, texture, clip) into the tiles it touches. **Only tiles whose hash changed are re-rasterized and uploaded**, and only the dirty region of those (`WritePixelsBuffer` with a sub-rectangle). A static UI uploads nothing.
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. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
**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).
**Instances inside windows.** The backend can place your own Instances (e.g. a `ViewportFrame`) inside a window's layer, so they stack, clip and move with it; see the `ImGui.Embed` widget in [examples/example_roblox/main.client.luau](/examples/example_roblox/main.client.luau).
### Backends ### Backends
| Backend | File | Build | Renderer | Example | | Backend | File | Build | Renderer | Example |
@@ -242,6 +244,20 @@ Measured headless (Luau CLI, Studio-like native code generation), demo window op
Most of the remaining cost is widget logic (your UI code and Dear ImGui's per-widget work), which runs every frame by design. Use `ImGuiListClipper` for long lists. Most of the remaining cost is widget logic (your UI code and Dear ImGui's per-widget work), which runs every frame by design. Use `ImGuiListClipper` for long lists.
Windows that rarely change can opt in to caching: `ImGui.SetNextWindowCached(key)` keeps last frame's drawing and makes `Begin` return `visible = false`, so your code for that window doesn't run. It refreshes when `key` changes, while the window is hovered, focused or active (plus one frame after), and when it appears, moves, resizes, scrolls, collapses or is docked. An idle 200-widget window goes from ~2.2 ms to ~0.15 ms per frame.
```lua
local stats = { version = 0, kills = 0 }
local function StatsWindow()
ImGui.SetNextWindowCached(stats.version) -- bump stats.version whenever stats change
local _, visible = ImGui.Begin("Stats")
if visible then -- false while the cached contents are reused
ImGui.Text("Kills: %d", stats.kills)
end
ImGui.End()
end
```
### Testing ### Testing
`tools/test/run.py` runs any LocalScript headless against a mocked Roblox (`tools/test/roblox_mock.luau`): `tools/test/run.py` runs any LocalScript headless against a mocked Roblox (`tools/test/roblox_mock.luau`):
@@ -283,7 +299,7 @@ Multi-select / box-select, typing-select, the Assets Browser example, the Item P
### AI disclosure ### AI disclosure
AI tools were used to assist in making this project (porting code, the Roblox and LÖVE backends, optimizations, tests and documentation). All changes were reviewed and tested by a human, and correctness is checked by the test suite (draw-data checksums against the reference behaviour, pixel comparisons, behaviour tests). Bug reports are welcome. AI tools were used to assist in making this project (porting code, optimizations, tests and documentation). All changes were reviewed and tested by a human, and correctness is checked by the test suite (draw-data checksums against the reference behaviour, pixel comparisons, behaviour tests). Bug reports are welcome.
### Credits ### Credits
+40 -3
View File
@@ -36,18 +36,39 @@ jobs:
python3 tools/bundle.py --no-demo dist/roblox-nodemo # ImGui.luau (library only) python3 tools/bundle.py --no-demo dist/roblox-nodemo # ImGui.luau (library only)
python3 tools/bundle.py --target love dist/love # imgui_love.lua (library + demo) python3 tools/bundle.py --target love dist/love # imgui_love.lua (library + demo)
python3 tools/bundle.py --target love --no-demo dist/love-nodemo 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 - name: Check
run: | run: |
python3 tools/sync_github_readme.py --check python3 tools/sync_github_readme.py --check
luau-compile --null dist/roblox/ImGui.luau luau-compile --null dist/roblox/ImGui.luau
luau-compile --null dist/roblox-nodemo/ImGui.luau 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/imgui_love.lua > /dev/null
luajit -bl dist/love-nodemo/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 python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/r.txt
grep -q "README OK" /tmp/r.txt grep -q "README OK" /tmp/r.txt
python3 tools/test/run.py --main tools/test/scripts/t_cached_window.luau --frames 130 --quiet --roblox --timeout 300 | tee /tmp/c.txt
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
grep -q "FIXES OK" /tmp/f.txt
python3 tools/bundle.py --target love >/dev/null python3 tools/bundle.py --target love >/dev/null
luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic | tee /tmp/h.txt
cp build/imgui_love.lua /tmp/imgui_love.lua && cp dist/imgui_love.min.lua build/imgui_love.lua
luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic | tee /tmp/hm.txt
cp /tmp/imgui_love.lua build/imgui_love.lua
diff /tmp/h.txt /tmp/hm.txt # minified LÖVE bundle draws exactly the same
luajit tools/test/love/t_examples.lua luajit tools/test/love/t_examples.lua
- name: Package - name: Package
@@ -59,7 +80,7 @@ jobs:
cp love/imgui_love.lua imgui_love.lua cp love/imgui_love.lua imgui_love.lua
cp love-nodemo/imgui_love.lua imgui_love-nodemo.lua cp love-nodemo/imgui_love.lua imgui_love-nodemo.lua
# Roblox: module + example LocalScript + Rojo project # Roblox: module + example LocalScript + Rojo project
mkdir -p pkg-roblox/build && cp ImGui.luau pkg-roblox/build/ && cp ../default.project.json pkg-roblox/ mkdir -p pkg-roblox/build && cp ImGui.luau roblox/ImGuiTyped.luau pkg-roblox/build/ && cp ../default.project.json pkg-roblox/
mkdir -p pkg-roblox/examples && cp -r ../examples/example_roblox ../examples/example_shared pkg-roblox/examples/ mkdir -p pkg-roblox/examples && cp -r ../examples/example_roblox ../examples/example_shared pkg-roblox/examples/
(cd pkg-roblox && zip -qr ../imgui-luau-roblox-$V.zip .) (cd pkg-roblox && zip -qr ../imgui-luau-roblox-$V.zip .)
# LÖVE: bundle + fonts + examples (run: love examples/example_love) # LÖVE: bundle + fonts + examples (run: love examples/example_love)
@@ -77,9 +98,25 @@ jobs:
dist/ImGui-nodemo.luau dist/ImGui-nodemo.luau
dist/imgui_love.lua dist/imgui_love.lua
dist/imgui_love-nodemo.lua dist/imgui_love-nodemo.lua
dist/*.min.luau
dist/*.min.lua
dist/*.zip dist/*.zip
generate_release_notes: true generate_release_notes: true
- name: Publish to Wally (tag)
if: startsWith(github.ref, 'refs/tags/v')
env:
WALLY_TOKEN: ${{ secrets.WALLY_TOKEN }}
run: |
[ -n "$WALLY_TOKEN" ] || { echo "WALLY_TOKEN not set, skipping"; exit 0; }
grep -q "^version = \"${GITHUB_REF_NAME#v}\"" wally.toml || { echo "wally.toml version != tag"; exit 1; }
curl -fsSL -o wally.zip https://github.com/UpliftGames/wally/releases/download/v0.3.2/wally-v0.3.2-linux.zip
unzip -q wally.zip -d wallybin && chmod +x wallybin/wally
mkdir -p dist/wally/src && cp wally.toml dist/wally/ && cp dist/roblox/ImGuiTyped.luau dist/wally/src/init.luau && cp dist/roblox/ImGui.luau dist/wally/src/Impl.luau
echo '{"name":"imgui-luau","tree":{"$path":"src"}}' > dist/wally/default.project.json
wallybin/wally login --token "$WALLY_TOKEN"
wallybin/wally publish --project-path dist/wally
- name: Release (latest) - name: Release (latest)
if: "!startsWith(github.ref, 'refs/tags/v')" if: "!startsWith(github.ref, 'refs/tags/v')"
env: env:
@@ -88,4 +125,4 @@ jobs:
gh release delete latest --yes --cleanup-tag || true gh release delete latest --yes --cleanup-tag || true
gh release create latest --latest --title "Latest build (${GITHUB_SHA::7})" \ gh release create latest --latest --title "Latest build (${GITHUB_SHA::7})" \
--notes "Automatic build of ${GITHUB_SHA} on ${GITHUB_REF_NAME}. Tag a commit v* for a versioned release." \ --notes "Automatic build of ${GITHUB_SHA} on ${GITHUB_REF_NAME}. Tag a commit v* for a versioned release." \
dist/ImGui.luau dist/ImGui-nodemo.luau dist/imgui_love.lua dist/imgui_love-nodemo.lua dist/*.zip dist/ImGui.luau dist/ImGui-nodemo.luau dist/imgui_love.lua dist/imgui_love-nodemo.lua dist/*.min.luau dist/*.min.lua dist/*.zip
+4
View File
@@ -0,0 +1,4 @@
.opencode/
Packages/
wally.lock
__pycache__/
+40 -24
View File
@@ -7,7 +7,7 @@ Dear ImGui for Roblox (and more)
A Lua/Luau port of [Dear ImGui](https://github.com/ocornut/imgui) (docking branch, 1.93 WIP). **Roblox is the primary target** (rendered with EditableImages); the same sources also run on [LÖVE](https://love2d.org) (LuaJIT, GPU renderer), and the backend layout leaves room for more. It follows the C++ source function by function, so upstream documentation, examples and the demo apply almost directly. A Lua/Luau port of [Dear ImGui](https://github.com/ocornut/imgui) (docking branch, 1.93 WIP). **Roblox is the primary target** (rendered with EditableImages); the same sources also run on [LÖVE](https://love2d.org) (LuaJIT, GPU renderer), and the backend layout leaves room for more. It follows the C++ source function by function, so upstream documentation, examples and the demo apply almost directly.
| [The Pitch](#the-pitch) - [Usage](#usage) - [How it works](#how-it-works) - [Demo](#demo) - [Getting Started & Integration](#getting-started--integration) - [Backends](#backends) | | [The Pitch](#the-pitch) - [Usage](#usage) - [Demo](#demo) - [Getting Started & Integration](#getting-started--integration-roblox) - [How it works](#how-it-works-roblox-backend) - [Backends](#backends) |
:----------------------------------------------------------: | :----------------------------------------------------------: |
| [Differences from C++](#differences-from-c-dear-imgui) - [Performance](#performance) - [Testing](#testing) - [Not ported yet](#not-ported-yet) | | [Differences from C++](#differences-from-c-dear-imgui) - [Performance](#performance) - [Testing](#testing) - [Not ported yet](#not-ported-yet) |
| [FAQ](#faq) - [Credits](#credits) - [License](#license) | | [FAQ](#faq) - [Credits](#credits) - [License](#license) |
@@ -76,27 +76,6 @@ Dear ImGui allows you to **create elaborate tools** as well as very short-lived
Every snippet in this README is run by [`tools/test/scripts/t_readme.luau`](tools/test/scripts/t_readme.luau), and every image was rendered by this port with [`tools/screenshots/make.py`](tools/screenshots/make.py). Every snippet in this README is run by [`tools/test/scripts/t_readme.luau`](tools/test/scripts/t_readme.luau), and every image was rendered by this port with [`tools/screenshots/make.py`](tools/screenshots/make.py).
### How it works (Roblox backend)
Dear ImGui itself never draws anything: every frame it produces `ImDrawData`, a list of textured, coloured triangles plus clip rectangles, and expects the backend to put them on screen. On desktop that is a few GPU draw calls. Roblox has no API to draw arbitrary triangles in 2D, so the Roblox backend ([backends/imgui_impl_roblox.lua](backends/imgui_impl_roblox.lua)) does what a GPU would do, in Luau, and pushes the result into **EditableImages**.
**Why software rasterization?** The alternatives don't fit Dear ImGui:
- *One GUI Instance per widget/shape* (what most Roblox UI libraries do) means re-creating, re-parenting or diffing thousands of Instances every frame, can't express anti-aliased curves, per-vertex gradients or font-atlas glyphs, and would no longer be Dear ImGui's renderer output.
- *Rasterizing the draw data* keeps the output identical to Dear ImGui's: the same fonts, anti-aliasing, rounding and gradients, and every widget (including custom `ImDrawList` drawing) works without backend support.
The cost is CPU time in Luau, so the backend is built around **not drawing**:
1. **Layers per window.** Every draw list (one per top-level window, popup, tooltip) gets its own `Frame` with `ZIndex` following Dear ImGui's draw order. Its content is rasterized relative to the window, so **moving a window only changes the Frame's `Position`**; no pixel is redrawn or uploaded.
2. **Tiles.** Each layer is covered by 128×128 EditableImage tiles (`ImageLabel`s). Every primitive is hashed (positions relative to the layer, UVs, colours, texture, clip) into the tiles it touches. **Only tiles whose hash changed are re-rasterized and uploaded**, and only the dirty region of those (`WritePixelsBuffer` with a sub-rectangle). A static UI uploads nothing.
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. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
**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). Games can't read the clipboard, so while a text field is active a hidden `TextBox` holds focus: the OS 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).
**Instances inside windows.** The backend can place your own Instances (e.g. a `ViewportFrame`) inside a window's layer, so they stack, clip and move with it; see the `ImGui.Embed` widget in [examples/example_roblox/main.client.luau](examples/example_roblox/main.client.luau).
### Demo ### Demo
Calling `ImGui.ShowDemoWindow()` creates a demo window that showcases a variety of features and examples. The code is in [imgui_demo.lua](imgui_demo.lua), like upstream's `imgui_demo.cpp`. **It's strongly recommended to keep it at hand as a reference**: you can find the code for any widget you see by searching for its label. Calling `ImGui.ShowDemoWindow()` creates a demo window that showcases a variety of features and examples. The code is in [imgui_demo.lua](imgui_demo.lua), like upstream's `imgui_demo.cpp`. **It's strongly recommended to keep it at hand as a reference**: you can find the code for any widget you see by searching for its label.
@@ -134,7 +113,9 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
### Getting Started & Integration (Roblox) ### Getting Started & Integration (Roblox)
1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out). With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/[email protected]"` 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`. 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`.
3. A minimal LocalScript (full version: [examples/example_roblox/main.client.luau](examples/example_roblox/main.client.luau)): 3. A minimal LocalScript (full version: [examples/example_roblox/main.client.luau](examples/example_roblox/main.client.luau)):
@@ -179,6 +160,27 @@ io.Fonts:AddFontDefault()
io.Fonts:AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0) io.Fonts:AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0)
``` ```
### How it works (Roblox backend)
Dear ImGui itself never draws anything: every frame it produces `ImDrawData`, a list of textured, coloured triangles plus clip rectangles, and expects the backend to put them on screen. On desktop that is a few GPU draw calls. Roblox has no API to draw arbitrary triangles in 2D, so the Roblox backend ([backends/imgui_impl_roblox.lua](backends/imgui_impl_roblox.lua)) does what a GPU would do, in Luau, and pushes the result into **EditableImages**.
**Why software rasterization?** The alternatives don't fit Dear ImGui:
- *One GUI Instance per widget/shape* (what most Roblox UI libraries do) means re-creating, re-parenting or diffing thousands of Instances every frame, can't express anti-aliased curves, per-vertex gradients or font-atlas glyphs, and would no longer be Dear ImGui's renderer output.
- *Rasterizing the draw data* keeps the output identical to Dear ImGui's: the same fonts, anti-aliasing, rounding and gradients, and every widget (including custom `ImDrawList` drawing) works without backend support.
The cost is CPU time in Luau, so the backend is built around **not drawing**:
1. **Layers per window.** Every draw list (one per top-level window, popup, tooltip) gets its own `Frame` with `ZIndex` following Dear ImGui's draw order. Its content is rasterized relative to the window, so **moving a window only changes the Frame's `Position`**; no pixel is redrawn or uploaded.
2. **Tiles.** Each layer is covered by 128×128 EditableImage tiles (`ImageLabel`s). Every primitive is hashed (positions relative to the layer, UVs, colours, texture, clip) into the tiles it touches. **Only tiles whose hash changed are re-rasterized and uploaded**, and only the dirty region of those (`WritePixelsBuffer` with a sub-rectangle). A static UI uploads nothing.
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. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
**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).
**Instances inside windows.** The backend can place your own Instances (e.g. a `ViewportFrame`) inside a window's layer, so they stack, clip and move with it; see the `ImGui.Embed` widget in [examples/example_roblox/main.client.luau](examples/example_roblox/main.client.luau).
### Backends ### Backends
| Backend | File | Build | Renderer | Example | | Backend | File | Build | Renderer | Example |
@@ -239,6 +241,20 @@ Measured headless (Luau CLI, Studio-like native code generation), demo window op
Most of the remaining cost is widget logic (your UI code and Dear ImGui's per-widget work), which runs every frame by design. Use `ImGuiListClipper` for long lists. Most of the remaining cost is widget logic (your UI code and Dear ImGui's per-widget work), which runs every frame by design. Use `ImGuiListClipper` for long lists.
Windows that rarely change can opt in to caching: `ImGui.SetNextWindowCached(key)` keeps last frame's drawing and makes `Begin` return `visible = false`, so your code for that window doesn't run. It refreshes when `key` changes, while the window is hovered, focused or active (plus one frame after), and when it appears, moves, resizes, scrolls, collapses or is docked. An idle 200-widget window goes from ~2.2 ms to ~0.15 ms per frame.
```lua
local stats = { version = 0, kills = 0 }
local function StatsWindow()
ImGui.SetNextWindowCached(stats.version) -- bump stats.version whenever stats change
local _, visible = ImGui.Begin("Stats")
if visible then -- false while the cached contents are reused
ImGui.Text("Kills: %d", stats.kills)
end
ImGui.End()
end
```
### Testing ### Testing
`tools/test/run.py` runs any LocalScript headless against a mocked Roblox (`tools/test/roblox_mock.luau`): `tools/test/run.py` runs any LocalScript headless against a mocked Roblox (`tools/test/roblox_mock.luau`):
@@ -280,7 +296,7 @@ Multi-select / box-select, typing-select, the Assets Browser example, the Item P
### AI disclosure ### AI disclosure
AI tools were used to assist in making this project (porting code, the Roblox and LÖVE backends, optimizations, tests and documentation). All changes were reviewed and tested by a human, and correctness is checked by the test suite (draw-data checksums against the reference behaviour, pixel comparisons, behaviour tests). Bug reports are welcome. AI tools were used to assist in making this project (porting code, optimizations, tests and documentation). All changes were reviewed and tested by a human, and correctness is checked by the test suite (draw-data checksums against the reference behaviour, pixel comparisons, behaviour tests). Bug reports are welcome.
### Credits ### Credits
+39 -19
View File
@@ -862,7 +862,8 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
end end
if rate > 0 then if rate > 0 then
local now = os.clock() local now = os.clock()
if now + 0.002 < bd.NextRender then table.clear(bd.Overlays); PE(); return end -- Skipped: kept pending, so if no new frame comes (the app stopped rendering) Heartbeat still draws it
if now + 0.002 < bd.NextRender then bd.Pending = draw_data; PE(); return end
bd.NextRender = math.max(bd.NextRender + 1 / rate, now) bd.NextRender = math.max(bd.NextRender + 1 / rate, now)
end end
@@ -1127,8 +1128,33 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
end end
end) end)
-- Text proxy: a hidden TextBox holds focus while an imgui text field is active, so the OS gives us typing (any bd.PendingChars = {}
-- layout/unicode), paste (Ctrl+V) and copy/cut (Ctrl+C/X of the proxy's text = imgui's current selection).
-- Render rate cap trailing edge: the last frame submitted is always drawn, even if it was skipped and the app
-- then stopped calling NewFrame (e.g. it rendered one empty frame to hide its windows)
connect(game:GetService("RunService").Heartbeat, function()
local d = bd.Pending
if d and os.clock() + 0.002 >= bd.NextRender then
bd.Pending = nil
ImGui_ImplRoblox_RenderDrawData(d)
end
end)
connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end)
connect(UserInputService.WindowFocusReleased, function() ImGui.GetIO():AddFocusEvent(false) end)
end
-- Text proxy (opt-in, ImGui_ImplRoblox.SetTextProxy(true)): a hidden TextBox holds focus while an imgui text field is
-- active, so the OS gives us typing (any layout/unicode), paste (Ctrl+V) and copy/cut (Ctrl+C/X of imgui's selection).
-- Off: typing comes from KeyCodes (printable ASCII), no OS clipboard.
function ImGui_ImplRoblox_SetTextProxy(enabled)
local bd = ImGui_ImplRoblox_GetBackendData()
if not enabled then
if bd.Proxy then bd.Proxy:Destroy(); bd.Proxy = nil end
proxy_focused = false
return
end
if bd.Proxy then return end
local proxy = Instance.new("TextBox") local proxy = Instance.new("TextBox")
proxy.Name = "TextProxy" proxy.Name = "TextProxy"
proxy.BackgroundTransparency = 1 proxy.BackgroundTransparency = 1
@@ -1140,12 +1166,11 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
proxy.ClearTextOnFocus = false proxy.ClearTextOnFocus = false
proxy.TextEditable = true proxy.TextEditable = true
proxy.Text = "" proxy.Text = ""
proxy.Parent = gui proxy.Parent = bd.Gui
bd.Proxy, bd.ProxyText, bd.PendingChars = proxy, "", {} bd.Proxy, bd.ProxyText = proxy, ""
-- Clicking elsewhere (or Roblox taking focus) ends the edit, like clicking outside the field in imgui. -- Clicking elsewhere (or Roblox taking focus) ends the edit, like clicking outside the field in imgui.
-- (Text is polled in NewFrame instead of using Text changed signals: those are deferred in Roblox.) -- (Text is polled in NewFrame instead of using Text changed signals: those are deferred in Roblox.)
connect(proxy.FocusLost, function() proxy.FocusLost:Connect(function()
if bd.ProxyDebug then print("[ImGui text proxy] FocusLost, mouse over imgui:", ImGui.GetIO().WantCaptureMouse) end
if bd.ProxyReleasing then return end if bd.ProxyReleasing then return end
-- Roblox drops TextBox focus on any click, including clicks inside imgui (e.g. moving the cursor within the -- Roblox drops TextBox focus on any click, including clicks inside imgui (e.g. moving the cursor within the
-- same field). Those must not end the edit: just recapture next frame. Only a click outside imgui ends it. -- same field). Those must not end the edit: just recapture next frame. Only a click outside imgui ends it.
@@ -1154,9 +1179,6 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
local g = ImGui.GetCurrentContext() local g = ImGui.GetCurrentContext()
if g and g.ActiveId ~= 0 and g.ActiveId == bd.ProxyActiveId then ImGui.ClearActiveID() end if g and g.ActiveId ~= 0 and g.ActiveId == bd.ProxyActiveId then ImGui.ClearActiveID() end
end) end)
connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end)
connect(UserInputService.WindowFocusReleased, function() ImGui.GetIO():AddFocusEvent(false) end)
end end
function ImGui_ImplRoblox_Shutdown() function ImGui_ImplRoblox_Shutdown()
@@ -1177,6 +1199,7 @@ function ImGui_ImplRoblox_NewFrame()
PB("ImGui NewFrame (backend)") PB("ImGui NewFrame (backend)")
local io = ImGui.GetIO() local io = ImGui.GetIO()
local bd = ImGui_ImplRoblox_GetBackendData() local bd = ImGui_ImplRoblox_GetBackendData()
if bd.Pending then bd.Pending = nil; table.clear(bd.Overlays) end -- skipped frame superseded (its draw lists get reset)
local screen = bd.Gui.AbsoluteSize local screen = bd.Gui.AbsoluteSize
local w, h = floor(screen.X * bd.Scale), floor(screen.Y * bd.Scale) local w, h = floor(screen.X * bd.Scale), floor(screen.Y * bd.Scale)
@@ -1201,15 +1224,16 @@ function ImGui_ImplRoblox_NewFrame()
-- otherwise the KeyCode fallback keeps typing working. -- otherwise the KeyCode fallback keeps typing working.
local proxy = bd.Proxy local proxy = bd.Proxy
local g = ImGui.GetCurrentContext() local g = ImGui.GetCurrentContext()
local focused_box = UserInputService:GetFocusedTextBox() local focused_box = proxy and UserInputService:GetFocusedTextBox()
if io.WantTextInput and g.ActiveId ~= 0 then if not proxy then
-- text proxy disabled: KeyCode typing only
elseif io.WantTextInput and g.ActiveId ~= 0 then
if g.ActiveId ~= bd.ProxyActiveId or g.ActiveIdIsJustActivated or g.IO.MouseClicked[0] then bd.ProxyActiveId = g.ActiveId; bd.ProxyLostId = nil end -- (re)clicking a field recaptures if g.ActiveId ~= bd.ProxyActiveId or g.ActiveIdIsJustActivated or g.IO.MouseClicked[0] then bd.ProxyActiveId = g.ActiveId; bd.ProxyLostId = nil end -- (re)clicking a field recaptures
local mouse_up = not (io.MouseDown[0] or io.MouseDown[1] or io.MouseDown[2]) local mouse_up = not (io.MouseDown[0] or io.MouseDown[1] or io.MouseDown[2])
if focused_box ~= proxy and mouse_up and bd.ProxyLostId ~= g.ActiveId and (focused_box == nil or not focused_box:IsDescendantOf(game)) then if focused_box ~= proxy and mouse_up and bd.ProxyLostId ~= g.ActiveId and (focused_box == nil or not focused_box:IsDescendantOf(game)) then
bd.ProxyText = proxy.Text -- what it already holds is not new input (else re-typing it would collapse imgui's selection) bd.ProxyText = proxy.Text -- what it already holds is not new input (else re-typing it would collapse imgui's selection)
proxy:CaptureFocus() proxy:CaptureFocus()
focused_box = UserInputService:GetFocusedTextBox() focused_box = UserInputService:GetFocusedTextBox()
if bd.ProxyDebug then print("[ImGui text proxy] CaptureFocus ->", focused_box == proxy and "focused" or ("FAILED, focused = " .. tostring(focused_box))) end
end end
elseif focused_box == proxy then elseif focused_box == proxy then
bd.ProxyReleasing = true bd.ProxyReleasing = true
@@ -1217,13 +1241,11 @@ function ImGui_ImplRoblox_NewFrame()
bd.ProxyReleasing = false bd.ProxyReleasing = false
focused_box = nil focused_box = nil
end end
local was_focused = proxy_focused proxy_focused = proxy ~= nil and focused_box == proxy
proxy_focused = focused_box == proxy
local used_proxy_text = false local used_proxy_text = false
if proxy_focused then if proxy_focused then
-- 1) whatever the OS put in the proxy since last frame (typing, paste, cut) replaces imgui's selection -- 1) whatever the OS put in the proxy since last frame (typing, paste, cut) replaces imgui's selection
local text = proxy.Text local text = proxy.Text
if text ~= bd.ProxyText and bd.ProxyDebug then print("[ImGui text proxy] text", string.format("%q", text), "was", string.format("%q", tostring(bd.ProxyText))) end
if text ~= bd.ProxyText and text ~= bd.ProxyLastRaw then -- (LastRaw: don't re-send if our reset hasn't applied yet) if text ~= bd.ProxyText and text ~= bd.ProxyLastRaw then -- (LastRaw: don't re-send if our reset hasn't applied yet)
bd.ProxyLastRaw = text bd.ProxyLastRaw = text
-- InputText ignores characters while Ctrl is held (upstream, so shortcuts don't type); a paste arrives with -- InputText ignores characters while Ctrl is held (upstream, so shortcuts don't type); a paste arrives with
@@ -1259,7 +1281,6 @@ function ImGui_ImplRoblox_NewFrame()
proxy.Text = sel proxy.Text = sel
bd.ProxyLastRaw = nil bd.ProxyLastRaw = nil
bd.ProxyText = sel bd.ProxyText = sel
if bd.ProxyDebug then print("[ImGui text proxy] mirror selection", string.format("%q", sel)) end
end end
-- keep imgui's selection fully selected in the proxy, so Ctrl+C copies it and typing/Ctrl+V replaces it -- keep imgui's selection fully selected in the proxy, so Ctrl+C copies it and typing/Ctrl+V replaces it
local want_sel, want_cur = (#sel > 0) and 1 or -1, #sel + 1 local want_sel, want_cur = (#sel > 0) and 1 or -1, #sel + 1
@@ -1275,7 +1296,6 @@ function ImGui_ImplRoblox_NewFrame()
if not used_proxy_text then for _, c in ipairs(pending) do io:AddInputCharacter(c) end end if not used_proxy_text then for _, c in ipairs(pending) do io:AddInputCharacter(c) end end
table.clear(pending) table.clear(pending)
end end
if bd.ProxyDebug and was_focused ~= proxy_focused then print("[ImGui text proxy] proxy_focused =", proxy_focused) end
-- While the mouse is over imgui, make the tiles Active so Roblox stops passing clicks/wheel to the game (camera zoom etc.) -- While the mouse is over imgui, make the tiles Active so Roblox stops passing clicks/wheel to the game (camera zoom etc.)
local want = io.WantCaptureMouse local want = io.WantCaptureMouse
@@ -1317,7 +1337,7 @@ ImGui_ImplRoblox = {
SetRenderScale = ImGui_ImplRoblox_SetRenderScale, SetRenderScale = ImGui_ImplRoblox_SetRenderScale,
SetRenderRate = ImGui_ImplRoblox_SetRenderRate, SetRenderRate = ImGui_ImplRoblox_SetRenderRate,
SetOverlay = ImGui_ImplRoblox_SetOverlay, SetOverlay = ImGui_ImplRoblox_SetOverlay,
SetTextProxyDebug = function(on) ImGui_ImplRoblox_GetBackendData().ProxyDebug = on end, -- prints focus/text events SetTextProxy = ImGui_ImplRoblox_SetTextProxy,
CreateTexture = ImGui_ImplRoblox_CreateTexture, CreateTexture = ImGui_ImplRoblox_CreateTexture,
DestroyTexture = ImGui_ImplRoblox_DestroyTexture, DestroyTexture = ImGui_ImplRoblox_DestroyTexture,
GetRenderScale = function() return ImGui_ImplRoblox_GetBackendData().Scale end, GetRenderScale = function() return ImGui_ImplRoblox_GetBackendData().Scale end,
+1 -1
View File
@@ -1 +1 @@
[[12, "imgui.lua"], [18472, "imgui_demo.lua"], [25757, "imgui_draw.lua"], [30995, "imgui_h.lua"], [33193, "imgui_impl_roblox.lua"], [34525, "imgui_impl_roblox_fonts.lua"], [40928, "imgui_internal.lua"], [44100, "imgui_tables.lua"], [48447, "imgui_widgets.lua"], [58202, "imstb_rectpack.lua"], [58648, "imstb_textedit.lua"], [59889, "imstb_truetype.lua"], [62357, "imstd_minstdio.lua"]] [[12, "imgui.lua"], [21288, "imgui_demo.lua"], [29706, "imgui_draw.lua"], [35476, "imgui_h.lua"], [38142, "imgui_impl_roblox.lua"], [39494, "imgui_impl_roblox_fonts.lua"], [45897, "imgui_internal.lua"], [49343, "imgui_tables.lua"], [54338, "imgui_widgets.lua"], [64948, "imstb_rectpack.lua"], [65612, "imstb_textedit.lua"], [67154, "imstb_truetype.lua"], [69942, "imstd_minstdio.lua"]]
+10244 -2658
View File
File diff suppressed because it is too large Load Diff
+958
View File
@@ -0,0 +1,958 @@
--!strict
-- GENERATED by tools/gen_types.py - do not edit. The library is the child ModuleScript 'Impl'.
export type ImVec2 = { x: number, y: number, [any]: any }
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,
AddDrawListToDrawDataEx: (...any) -> ...any,
AddSettingsHandler: (...any) -> ...any,
AddUpdateViewport: (...any) -> ...any,
AlignTextToFramePadding: () -> (),
ArrowButton: (str_id: string, dir: number) -> boolean,
ArrowButtonEx: (...any) -> ...any,
Begin: (name: string, open: boolean?, flags: number?) -> (boolean, boolean?),
BeginChild: (...any) -> ...any,
BeginChildEx: (...any) -> ...any,
BeginColumns: (...any) -> ...any,
BeginCombo: (label: string, preview_value: string, flags: number?) -> boolean,
BeginComboPopup: (...any) -> ...any,
BeginComboPreview: (...any) -> ...any,
BeginDisabled: (disabled: boolean?) -> (),
BeginDisabledOverrideReenable: (...any) -> ...any,
BeginDockableDragDropSource: (...any) -> ...any,
BeginDockableDragDropTarget: (...any) -> ...any,
BeginDocked: (...any) -> ...any,
BeginDragDropSource: (flags: number?) -> boolean,
BeginDragDropTarget: () -> boolean,
BeginDragDropTargetCustom: (...any) -> ...any,
BeginDragDropTargetViewport: (...any) -> ...any,
BeginErrorTooltip: (...any) -> ...any,
BeginGroup: () -> (),
BeginItemTooltip: () -> boolean,
BeginListBox: (label: string, size_arg: ImVec2?) -> boolean,
BeginMainMenuBar: () -> boolean,
BeginMenu: (label: string, enabled: boolean?) -> boolean,
BeginMenuBar: () -> boolean,
BeginMenuEx: (...any) -> ...any,
BeginPopup: (str_id: string, flags: number?) -> boolean,
BeginPopupContextItem: (str_id: string?, popup_flags: number?) -> boolean,
BeginPopupContextVoid: (str_id: string?, popup_flags: number?) -> boolean,
BeginPopupContextWindow: (str_id: string?, popup_flags: number?) -> boolean,
BeginPopupEx: (...any) -> ...any,
BeginPopupMenuEx: (...any) -> ...any,
BeginPopupModal: (name: string, p_open: boolean?, flags: number?) -> (boolean, boolean?),
BeginTabBar: (str_id: string, flags: number?) -> boolean,
BeginTabBarEx: (...any) -> ...any,
BeginTabItem: (label: string, p_open: boolean?, flags: number?) -> (boolean, boolean?),
BeginTable: (str_id: string, columns_count: number, flags: number?, outer_size: ImVec2?, inner_width: number?) -> boolean,
BeginTableEx: (...any) -> ...any,
BeginTooltip: () -> boolean,
BeginTooltipEx: (...any) -> ...any,
BeginTooltipHidden: (...any) -> ...any,
BeginViewportSideBar: (...any) -> ...any,
BringWindowToDisplayBack: (...any) -> ...any,
BringWindowToDisplayBehind: (...any) -> ...any,
BringWindowToDisplayFront: (...any) -> ...any,
BringWindowToFocusFront: (...any) -> ...any,
Bullet: () -> (),
BulletText: (fmt: string, ...any) -> (),
Button: (label: string, size_arg: ImVec2?) -> boolean,
ButtonBehavior: (...any) -> ...any,
ButtonEx: (...any) -> ...any,
CalcClipRectVisibleItemsY: (...any) -> ...any,
CalcItemSize: (...any) -> ...any,
CalcItemWidth: () -> number,
CalcRoundingFlagsForRectInRect: (...any) -> ...any,
CalcTextSize: (text: string, text_end: string?, hide_text_after_double_hash: boolean?, wrap_width: number?) -> ImVec2,
CalcTextSizeEx: (...any) -> ...any,
CalcTypematicRepeatAmount: (...any) -> ...any,
CalcWindowNextAutoFitSize: (...any) -> ...any,
CalcWrapWidthForPos: (...any) -> ...any,
Checkbox: (label: string, v: boolean?) -> (boolean, boolean?),
CheckboxFlags: (...any) -> ...any,
CleanupIniSettings: (...any) -> ...any,
ClearActiveID: (...any) -> ...any,
ClearDragDrop: (...any) -> ...any,
ClearIniSettings: (...any) -> ...any,
ClearWindowSettings: (...any) -> ...any,
CloseButton: (...any) -> ...any,
CloseCurrentPopup: () -> (),
ClosePopupToLevel: (...any) -> ...any,
ClosePopupsExceptModals: (...any) -> ...any,
ClosePopupsOverWindow: (...any) -> ...any,
CollapseButton: (...any) -> ...any,
CollapsingHeader: (...any) -> ...any,
ColorButton: (desc_id: string, col: ImVec4, flags: number?, size_arg: ImVec2?) -> boolean,
ColorConvertFloat4ToU32: (in_col: ImVec4) -> number,
ColorConvertHSVtoRGB: (...any) -> ...any,
ColorConvertRGBtoHSV: (...any) -> ...any,
ColorConvertU32ToFloat4: (...any) -> ...any,
ColorEdit3: (label: string, col: any, flags: number?) -> boolean,
ColorEdit4: (label: string, col: any, flags: number?) -> boolean,
ColorEditOptionsPopup: (...any) -> ...any,
ColorPicker3: (label: string, col: any, flags: number?) -> boolean,
ColorPicker4: (label: string, col: any, flags: number?, ref_col: any) -> boolean,
ColorPickerOptionsPopup: (...any) -> ...any,
ColorTooltip: (...any) -> ...any,
Columns: (columns_count: number?, id: string?, borders: boolean?) -> (),
Combo: (...any) -> ...any,
ConvertSingleModFlagToKey: (...any) -> ...any,
CreateContext: (shared_font_atlas: any) -> ...any,
CreateNewWindowSettings: (...any) -> ...any,
DataTypeApplyFromText: (...any) -> ...any,
DataTypeApplyOp: (...any) -> ...any,
DataTypeClamp: (...any) -> ...any,
DataTypeCompare: (...any) -> ...any,
DataTypeFormatString: (...any) -> ...any,
DataTypeGetInfo: (...any) -> ...any,
DataTypeIsZero: (...any) -> ...any,
DebugAllocHook: (...any) -> ...any,
DebugHookIdInfo: (...any) -> ...any,
DebugLog: (fmt: string, ...any) -> (),
DebugNodeColumns: (...any) -> ...any,
DebugNodeDockNode: (...any) -> ...any,
DebugNodeDrawList: (...any) -> ...any,
DebugNodeFont: (...any) -> ...any,
DebugNodeFontGlyph: (...any) -> ...any,
DebugNodeFontGlyphsForSrcMask: (...any) -> ...any,
DebugNodeStorage: (...any) -> ...any,
DebugNodeTabBar: (...any) -> ...any,
DebugNodeTable: (...any) -> ...any,
DebugNodeTableSettings: (...any) -> ...any,
DebugNodeTexture: (...any) -> ...any,
DebugNodeViewport: (...any) -> ...any,
DebugNodeWindow: (...any) -> ...any,
DebugNodeWindowSettings: (...any) -> ...any,
DebugNodeWindowsList: (...any) -> ...any,
DebugNodeWindowsListByBeginStackParent: (...any) -> ...any,
DebugRenderViewportThumbnail: (...any) -> ...any,
DebugTextEncoding: (str: string) -> (),
DestroyContext: (ctx: any) -> ...any,
DestroyPlatformWindow: (...any) -> ...any,
DestroyPlatformWindows: () -> (),
DestroyViewport: (...any) -> ...any,
DockBuilderAddNode: (...any) -> ...any,
DockBuilderCopyDockSpace: (...any) -> ...any,
DockBuilderCopyNode: (...any) -> ...any,
DockBuilderCopyWindowSettings: (...any) -> ...any,
DockBuilderDockWindow: (...any) -> ...any,
DockBuilderFinish: (...any) -> ...any,
DockBuilderGetCentralNode: (...any) -> ...any,
DockBuilderGetNode: (...any) -> ...any,
DockBuilderRemoveNode: (...any) -> ...any,
DockBuilderRemoveNodeChildNodes: (...any) -> ...any,
DockBuilderRemoveNodeDockedWindows: (...any) -> ...any,
DockBuilderSetNodePos: (...any) -> ...any,
DockBuilderSetNodeSize: (...any) -> ...any,
DockBuilderSplitNode: (...any) -> ...any,
DockContextAddNode: (...any) -> ...any,
DockContextBindNodeToWindow: (...any) -> ...any,
DockContextBuildAddWindowsToNodes: (...any) -> ...any,
DockContextBuildNodesFromSettings: (...any) -> ...any,
DockContextCalcDropPosForDocking: (...any) -> ...any,
DockContextClearNodes: (...any) -> ...any,
DockContextDeleteNode: (...any) -> ...any,
DockContextEndFrame: (...any) -> ...any,
DockContextFindNodeByID: (...any) -> ...any,
DockContextGenNodeID: (...any) -> ...any,
DockContextInitialize: (...any) -> ...any,
DockContextNewFrameUpdateDocking: (...any) -> ...any,
DockContextNewFrameUpdateUndocking: (...any) -> ...any,
DockContextProcessDock: (...any) -> ...any,
DockContextProcessUndockNode: (...any) -> ...any,
DockContextProcessUndockWindow: (...any) -> ...any,
DockContextPruneUnusedSettingsNodes: (...any) -> ...any,
DockContextQueueDock: (...any) -> ...any,
DockContextQueueNotifyRemovedNode: (...any) -> ...any,
DockContextQueueUndockNode: (...any) -> ...any,
DockContextQueueUndockWindow: (...any) -> ...any,
DockContextRebuildNodes: (...any) -> ...any,
DockContextRemoveNode: (...any) -> ...any,
DockContextShutdown: (...any) -> ...any,
DockNodeAddTabBar: (...any) -> ...any,
DockNodeAddWindow: (...any) -> ...any,
DockNodeApplyPosSizeToWindows: (...any) -> ...any,
DockNodeBeginAmendTabBar: (...any) -> ...any,
DockNodeCalcDropRectsAndTestMousePos: (...any) -> ...any,
DockNodeCalcSplitRects: (...any) -> ...any,
DockNodeCalcTabBarLayout: (...any) -> ...any,
DockNodeEndAmendTabBar: (...any) -> ...any,
DockNodeFindWindowByID: (...any) -> ...any,
DockNodeGetDepth: (...any) -> ...any,
DockNodeGetRootNode: (...any) -> ...any,
DockNodeGetTabOrder: (...any) -> ...any,
DockNodeGetWindowMenuButtonId: (...any) -> ...any,
DockNodeHideHostWindow: (...any) -> ...any,
DockNodeIsDropAllowed: (...any) -> ...any,
DockNodeIsInHierarchyOf: (...any) -> ...any,
DockNodeMoveChildNodes: (...any) -> ...any,
DockNodeMoveWindows: (...any) -> ...any,
DockNodePreviewDockRender: (...any) -> ...any,
DockNodePreviewDockSetup: (...any) -> ...any,
DockNodeRemoveTabBar: (...any) -> ...any,
DockNodeRemoveWindow: (...any) -> ...any,
DockNodeStartMouseMovingWindow: (...any) -> ...any,
DockNodeTreeFindFallbackLeafNode: (...any) -> ...any,
DockNodeTreeFindVisibleNodeByPos: (...any) -> ...any,
DockNodeTreeMerge: (...any) -> ...any,
DockNodeTreeSplit: (...any) -> ...any,
DockNodeTreeUpdatePosSize: (...any) -> ...any,
DockNodeTreeUpdateSplitter: (...any) -> ...any,
DockNodeUpdate: (...any) -> ...any,
DockNodeUpdateFlagsAndCollapse: (...any) -> ...any,
DockNodeUpdateForRootNode: (...any) -> ...any,
DockNodeUpdateHasCentralNodeChild: (...any) -> ...any,
DockNodeUpdateTabBar: (...any) -> ...any,
DockNodeUpdateVisibleFlag: (...any) -> ...any,
DockNodeWindowMenuHandler_Default: (...any) -> ...any,
DockNodeWindowMenuUpdate: (...any) -> ...any,
DockSpace: (dockspace_id: number, size_arg: ImVec2?, flags: number?, window_class: any) -> number,
DockSpaceOverViewport: (dockspace_id: number?, viewport: any, dockspace_flags: number?, window_class: any) -> number,
DragBehavior: (...any) -> ...any,
DragBehaviorT: (...any) -> ...any,
DragFloat: (label: string, v: number, v_speed: number?, v_min: number?, v_max: number?, format: string?, flags: number?) -> (number, boolean),
DragFloatRange2: (label: string, v_current_min: number, v_current_max: number, v_speed: number?, v_min: number?, v_max: number?, format: string?, format_max: string?, flags: number?) -> ...any,
DragInt: (label: string, v: number, v_speed: number?, v_min: number?, v_max: number?, format: string?, flags: number?) -> (number, boolean),
DragIntRange2: (label: string, v_current_min: number, v_current_max: number, v_speed: number?, v_min: number?, v_max: number?, format: string?, format_max: string?, flags: number?) -> ...any,
DragScalar: (label: string, data_type: number, data: any, v_speed: number?, min: any, max: any, format: string?, flags: number?) -> ...any,
DragScalarN: (label: string, data_type: number, v: any, components: number, v_speed: number?, v_min: any, v_max: any, format: string?, flags: number?) -> ...any,
Dummy: (size: ImVec2) -> (),
End: () -> (),
EndChild: () -> (),
EndColumns: (...any) -> ...any,
EndCombo: () -> (),
EndComboPreview: (...any) -> ...any,
EndDisabled: () -> (),
EndDisabledOverrideReenable: (...any) -> ...any,
EndDragDropSource: () -> (),
EndDragDropTarget: () -> (),
EndErrorTooltip: (...any) -> ...any,
EndFrame: () -> (),
EndGroup: () -> (),
EndListBox: () -> (),
EndMainMenuBar: () -> (),
EndMenu: () -> (),
EndMenuBar: () -> (),
EndPopup: () -> (),
EndTabBar: () -> (),
EndTabItem: () -> (),
EndTable: () -> (),
EndTooltip: () -> (),
ErrorCheckEndFrameFinalizeErrorTooltip: (...any) -> ...any,
ErrorCheckUsingSetCursorPosToExtendParentBoundaries: (...any) -> ...any,
ErrorLog: (...any) -> ...any,
ErrorRecoveryStoreState: (...any) -> ...any,
ErrorRecoveryTryToRecoverState: (...any) -> ...any,
ErrorRecoveryTryToRecoverWindowState: (...any) -> ...any,
ExtendHitBoxWhenNearViewportEdge: (...any) -> ...any,
FindBestWindowPosForPopup: (...any) -> ...any,
FindBestWindowPosForPopupEx: (...any) -> ...any,
FindBlockingModal: (...any) -> ...any,
FindBottomMostVisibleWindowWithinBeginStack: (...any) -> ...any,
FindFrontMostVisibleChildWindow: (...any) -> ...any,
FindHoveredViewportFromPlatformWindowStack: (...any) -> ...any,
FindOrCreateColumns: (...any) -> ...any,
FindPlatformMonitorForPos: (...any) -> ...any,
FindPlatformMonitorForRect: (...any) -> ...any,
FindRenderedTextEnd: (...any) -> ...any,
FindSettingsHandler: (...any) -> ...any,
FindViewportByID: (viewport_id: number) -> any,
FindWindowByID: (...any) -> ...any,
FindWindowByName: (...any) -> ...any,
FindWindowDisplayIndex: (...any) -> ...any,
FindWindowFocusIndex: (...any) -> ...any,
FindWindowNavFocusable: (...any) -> ...any,
FindWindowSettingsByID: (...any) -> ...any,
FindWindowSettingsByWindow: (...any) -> ...any,
FixupKeyChord: (...any) -> ...any,
FocusItem: (...any) -> ...any,
FocusTopMostWindowUnderOne: (...any) -> ...any,
FocusWindow: (...any) -> ...any,
GcAwakeTransientWindowBuffers: (...any) -> ...any,
GcCompactTransientMiscBuffers: (...any) -> ...any,
GcCompactTransientWindowBuffers: (...any) -> ...any,
GetBackgroundDrawList: (viewport: any) -> ...any,
GetClipboardText: () -> any,
GetColorU32: (...any) -> ...any,
GetColumnIndex: () -> number,
GetColumnNormFromOffset: (...any) -> ...any,
GetColumnOffset: (column_index: number?) -> number,
GetColumnOffsetFromNorm: (...any) -> ...any,
GetColumnWidth: (column_index: number?) -> number,
GetColumnsCount: () -> number,
GetColumnsID: (...any) -> ...any,
GetContentRegionAvail: () -> ImVec2,
GetContentRegionMax: (...any) -> ...any,
GetCurrentContext: () -> any,
GetCurrentWindow: (...any) -> ...any,
GetCurrentWindowRead: (...any) -> ...any,
GetCursorPos: () -> ImVec2,
GetCursorPosX: () -> number,
GetCursorPosY: () -> number,
GetCursorScreenPos: () -> ImVec2,
GetCursorStartPos: () -> ImVec2,
GetDefaultFont: (...any) -> ...any,
GetDragDropPayload: () -> any,
GetDrawData: () -> any,
GetDrawListSharedData: () -> any,
GetFont: () -> any,
GetFontSize: () -> number,
GetFontTexUvWhitePixel: () -> ImVec2,
GetForegroundDrawList: (viewport: any) -> ...any,
GetFrameCount: () -> number,
GetFrameHeight: () -> number,
GetFrameHeightWithSpacing: () -> number,
GetHoveredID: (...any) -> ...any,
GetID: (...any) -> ...any,
GetIDWithSeed: (...any) -> ...any,
GetIO: () -> any,
GetInputTextState: (...any) -> ...any,
GetItemClickedCountWithSingleClickDelay: (mouse_button: number?, delay: number?) -> number,
GetItemFlags: () -> number,
GetItemID: () -> number,
GetItemRectMax: () -> ImVec2,
GetItemRectMin: () -> ImVec2,
GetItemRectSize: () -> ImVec2,
GetKeyChordName: (...any) -> ...any,
GetKeyData: (...any) -> ...any,
GetKeyMagnitude2d: (...any) -> ...any,
GetKeyName: (key: number) -> any,
GetKeyOwner: (...any) -> ...any,
GetKeyOwnerData: (...any) -> ...any,
GetKeyPressedAmount: (key: number, repeat_delay: number, repeat_rate: number) -> number,
GetMainViewport: () -> any,
GetMergedModsFromKeys: (...any) -> ...any,
GetMetatables: (...any) -> ...any,
GetMouseButtonFromPopupFlags: (...any) -> ...any,
GetMouseClickedCount: (button: number) -> number,
GetMouseCursor: () -> number,
GetMouseDragDelta: (button: number?, lock_threshold: number?) -> ImVec2,
GetMousePos: () -> ImVec2,
GetMousePosOnOpeningCurrentPopup: () -> ImVec2,
GetNavTweakPressedAmount: (...any) -> ...any,
GetPlatformIO: () -> any,
GetPopupAllowedExtentRect: (...any) -> ...any,
GetRoundedFontSize: (...any) -> ...any,
GetScale: (...any) -> ...any,
GetScrollMaxX: () -> number,
GetScrollMaxY: () -> number,
GetScrollX: () -> number,
GetScrollY: () -> number,
GetShortcutRoutingData: (...any) -> ...any,
GetStateStorage: () -> any,
GetStyle: () -> any,
GetStyleColorName: (idx: number) -> any,
GetStyleColorVec4: (idx: number) -> ImVec4,
GetStyleVarInfo: (...any) -> ...any,
GetTextLineHeight: () -> number,
GetTextLineHeightWithSpacing: () -> number,
GetTime: () -> number,
GetTopMostAndVisiblePopupModal: (...any) -> ...any,
GetTopMostPopupModal: (...any) -> ...any,
GetTreeNodeToLabelSpacing: () -> number,
GetTypematicRepeatRate: (...any) -> ...any,
GetVersion: () -> any,
GetViewportPlatformMonitor: (...any) -> ...any,
GetWindowAlwaysWantOwnTabBar: (...any) -> ...any,
GetWindowAlwaysWantOwnViewport: (...any) -> ...any,
GetWindowContentRegionMax: (...any) -> ...any,
GetWindowContentRegionMin: (...any) -> ...any,
GetWindowDockID: () -> number,
GetWindowDpiScale: () -> number,
GetWindowDrawList: () -> any,
GetWindowHeight: () -> number,
GetWindowPos: () -> ImVec2,
GetWindowScrollbarID: (...any) -> ...any,
GetWindowScrollbarRect: (...any) -> ...any,
GetWindowSize: () -> ImVec2,
GetWindowViewport: () -> any,
GetWindowWidth: () -> number,
Image: (tex_ref: any, image_size: ImVec2, uv0: ImVec2?, uv1: ImVec2?) -> (),
ImageButton: (str_id: string, tex_ref: any, image_size: ImVec2, uv0: ImVec2?, uv1: ImVec2?, bg_col: ImVec4?, tint_col: ImVec4?) -> boolean,
ImageButtonEx: (...any) -> ...any,
ImageWithBg: (tex_ref: any, image_size: ImVec2, uv0: ImVec2?, uv1: ImVec2?, bg_col: ImVec4?, tint_col: ImVec4?) -> (),
Indent: (indent_w: number?) -> (),
Initialize: (...any) -> ...any,
InputDouble: (label: string, v: number, step: number?, step_fast: number?, format: string?, flags: number?) -> (number, boolean),
InputFloat: (label: string, v: number, step: number?, step_fast: number?, format: string?, flags: number?) -> (number, boolean),
InputFloat2: (label: string, v: any, format: string?, flags: number?) -> boolean,
InputFloat3: (label: string, v: any, format: string?, flags: number?) -> boolean,
InputFloat4: (label: string, v: any, format: string?, flags: number?) -> boolean,
InputInt: (label: string, v: number, step: number?, step_fast: number?, flags: number?) -> (number, boolean),
InputInt2: (label: string, v: any, flags: number?) -> boolean,
InputInt3: (label: string, v: any, flags: number?) -> boolean,
InputInt4: (label: string, v: any, flags: number?) -> boolean,
InputScalar: (label: string, data_type: number, data: any, step: any, step_fast: any, format: string?, flags: number?) -> ...any,
InputScalarN: (label: string, data_type: number, data: any, components: number, step: any, step_fast: any, format: string?, flags: number?) -> ...any,
InputText: (label: string, buf: any, buf_size: number, flags: number?, callback: any, user_data: any) -> ...any,
InputTextDeactivateHook: (...any) -> ...any,
InputTextEx: (...any) -> ...any,
InputTextMultiline: (label: string, buf: any, buf_size: number, size: ImVec2?, flags: number?, callback: any, user_data: any) -> ...any,
InputTextWithHint: (label: string, hint: string, buf: any, buf_size: number, flags: number?, callback: any, user_data: any) -> ...any,
InvisibleButton: (str_id: string, size_arg: ImVec2, flags: number?) -> boolean,
IsActiveIdUsingNavDir: (...any) -> ...any,
IsAliasKey: (...any) -> ...any,
IsAnyItemActive: () -> boolean,
IsAnyItemFocused: () -> boolean,
IsAnyItemHovered: () -> boolean,
IsAnyMouseDown: () -> boolean,
IsClippedEx: (...any) -> ...any,
IsDragDropActive: (...any) -> ...any,
IsDragDropPayloadBeingAccepted: (...any) -> ...any,
IsGamepadKey: (...any) -> ...any,
IsInNavFocusRoute: (...any) -> ...any,
IsItemActivated: () -> boolean,
IsItemActive: () -> boolean,
IsItemClicked: (mouse_button: number?) -> boolean,
IsItemDeactivated: () -> boolean,
IsItemDeactivatedAfterEdit: () -> boolean,
IsItemEdited: () -> boolean,
IsItemFocused: () -> boolean,
IsItemHovered: (flags: number?) -> boolean,
IsItemToggledOpen: () -> boolean,
IsItemToggledSelection: () -> boolean,
IsItemVisible: () -> boolean,
IsKeyChordPressed: (...any) -> ...any,
IsKeyDown: (...any) -> ...any,
IsKeyPressed: (key: number, is_repeat: boolean?) -> boolean,
IsKeyPressedEx: (...any) -> ...any,
IsKeyReleased: (...any) -> ...any,
IsKeyboardKey: (...any) -> ...any,
IsLRModKey: (...any) -> ...any,
IsMouseClicked: (button: number, is_repeat: boolean?) -> boolean,
IsMouseClickedEx: (...any) -> ...any,
IsMouseDoubleClicked: (...any) -> ...any,
IsMouseDown: (...any) -> ...any,
IsMouseDragPastThreshold: (...any) -> ...any,
IsMouseDragging: (button: number, lock_threshold: number?) -> boolean,
IsMouseHoveringRect: (r_min: ImVec2, r_max: ImVec2, clip: boolean?) -> boolean,
IsMouseKey: (...any) -> ...any,
IsMousePosValid: (mouse_pos: any) -> boolean,
IsMouseReleased: (...any) -> ...any,
IsMouseReleasedWithDelay: (button: number, delay: number?) -> boolean,
IsNamedKey: (...any) -> ...any,
IsNamedKeyOrMod: (...any) -> ...any,
IsPopupOpen: (id: string, popup_flags: number?) -> boolean,
IsPopupOpenRequestForItem: (...any) -> ...any,
IsPopupOpenRequestForWindow: (...any) -> ...any,
IsRectVisible: (...any) -> ...any,
IsViewportAbove: (...any) -> ...any,
IsWindowAbove: (...any) -> ...any,
IsWindowActiveAndVisible: (...any) -> ...any,
IsWindowAppearing: () -> boolean,
IsWindowChildOf: (...any) -> ...any,
IsWindowCollapsed: () -> boolean,
IsWindowContentHoverable: (...any) -> ...any,
IsWindowDocked: () -> boolean,
IsWindowFocused: (flags: number?) -> boolean,
IsWindowHovered: (flags: number?) -> boolean,
IsWindowInBeginStack: (...any) -> ...any,
IsWindowNavFocusable: (...any) -> ...any,
IsWindowWithinBeginStackOf: (...any) -> ...any,
ItemAdd: (...any) -> ...any,
ItemHandleShortcut: (...any) -> ...any,
ItemHoverable: (...any) -> ...any,
ItemSize: (...any) -> ...any,
KeepAliveID: (...any) -> ...any,
LabelText: (label: string, fmt: string, ...any) -> (),
ListBox: (...any) -> ...any,
LoadIniSettingsFromDisk: (ini_filename: string) -> (),
LoadIniSettingsFromMemory: (ini_data: string, ini_size: number?) -> (),
LocalizeGetMsg: (...any) -> ...any,
LocalizeRegisterEntries: (...any) -> ...any,
LogBegin: (...any) -> ...any,
LogButtons: () -> (),
LogFinish: () -> (),
LogRenderedText: (...any) -> ...any,
LogSetNextTextDecoration: (...any) -> ...any,
LogText: (fmt: string, ...any) -> (),
LogToBuffer: (...any) -> ...any,
LogToClipboard: (auto_open_depth: number?) -> (),
LogToFile: (auto_open_depth: number?, filename: string?) -> (),
LogToTTY: (auto_open_depth: number?) -> (),
MarkIniSettingsDirty: (...any) -> ...any,
MarkItemEdited: (...any) -> ...any,
MemAlloc: (...any) -> ...any,
MemFree: (...any) -> ...any,
MenuItem: (...any) -> ...any,
MenuItemEx: (...any) -> ...any,
MouseButtonToKey: (...any) -> ...any,
NavApplyItemToResult: (...any) -> ...any,
NavBiasScoringRect: (...any) -> ...any,
NavCalcPreferredRefPos: (...any) -> ...any,
NavCalcPreferredRefPosSource: (...any) -> ...any,
NavClearPreferredPosForAxis: (...any) -> ...any,
NavEndFrame: (...any) -> ...any,
NavHighlightActivated: (...any) -> ...any,
NavInitRequestApplyResult: (...any) -> ...any,
NavInitWindow: (...any) -> ...any,
NavMoveRequestApplyResult: (...any) -> ...any,
NavMoveRequestButNoResultYet: (...any) -> ...any,
NavMoveRequestCancel: (...any) -> ...any,
NavMoveRequestForward: (...any) -> ...any,
NavMoveRequestResolveWithLastItem: (...any) -> ...any,
NavMoveRequestResolveWithPastTreeNode: (...any) -> ...any,
NavMoveRequestSubmit: (...any) -> ...any,
NavMoveRequestTryWrapping: (...any) -> ...any,
NavProcessItem: (...any) -> ...any,
NavProcessItemForTabbingRequest: (...any) -> ...any,
NavRestoreLastChildNavWindow: (...any) -> ...any,
NavRestoreLayer: (...any) -> ...any,
NavSaveLastChildNavWindowIntoParent: (...any) -> ...any,
NavScoreItem: (...any) -> ...any,
NavUpdate: (...any) -> ...any,
NavUpdateAnyRequestFlag: (...any) -> ...any,
NavUpdateCancelRequest: (...any) -> ...any,
NavUpdateContextMenuRequest: (...any) -> ...any,
NavUpdateCreateMoveRequest: (...any) -> ...any,
NavUpdateCreateTabbingRequest: (...any) -> ...any,
NavUpdateCurrentWindowIsScrollPushableX: (...any) -> ...any,
NavUpdatePageUpPageDown: (...any) -> ...any,
NavUpdateWindowing: (...any) -> ...any,
NavUpdateWindowingApplyFocus: (...any) -> ...any,
NavUpdateWindowingOverlay: (...any) -> ...any,
NavUpdateWindowingTarget: (...any) -> ...any,
NewFrame: () -> (),
NewLine: () -> (),
NewTextBuffer: (str: string, size: number?) -> {number},
NextColumn: () -> (),
OpenPopup: (...any) -> ...any,
OpenPopupEx: (...any) -> ...any,
OpenPopupOnItemClick: (str_id: string?, popup_flags: number?) -> boolean,
PayloadIsDataType: (...any) -> ...any,
PlotEx: (...any) -> ...any,
PlotHistogram: (...any) -> ...any,
PlotLines: (...any) -> ...any,
PopClipRect: () -> (),
PopColumnsBackground: (...any) -> ...any,
PopFocusScope: (...any) -> ...any,
PopFont: () -> (),
PopID: () -> (),
PopItemFlag: () -> (),
PopItemWidth: () -> (),
PopPasswordFont: (...any) -> ...any,
PopStyleColor: (count: number?) -> (),
PopStyleVar: (count: number?) -> (),
PopTextWrapPos: () -> (),
ProgressBar: (fraction: number, size_arg: ImVec2?, overlay: string?) -> (),
PushClipRect: (clip_rect_min: ImVec2, clip_rect_max: ImVec2, intersect_with_current_clip_rect: boolean) -> (),
PushColumnClipRect: (...any) -> ...any,
PushColumnsBackground: (...any) -> ...any,
PushFocusScope: (...any) -> ...any,
PushFont: (font: any, font_size_base: number) -> ...any,
PushID: (...any) -> ...any,
PushItemFlag: (option: number, enabled: boolean) -> (),
PushItemWidth: (item_width: number) -> (),
PushMultiItemsWidths: (...any) -> ...any,
PushOverrideID: (...any) -> ...any,
PushPasswordFont: (...any) -> ...any,
PushStyleColor: (...any) -> ...any,
PushStyleVar: (...any) -> ...any,
PushStyleVarX: (idx: number, val_x: number) -> (),
PushStyleVarY: (idx: number, val_y: number) -> (),
PushTextWrapPos: (wrap_local_pos_x: number?) -> (),
RadioButton: (...any) -> ...any,
RadioButtonEx: (...any) -> ...any,
RegisterFontAtlas: (...any) -> ...any,
RemoveSettingsHandler: (...any) -> ...any,
Render: () -> (),
RenderArrow: (...any) -> ...any,
RenderArrowDockMenu: (...any) -> ...any,
RenderArrowPointingAt: (...any) -> ...any,
RenderBullet: (...any) -> ...any,
RenderCheckMark: (...any) -> ...any,
RenderColorComponentMarker: (...any) -> ...any,
RenderColorRectWithAlphaCheckerboard: (...any) -> ...any,
RenderDimmedBackgroundBehindWindow: (...any) -> ...any,
RenderDimmedBackgrounds: (...any) -> ...any,
RenderDragDropTargetRectEx: (...any) -> ...any,
RenderDragDropTargetRectForItem: (...any) -> ...any,
RenderDragDropTargetRectForViewport: (...any) -> ...any,
RenderFrame: (...any) -> ...any,
RenderFrameBorder: (...any) -> ...any,
RenderMouseCursor: (...any) -> ...any,
RenderNavCursor: (...any) -> ...any,
RenderPlatformWindowsDefault: (platform_render_arg: any, renderer_render_arg: any) -> ...any,
RenderRectFilledInRangeH: (...any) -> ...any,
RenderRectFilledWithHole: (...any) -> ...any,
RenderText: (...any) -> ...any,
RenderTextClipped: (...any) -> ...any,
RenderTextClippedEx: (...any) -> ...any,
RenderTextEllipsis: (...any) -> ...any,
RenderTextWrapped: (...any) -> ...any,
RenderViewportsThumbnails: (...any) -> ...any,
ResetMouseDragDelta: (button: number?) -> (),
RoundScalarWithFormatT: (...any) -> ...any,
SameLine: (offset_from_start_x: number?, spacing_w: number?) -> (),
SaveIniSettingsToDisk: (ini_filename: string) -> (),
SaveIniSettingsToMemory: (...any) -> ...any,
ScaleRatioFromValueT: (...any) -> ...any,
ScaleValueFromRatioT: (...any) -> ...any,
ScaleWindowsInViewport: (...any) -> ...any,
ScrollToItem: (...any) -> ...any,
ScrollToRect: (...any) -> ...any,
ScrollToRectEx: (...any) -> ...any,
Scrollbar: (...any) -> ...any,
ScrollbarEx: (...any) -> ...any,
Selectable: (...any) -> ...any,
Separator: () -> (),
SeparatorEx: (...any) -> ...any,
SeparatorText: (label: string) -> (),
SeparatorTextEx: (...any) -> ...any,
SetActiveID: (...any) -> ...any,
SetActiveIdUsingAllKeyboardKeys: (...any) -> ...any,
SetAllocatorFunctions: (alloc_func: any, free_func: any, user_data: any) -> ...any,
SetClipboardText: (text: string) -> (),
SetColorEditOptions: (...any) -> ...any,
SetColumnOffset: (column_index: number, offset: number) -> (),
SetColumnWidth: (column_index: number, width: number) -> (),
SetCurrentContext: (ctx: any) -> ...any,
SetCurrentFont: (...any) -> ...any,
SetCurrentViewport: (...any) -> ...any,
SetCursorPos: (local_pos: ImVec2) -> (),
SetCursorPosX: (x: number) -> (),
SetCursorPosY: (y: number) -> (),
SetCursorScreenPos: (pos: ImVec2) -> (),
SetDragDropPayload: (type: string, data: any, data_size: number, cond: number?) -> boolean,
SetFocusID: (...any) -> ...any,
SetHoveredID: (...any) -> ...any,
SetItemDefaultFocus: () -> (),
SetItemKeyOwner: (...any) -> ...any,
SetItemTooltip: (fmt: string, ...any) -> (),
SetKeyOwner: (...any) -> ...any,
SetKeyOwnersForKeyChord: (...any) -> ...any,
SetKeyboardFocusHere: (offset: number?) -> (),
SetLastItemData: (...any) -> ...any,
SetLastItemDataForChildWindowItem: (...any) -> ...any,
SetLastItemDataForWindow: (...any) -> ...any,
SetMouseCursor: (cursor_type: number) -> (),
SetNavCursorVisible: (visible: boolean) -> (),
SetNavCursorVisibleAfterMove: (...any) -> ...any,
SetNavFocusScope: (...any) -> ...any,
SetNavID: (...any) -> ...any,
SetNavWindow: (...any) -> ...any,
SetNextFrameWantCaptureKeyboard: (want: boolean) -> (),
SetNextFrameWantCaptureMouse: (want: boolean) -> (),
SetNextItemAllowOverlap: () -> (),
SetNextItemColorMarker: (...any) -> ...any,
SetNextItemOpen: (is_open: boolean, cond: number?) -> (),
SetNextItemRefVal: (...any) -> ...any,
SetNextItemShortcut: (key_chord: any, flags: number?) -> (),
SetNextItemStorageID: (storage_id: number) -> (),
SetNextItemWidth: (item_width: number) -> (),
SetNextWindowBgAlpha: (alpha: number) -> (),
SetNextWindowCached: (key: any) -> (),
SetNextWindowChildFlags: (...any) -> ...any,
SetNextWindowClass: (window_class: any) -> (),
SetNextWindowCollapsed: (collapsed: boolean, cond: number?) -> (),
SetNextWindowContentSize: (size: ImVec2) -> (),
SetNextWindowDockID: (id: number, cond: number?) -> (),
SetNextWindowFlags: (...any) -> ...any,
SetNextWindowFocus: () -> (),
SetNextWindowPos: (pos: ImVec2, cond: number?, pivot: ImVec2?) -> (),
SetNextWindowRefreshPolicy: (...any) -> ...any,
SetNextWindowScroll: (scroll: ImVec2) -> (),
SetNextWindowSize: (size: ImVec2, cond: number?) -> (),
SetNextWindowSizeConstraints: (size_min: ImVec2, size_max: ImVec2, custom_callback: any, custom_callback_user_data: any) -> ...any,
SetNextWindowViewport: (id: number) -> (),
SetScrollFromPosX: (...any) -> ...any,
SetScrollFromPosY: (...any) -> ...any,
SetScrollHereX: (center_x_ratio: number?) -> (),
SetScrollHereY: (center_y_ratio: number?) -> (),
SetScrollX: (...any) -> ...any,
SetScrollY: (...any) -> ...any,
SetShortcutRouting: (...any) -> ...any,
SetStateStorage: (tree: any) -> ...any,
SetTabItemClosed: (label: string) -> (),
SetTooltip: (fmt: string, ...any) -> (),
SetWindowClipRectBeforeSetChannel: (...any) -> ...any,
SetWindowCollapsed: (...any) -> ...any,
SetWindowDock: (...any) -> ...any,
SetWindowFocus: (...any) -> ...any,
SetWindowFontScale: (...any) -> ...any,
SetWindowHiddenAndSkipItemsForCurrentFrame: (...any) -> ...any,
SetWindowHitTestHole: (...any) -> ...any,
SetWindowPos: (...any) -> ...any,
SetWindowSize: (...any) -> ...any,
SetWindowViewport: (...any) -> ...any,
ShadeVertsLinearColorGradientKeepAlpha: (...any) -> ...any,
ShadeVertsLinearUV: (...any) -> ...any,
ShadeVertsTransformPos: (...any) -> ...any,
Shortcut: (...any) -> ...any,
ShowAboutWindow: (p_open: boolean?) -> ...any,
ShowDebugLogWindow: (p_open: boolean?) -> ...any,
ShowDemoWindow: (p_open: boolean?) -> ...any,
ShowFontAtlas: (...any) -> ...any,
ShowFontSelector: (label: string) -> (),
ShowIDStackToolWindow: (p_open: boolean?) -> ...any,
ShowMetricsWindow: (p_open: boolean?) -> ...any,
ShowStyleEditor: (ref: any) -> ...any,
ShowStyleSelector: (label: string) -> boolean,
ShowUserGuide: () -> (),
ShrinkWidths: (...any) -> ...any,
Shutdown: (...any) -> ...any,
SliderAngle: (label: string, v_rad: number, v_degrees_min: number?, v_degrees_max: number?, format: string?, flags: number?) -> (number, boolean),
SliderBehavior: (...any) -> ...any,
SliderBehaviorT: (...any) -> ...any,
SliderFloat: (label: string, v: number, v_min: number, v_max: number, format: string?, flags: number?) -> (number, boolean),
SliderFloat2: (label: string, v: any, v_min: number, v_max: number, format: string?, flags: number?) -> boolean,
SliderFloat3: (label: string, v: any, v_min: number, v_max: number, format: string?, flags: number?) -> boolean,
SliderFloat4: (label: string, v: any, v_min: number, v_max: number, format: string?, flags: number?) -> boolean,
SliderInt: (label: string, v: number, v_min: number, v_max: number, format: string?, flags: number?) -> (number, boolean),
SliderInt2: (label: string, v: any, v_min: number, v_max: number, format: string?, flags: number?) -> boolean,
SliderInt3: (label: string, v: any, v_min: number, v_max: number, format: string?, flags: number?) -> boolean,
SliderInt4: (label: string, v: any, v_min: number, v_max: number, format: string?, flags: number?) -> boolean,
SliderScalar: (label: string, data_type: number, data: any, min: any, max: any, format: string?, flags: number?) -> ...any,
SliderScalarN: (label: string, data_type: number, v: any, components: number, v_min: any, v_max: any, format: string?, flags: number?) -> ...any,
SmallButton: (label: string) -> boolean,
Spacing: () -> (),
SplitterBehavior: (...any) -> ...any,
StartMouseMovingWindow: (...any) -> ...any,
StartMouseMovingWindowOrNode: (...any) -> ...any,
StopMouseMovingWindow: (...any) -> ...any,
StyleColorsClassic: (dst: any) -> ...any,
StyleColorsDark: (dst: any) -> ...any,
StyleColorsLight: (dst: any) -> ...any,
TabBarAddTab: (...any) -> ...any,
TabBarCalcMaxTabWidth: (...any) -> ...any,
TabBarCalcTabID: (...any) -> ...any,
TabBarCloseTab: (...any) -> ...any,
TabBarFindByID: (...any) -> ...any,
TabBarFindMostRecentlySelectedTabForActiveWindow: (...any) -> ...any,
TabBarFindTabByID: (...any) -> ...any,
TabBarFindTabByOrder: (...any) -> ...any,
TabBarGetCurrentTab: (...any) -> ...any,
TabBarGetTabName: (...any) -> ...any,
TabBarGetTabOrder: (...any) -> ...any,
TabBarGetTabPos: (...any) -> ...any,
TabBarLayout: (...any) -> ...any,
TabBarProcessReorder: (...any) -> ...any,
TabBarQueueFocus: (...any) -> ...any,
TabBarQueueReorder: (...any) -> ...any,
TabBarQueueReorderFromMousePos: (...any) -> ...any,
TabBarRemove: (...any) -> ...any,
TabBarRemoveTab: (...any) -> ...any,
TabBarScrollClamp: (...any) -> ...any,
TabBarScrollToTab: (...any) -> ...any,
TabBarScrollingButtons: (...any) -> ...any,
TabBarTabListPopupButton: (...any) -> ...any,
TabItemBackground: (...any) -> ...any,
TabItemButton: (label: string, flags: number?) -> boolean,
TabItemCalcSize: (...any) -> ...any,
TabItemEx: (...any) -> ...any,
TabItemLabelAndCloseButton: (...any) -> ...any,
TabItemSpacing: (...any) -> ...any,
TableAngledHeadersRow: () -> (),
TableAngledHeadersRowEx: (...any) -> ...any,
TableApplyExternalUnclipRect: (...any) -> ...any,
TableApplyQueuedRequests: (...any) -> ...any,
TableBeginCell: (...any) -> ...any,
TableBeginContextMenuPopup: (...any) -> ...any,
TableBeginInitMemory: (...any) -> ...any,
TableBeginRow: (...any) -> ...any,
TableCalcMaxColumnWidth: (...any) -> ...any,
TableDrawBorders: (...any) -> ...any,
TableDrawDefaultContextMenu: (...any) -> ...any,
TableEndCell: (...any) -> ...any,
TableEndRow: (...any) -> ...any,
TableFindByID: (...any) -> ...any,
TableFixColumnSortDirection: (...any) -> ...any,
TableFixDisplayOrder: (...any) -> ...any,
TableGcCompactSettings: (...any) -> ...any,
TableGcCompactTransientBuffers: (...any) -> ...any,
TableGetBoundSettings: (...any) -> ...any,
TableGetCellBgRect: (...any) -> ...any,
TableGetColumnCount: () -> number,
TableGetColumnFlags: (column_n: number?) -> number,
TableGetColumnIndex: () -> number,
TableGetColumnName: (...any) -> ...any,
TableGetColumnNextSortDirection: (...any) -> ...any,
TableGetColumnResizeID: (...any) -> ...any,
TableGetColumnWidthAuto: (...any) -> ...any,
TableGetHeaderAngledMaxLabelWidth: (...any) -> ...any,
TableGetHeaderRowHeight: (...any) -> ...any,
TableGetHoveredColumn: () -> number,
TableGetHoveredRow: (...any) -> ...any,
TableGetInstanceData: (...any) -> ...any,
TableGetInstanceID: (...any) -> ...any,
TableGetRowIndex: () -> number,
TableGetSortSpecs: () -> any,
TableHeader: (label: string) -> (),
TableHeadersRow: () -> (),
TableInitColumnDefaults: (...any) -> ...any,
TableLoadSettings: (...any) -> ...any,
TableLoadSettingsForColumn: (...any) -> ...any,
TableLoadSettingsForColumns: (...any) -> ...any,
TableMergeDrawChannels: (...any) -> ...any,
TableNextColumn: () -> boolean,
TableNextRow: (row_flags: number?, row_min_height: number?) -> (),
TableOpenContextMenu: (...any) -> ...any,
TablePopBackgroundChannel: (...any) -> ...any,
TablePopColumnChannel: (...any) -> ...any,
TablePushBackgroundChannel: (...any) -> ...any,
TablePushColumnChannel: (...any) -> ...any,
TableQueueSetColumnDisplayOrder: (...any) -> ...any,
TableReconcileColumns: (...any) -> ...any,
TableRemove: (...any) -> ...any,
TableResetSettings: (...any) -> ...any,
TableSaveSettings: (...any) -> ...any,
TableSetBgColor: (target: number, color: number, column_n: number?) -> (),
TableSetColumnDisplayOrder: (...any) -> ...any,
TableSetColumnEnabled: (column_n: number, enabled: boolean) -> (),
TableSetColumnIndex: (column_n: number) -> boolean,
TableSetColumnSortDirection: (...any) -> ...any,
TableSetColumnWidth: (...any) -> ...any,
TableSetColumnWidthAutoAll: (...any) -> ...any,
TableSetColumnWidthAutoSingle: (...any) -> ...any,
TableSettingsAddSettingsHandler: (...any) -> ...any,
TableSettingsCreate: (...any) -> ...any,
TableSettingsFindByID: (...any) -> ...any,
TableSetupColumn: (label: string, flags: number?, init_width_or_weight: number?, user_data: number?) -> (),
TableSetupDrawChannels: (...any) -> ...any,
TableSetupScrollFreeze: (columns: number, rows: number) -> (),
TableSortSpecsBuild: (...any) -> ...any,
TableSortSpecsSanitize: (...any) -> ...any,
TableUpdateBorders: (...any) -> ...any,
TableUpdateColumnsWeightFromWidth: (...any) -> ...any,
TableUpdateLayout: (...any) -> ...any,
TeleportMousePos: (...any) -> ...any,
TempInputIsActive: (...any) -> ...any,
TempInputScalar: (...any) -> ...any,
TempInputText: (...any) -> ...any,
TestKeyOwner: (...any) -> ...any,
Text: (fmt: string, ...any) -> (),
TextAligned: (...any) -> ...any,
TextBufferToString: (buf: {number}) -> string,
TextColored: (col: ImVec4, fmt: string, ...any) -> (),
TextDisabled: (fmt: string, ...any) -> (),
TextEx: (...any) -> ...any,
TextLink: (label: string) -> boolean,
TextLinkOpenURL: (label: string, url: string?) -> boolean,
TextUnformatted: (text: string, text_end: string?) -> (),
TextV: (fmt: string, ...any) -> (),
TextWrapped: (fmt: string, ...any) -> (),
TranslateWindow: (...any) -> ...any,
TranslateWindowsInViewport: (...any) -> ...any,
TreeNode: (...any) -> ...any,
TreeNodeBehavior: (...any) -> ...any,
TreeNodeDrawLineToChildNode: (...any) -> ...any,
TreeNodeDrawLineToTreePop: (...any) -> ...any,
TreeNodeEx: (...any) -> ...any,
TreeNodeExV: (...any) -> ...any,
TreeNodeGetOpen: (storage_id: number) -> boolean,
TreeNodeSetOpen: (...any) -> ...any,
TreeNodeUpdateNextOpen: (...any) -> ...any,
TreeNodeV: (...any) -> ...any,
TreePop: () -> (),
TreePush: (...any) -> ...any,
TreePushOverrideID: (...any) -> ...any,
Unindent: (indent_w: number?) -> (),
UnregisterFontAtlas: (...any) -> ...any,
UpdateAliasKey: (...any) -> ...any,
UpdateCurrentFontSize: (...any) -> ...any,
UpdateDebugToolItemPathQuery: (...any) -> ...any,
UpdateFontsEndFrame: (...any) -> ...any,
UpdateFontsNewFrame: (...any) -> ...any,
UpdateHoveredWindowAndCaptureFlags: (...any) -> ...any,
UpdateInputEvents: (...any) -> ...any,
UpdateKeyRoutingTable: (...any) -> ...any,
UpdateKeyboardInputs: (...any) -> ...any,
UpdateMouseInputs: (...any) -> ...any,
UpdateMouseMovingWindowEndFrame: (...any) -> ...any,
UpdateMouseMovingWindowNewFrame: (...any) -> ...any,
UpdateMouseWheel: (...any) -> ...any,
UpdatePlatformWindows: () -> (),
UpdateSettings: (...any) -> ...any,
UpdateTexturesEndFrame: (...any) -> ...any,
UpdateTexturesNewFrame: (...any) -> ...any,
UpdateTryMergeWindowIntoHostViewport: (...any) -> ...any,
UpdateTryMergeWindowIntoHostViewports: (...any) -> ...any,
UpdateViewportPlatformMonitor: (...any) -> ...any,
UpdateViewportsEndFrame: (...any) -> ...any,
UpdateViewportsNewFrame: (...any) -> ...any,
UpdateWindowInFocusOrderList: (...any) -> ...any,
UpdateWindowParentAndRootLinks: (...any) -> ...any,
UpdateWindowSkipRefresh: (...any) -> ...any,
VSliderFloat: (label: string, size: ImVec2, v: number, v_min: number, v_max: number, format: string?, flags: number?) -> (number, boolean),
VSliderInt: (label: string, size: ImVec2, v: number, v_min: number, v_max: number, format: string?, flags: number?) -> (number, boolean),
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,
WindowRectAbsToRel: (...any) -> ...any,
WindowRectRelToAbs: (...any) -> ...any,
WindowSelectViewport: (...any) -> ...any,
WindowSettingsAddSettingsHandler: (...any) -> ...any,
WindowSyncOwnedViewport: (...any) -> ...any,
_ByteArrayToString: (...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 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 }
export type ImGuiChildFlags = { None: number, Borders: number, AlwaysUseWindowPadding: number, ResizeX: number, ResizeY: number, AutoResizeX: number, AutoResizeY: number, AlwaysAutoResize: number, FrameStyle: number, NavFlattened: number, ResizeBoth: number, ResizeXAndY: number, [string]: number }
export type ImGuiCol = { Text: number, TextDisabled: number, WindowBg: number, ChildBg: number, PopupBg: number, Border: number, BorderShadow: number, FrameBg: number, FrameBgHovered: number, FrameBgActive: number, TitleBg: number, TitleBgActive: number, TitleBgCollapsed: number, MenuBarBg: number, ScrollbarBg: number, ScrollbarGrab: number, ScrollbarGrabHovered: number, ScrollbarGrabActive: number, CheckMark: number, CheckboxSelectedBg: number, SliderGrab: number, SliderGrabActive: number, Button: number, ButtonHovered: number, ButtonActive: number, Header: number, HeaderHovered: number, HeaderActive: number, Separator: number, SeparatorHovered: number, SeparatorActive: number, ResizeGrip: number, ResizeGripHovered: number, ResizeGripActive: number, InputTextCursor: number, TabHovered: number, Tab: number, TabSelected: number, TabSelectedOverline: number, TabDimmed: number, TabDimmedSelected: number, TabDimmedSelectedOverline: number, DockingPreview: number, DockingEmptyBg: number, PlotLines: number, PlotLinesHovered: number, PlotHistogram: number, PlotHistogramHovered: number, TableHeaderBg: number, TableBorderStrong: number, TableBorderLight: number, TableRowBg: number, TableRowBgAlt: number, TextLink: number, TextSelectedBg: number, TreeLines: number, DragDropTarget: number, DragDropTargetBg: number, UnsavedMarker: number, NavCursor: number, NavWindowingHighlight: number, NavWindowingDimBg: number, ModalWindowDimBg: number, COUNT: number, [string]: number }
export type ImGuiColorEditFlags = { None: number, NoAlpha: number, NoPicker: number, NoOptions: number, NoSmallPreview: number, NoInputs: number, NoTooltip: number, NoLabel: number, NoSidePreview: number, NoDragDrop: number, NoBorder: number, NoColorMarkers: number, AlphaOpaque: number, AlphaNoBg: number, AlphaPreviewHalf: number, AlphaBar: number, HDR: number, DisplayRGB: number, DisplayHSV: number, DisplayHex: number, Uint8: number, Float: number, PickerHueBar: number, PickerHueWheel: number, PickerNoRotate: number, InputRGB: number, InputHSV: number, DefaultOptions_: number, AlphaMask_: number, DisplayMask_: number, DataTypeMask_: number, PickerMask_: number, InputMask_: number, [string]: number }
export type ImGuiComboFlags = { None: number, PopupAlignLeft: number, HeightSmall: number, HeightRegular: number, HeightLarge: number, HeightLargest: number, NoArrowButton: number, NoPreview: number, WidthFitPreview: number, HeightMask_: number, CustomPreview: number, [string]: number }
export type ImGuiCond = { None: number, Always: number, Once: number, FirstUseEver: number, Appearing: number, [string]: number }
export type ImGuiConfigFlags = { None: number, NavEnableKeyboard: number, NavEnableGamepad: number, NoMouse: number, NoMouseCursorChange: number, NoKeyboard: number, DockingEnable: number, ViewportsEnable: number, IsSRGB: number, IsTouchScreen: number, [string]: number }
export type ImGuiDragDropFlags = { None: number, SourceNoPreviewTooltip: number, SourceNoDisableHover: number, SourceNoHoldToOpenOthers: number, SourceAllowNullID: number, SourceExtern: number, PayloadAutoExpire: number, PayloadNoCrossContext: number, PayloadNoCrossProcess: number, AcceptBeforeDelivery: number, AcceptNoDrawDefaultRect: number, AcceptNoPreviewTooltip: number, AcceptDrawAsHovered: number, AcceptPeekOnly: number, [string]: number }
export type ImGuiFocusedFlags = { None: number, ChildWindows: number, RootWindow: number, AnyWindow: number, NoPopupHierarchy: number, DockHierarchy: number, RootAndChildWindows: number, [string]: number }
export type ImGuiHoveredFlags = { None: number, ChildWindows: number, RootWindow: number, AnyWindow: number, NoPopupHierarchy: number, DockHierarchy: number, AllowWhenBlockedByPopup: number, AllowWhenBlockedByActiveItem: number, AllowWhenOverlappedByItem: number, AllowWhenOverlappedByWindow: number, AllowWhenDisabled: number, NoNavOverride: number, ForTooltip: number, Stationary: number, DelayNone: number, DelayShort: number, DelayNormal: number, NoSharedDelay: number, AllowWhenOverlapped: number, RectOnly: number, RootAndChildWindows: number, DelayMask_: number, AllowedMaskForIsWindowHovered: number, AllowedMaskForIsItemHovered: number, [string]: number }
export type ImGuiInputFlags = { None: number, Repeat: number, RouteActive: number, RouteFocused: number, RouteGlobal: number, RouteAlways: number, RouteOverFocused: number, RouteOverActive: number, RouteUnlessBgFocused: number, RouteFromRootWindow: number, Tooltip: number, RepeatRateDefault: number, RepeatRateNavMove: number, RepeatRateNavTweak: number, RepeatUntilRelease: number, RepeatUntilKeyModsChange: number, RepeatUntilKeyModsChangeFromNone: number, RepeatUntilOtherKeyPress: number, LockThisFrame: number, LockUntilRelease: number, CondHovered: number, CondActive: number, CondDefault_: number, RepeatRateMask_: number, RepeatUntilMask_: number, RepeatMask_: number, CondMask_: number, RouteTypeMask_: number, RouteOptionsMask_: number, SupportedByIsKeyPressed: number, SupportedByIsMouseClicked: number, SupportedByShortcut: number, SupportedBySetNextItemShortcut: number, SupportedBySetKeyOwner: number, SupportedBySetItemKeyOwner: number, [string]: number }
export type ImGuiInputTextFlags = { None: number, CharsDecimal: number, CharsHexadecimal: number, CharsScientific: number, CharsUppercase: number, CharsNoBlank: number, AllowTabInput: number, EnterReturnsTrue: number, EscapeClearsAll: number, CtrlEnterForNewLine: number, ReadOnly: number, Password: number, AlwaysOverwrite: number, AutoSelectAll: number, ParseEmptyRefVal: number, DisplayEmptyRefVal: number, NoHorizontalScroll: number, NoUndoRedo: number, ElideLeft: number, CallbackCompletion: number, CallbackHistory: number, CallbackAlways: number, CallbackCharFilter: number, CallbackResize: number, CallbackEdit: number, WordWrap: number, Multiline: number, TempInput: number, LocalizeDecimalPoint: number, [string]: number }
export type ImGuiItemFlags = { None: number, NoTabStop: number, NoNav: number, NoNavDefaultFocus: number, ButtonRepeat: number, AutoClosePopups: number, AllowDuplicateId: number, Disabled: number, LiveEditOnInputText: number, LiveEditOnInputScalar: number, LiveEditOnInput: number, ReadOnly: number, MixedValue: number, NoWindowHoverableCheck: number, AllowOverlap: number, NoNavDisableMouseHover: number, NoMarkEdited: number, NoFocus: number, Inputable: number, HasSelectionUserData: number, IsMultiSelect: number, Default_: number, [string]: number }
export type ImGuiItemStatusFlags = { None: number, HoveredRect: number, HasDisplayRect: number, Edited: number, ToggledSelection: number, ToggledOpen: number, HasDeactivated: number, Deactivated: number, HoveredWindow: number, Visible: number, HasClipRect: number, HasShortcut: number, EditedInternal: number, [string]: number }
export type 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 ImGuiMouseSource = { Mouse: number, TouchScreen: number, Pen: number, COUNT: 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 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 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 }
export type ImGuiModule = {
ImGui: ImGuiAPI,
ImVec2: (x: number?, y: number?, z: number?, w: number?) -> ImVec2,
ImVec4: (x: number?, y: number?, z: number?, w: number?) -> ImVec4,
ImDrawFlags: ImDrawFlags,
ImDrawListFlags: ImDrawListFlags,
ImFontFlags: ImFontFlags,
ImGuiBackendFlags: ImGuiBackendFlags,
ImGuiButtonFlags: ImGuiButtonFlags,
ImGuiChildFlags: ImGuiChildFlags,
ImGuiCol: ImGuiCol,
ImGuiColorEditFlags: ImGuiColorEditFlags,
ImGuiComboFlags: ImGuiComboFlags,
ImGuiCond: ImGuiCond,
ImGuiConfigFlags: ImGuiConfigFlags,
ImGuiDragDropFlags: ImGuiDragDropFlags,
ImGuiFocusedFlags: ImGuiFocusedFlags,
ImGuiHoveredFlags: ImGuiHoveredFlags,
ImGuiInputFlags: ImGuiInputFlags,
ImGuiInputTextFlags: ImGuiInputTextFlags,
ImGuiItemFlags: ImGuiItemFlags,
ImGuiItemStatusFlags: ImGuiItemStatusFlags,
ImGuiKey: ImGuiKey,
ImGuiMouseSource: ImGuiMouseSource,
ImGuiNextItemDataFlags: ImGuiNextItemDataFlags,
ImGuiPopupFlags: ImGuiPopupFlags,
ImGuiSelectableFlags: ImGuiSelectableFlags,
ImGuiSliderFlags: ImGuiSliderFlags,
ImGuiStyleVar: ImGuiStyleVar,
ImGuiTreeNodeFlags: ImGuiTreeNodeFlags,
ImGuiViewportFlags: ImGuiViewportFlags,
ImGuiWindowFlags: ImGuiWindowFlags,
[string]: any,
}
return (require(script:WaitForChild("Impl")) :: any) :: ImGuiModule
+1 -1
View File
@@ -1 +1 @@
[[149, "imgui.lua"], [18609, "imgui_demo.lua"], [25894, "imgui_draw.lua"], [31132, "imgui_h.lua"], [33330, "imgui_impl_love.lua"], [33870, "imgui_internal.lua"], [37042, "imgui_tables.lua"], [41389, "imgui_widgets.lua"], [51144, "imstb_rectpack.lua"], [51590, "imstb_textedit.lua"], [52831, "imstb_truetype.lua"], [55299, "imstd_minstdio.lua"]] [[149, "imgui.lua"], [21425, "imgui_demo.lua"], [29843, "imgui_draw.lua"], [35613, "imgui_h.lua"], [38279, "imgui_impl_love.lua"], [38819, "imgui_internal.lua"], [42265, "imgui_tables.lua"], [47260, "imgui_widgets.lua"], [57870, "imstb_rectpack.lua"], [58534, "imstb_textedit.lua"], [60076, "imstb_truetype.lua"], [62864, "imstd_minstdio.lua"]]
+10204 -2639
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -3,7 +3,7 @@
"tree": { "tree": {
"$className": "DataModel", "$className": "DataModel",
"ReplicatedStorage": { "ReplicatedStorage": {
"ImGui": { "$path": "build/ImGui.luau" } "ImGui": { "$path": "build/ImGuiTyped.luau", "Impl": { "$path": "build/ImGui.luau" } }
}, },
"StarterPlayer": { "StarterPlayer": {
"StarterPlayerScripts": { "StarterPlayerScripts": {
-1
View File
@@ -45,7 +45,6 @@ KNOWN ISSUES
PERFORMANCE IDEAS (not done) PERFORMANCE IDEAS (not done)
---------------------------- ----------------------------
- Opt-in cached windows (skip user code for idle windows).
- Cheaper visible ColorEdit4 rows; cheaper Begin() for child windows. - Cheaper visible ColorEdit4 rows; cheaper Begin() for child windows.
PLANNED EXTENSIONS PLANNED EXTENSIONS
+452 -293
View File
@@ -1,336 +1,495 @@
-- LocalScript. Build the library first: `python3 tools/bundle.py` -> build/ImGui.luau (see default.project.json)
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService") local RunService = game:GetService("RunService")
local AssetService = game:GetService("AssetService")
local G = require(ReplicatedStorage:WaitForChild("ImGui")) local G = require(ReplicatedStorage:WaitForChild("ImGui"))
local ExampleImageViewerData, ExampleImageViewer_DrawOptions, ExampleImageViewer_DrawCanvas = G.ExampleImageViewerData, G.ExampleImageViewer_DrawOptions, G.ExampleImageViewer_DrawCanvas
local ImGui, ImVec4, ImGuiConfigFlags, ImGui_ImplRoblox = G.ImGui, G.ImVec4, G.ImGuiConfigFlags, G.ImGui_ImplRoblox local BaseEnvironment = getfenv()
setfenv(1, setmetatable({}, {
__index = function(_, Key)
local Value = G[Key]
if Value ~= nil then
return Value
end
return BaseEnvironment[Key]
end,
}))
local main_scale = 1.0 local MainScale = 1.0
-- Roblox UI points are multiplied by the OS display scale (e.g. 150%), which makes imgui look large. local RenderScale = 1.0
-- Set this to your display scale to render imgui at physical-pixel resolution (same size as a desktop imgui app). local RenderRate = 60
local render_scale = 1.0 local ThumbnailSize = 420
local clear_color = ImVec4(0.45, 0.55, 0.60, 1.00) local FontSize = 13.0
local show_demo_window = true local FontNames = { "Cousine-Regular", "DroidSans", "Karla-Regular", "ProggyTiny", "Roboto-Medium" }
local show_another_window = false
local f = 0.0 local State = {
local counter = 0 ClearColor = ImVec4(0.45, 0.55, 0.60, 1.00),
ShowDemoWindow = true,
ShowAnotherWindow = false,
Float = 0.0,
Counter = 0,
}
local Spinner = {
Show = true,
Speed = 1.0,
Angle = 0.0,
Color = ImVec4(0.80, 0.65, 0.97, 1.0),
}
ImGui.CreateContext() ImGui.CreateContext()
local io = ImGui.GetIO() local IO = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard, ImGuiConfigFlags.DockingEnable) IO.ConfigFlags = bit32.bor(IO.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard, ImGuiConfigFlags.DockingEnable)
-- Catppuccin Mocha, Mauve accent (https://catppuccin.com/palette) -- catppuccin mocha mauve https://catppuccin.com/palette
local function StyleColorsCatppuccinMocha(style) local function StyleColorsCatppuccinMocha(Style)
local function hex(h, a) local function Hex(Value, Alpha)
return ImVec4(bit32.extract(h, 16, 8) / 255, bit32.extract(h, 8, 8) / 255, bit32.extract(h, 0, 8) / 255, a or 1.0) return ImVec4(
end bit32.extract(Value, 16, 8) / 255,
local crust, mantle, base = 0x11111b, 0x181825, 0x1e1e2e bit32.extract(Value, 8, 8) / 255,
local surface0, surface1, surface2 = 0x313244, 0x45475a, 0x585b70 bit32.extract(Value, 0, 8) / 255,
local overlay0, overlay2, text = 0x6c7086, 0x9399b2, 0xcdd6f4 Alpha or 1.0
local mauve, lavender = 0xcba6f7, 0xb4befe )
local C, c = G.ImGuiCol, style.Colors end
local function set(idx, h, a) c[idx] = hex(h, a) end
set(C.Text, text); set(C.TextDisabled, overlay0) local Crust, Mantle, Base = 0x11111b, 0x181825, 0x1e1e2e
set(C.WindowBg, base); set(C.ChildBg, base, 0.0) local Surface0, Surface1, Surface2 = 0x313244, 0x45475a, 0x585b70
set(C.PopupBg, mantle, 0.98); set(C.Border, surface1); set(C.BorderShadow, crust, 0.0) local Overlay0, Overlay2, Text = 0x6c7086, 0x9399b2, 0xcdd6f4
set(C.FrameBg, surface0); set(C.FrameBgHovered, surface1); set(C.FrameBgActive, surface2) local Mauve, Lavender = 0xcba6f7, 0xb4befe
set(C.TitleBg, mantle); set(C.TitleBgActive, crust); set(C.TitleBgCollapsed, mantle, 0.75)
set(C.MenuBarBg, mantle) local Entries = {
set(C.ScrollbarBg, mantle); set(C.ScrollbarGrab, surface1) { "Text", Text },
set(C.ScrollbarGrabHovered, surface2); set(C.ScrollbarGrabActive, overlay0) { "TextDisabled", Overlay0 },
set(C.CheckMark, mauve); set(C.SliderGrab, mauve, 0.8); set(C.SliderGrabActive, mauve)
set(C.Button, surface0); set(C.ButtonHovered, surface1); set(C.ButtonActive, mauve, 0.6) { "WindowBg", Base },
set(C.Header, surface0); set(C.HeaderHovered, surface1); set(C.HeaderActive, mauve, 0.5) { "ChildBg", Base, 0.0 },
set(C.Separator, surface1); set(C.SeparatorHovered, mauve, 0.7); set(C.SeparatorActive, mauve) { "PopupBg", Mantle, 0.98 },
set(C.ResizeGrip, mauve, 0.2); set(C.ResizeGripHovered, mauve, 0.6); set(C.ResizeGripActive, mauve, 0.9) { "Border", Surface1 },
set(C.InputTextCursor, text) { "BorderShadow", Crust, 0.0 },
set(C.TabHovered, surface2); set(C.Tab, surface0)
set(C.TabSelected, surface1); set(C.TabSelectedOverline, mauve) { "FrameBg", Surface0 },
set(C.TabDimmed, mantle); set(C.TabDimmedSelected, surface0); set(C.TabDimmedSelectedOverline, overlay0) { "FrameBgHovered", Surface1 },
set(C.DockingPreview, mauve, 0.7); set(C.DockingEmptyBg, crust) { "FrameBgActive", Surface2 },
set(C.PlotLines, overlay2); set(C.PlotLinesHovered, mauve)
set(C.PlotHistogram, mauve, 0.8); set(C.PlotHistogramHovered, lavender) { "TitleBg", Mantle },
set(C.TableHeaderBg, surface0); set(C.TableBorderStrong, surface1); set(C.TableBorderLight, surface0) { "TitleBgActive", Crust },
set(C.TableRowBg, base, 0.0); set(C.TableRowBgAlt, surface0, 0.3) { "TitleBgCollapsed", Mantle, 0.75 },
set(C.TextLink, mauve); set(C.TextSelectedBg, mauve, 0.35) { "MenuBarBg", Mantle },
set(C.TreeLines, surface2)
set(C.DragDropTarget, mauve, 0.9) { "ScrollbarBg", Mantle },
set(C.NavCursor, lavender); set(C.NavWindowingHighlight, lavender, 0.7) { "ScrollbarGrab", Surface1 },
set(C.NavWindowingDimBg, crust, 0.5); set(C.ModalWindowDimBg, crust, 0.6) { "ScrollbarGrabHovered", Surface2 },
{ "ScrollbarGrabActive", Overlay0 },
{ "CheckMark", Mauve },
{ "SliderGrab", Mauve, 0.8 },
{ "SliderGrabActive", Mauve },
{ "Button", Surface0 },
{ "ButtonHovered", Surface1 },
{ "ButtonActive", Mauve, 0.6 },
{ "Header", Surface0 },
{ "HeaderHovered", Surface1 },
{ "HeaderActive", Mauve, 0.5 },
{ "Separator", Surface1 },
{ "SeparatorHovered", Mauve, 0.7 },
{ "SeparatorActive", Mauve },
{ "ResizeGrip", Mauve, 0.2 },
{ "ResizeGripHovered", Mauve, 0.6 },
{ "ResizeGripActive", Mauve, 0.9 },
{ "InputTextCursor", Text },
{ "Tab", Surface0 },
{ "TabHovered", Surface2 },
{ "TabSelected", Surface1 },
{ "TabSelectedOverline", Mauve },
{ "TabDimmed", Mantle },
{ "TabDimmedSelected", Surface0 },
{ "TabDimmedSelectedOverline", Overlay0 },
{ "DockingPreview", Mauve, 0.7 },
{ "DockingEmptyBg", Crust },
{ "PlotLines", Overlay2 },
{ "PlotLinesHovered", Mauve },
{ "PlotHistogram", Mauve, 0.8 },
{ "PlotHistogramHovered", Lavender },
{ "TableHeaderBg", Surface0 },
{ "TableBorderStrong", Surface1 },
{ "TableBorderLight", Surface0 },
{ "TableRowBg", Base, 0.0 },
{ "TableRowBgAlt", Surface0, 0.3 },
{ "TextLink", Mauve },
{ "TextSelectedBg", Mauve, 0.35 },
{ "TreeLines", Surface2 },
{ "DragDropTarget", Mauve, 0.9 },
{ "NavCursor", Lavender },
{ "NavWindowingHighlight", Lavender, 0.7 },
{ "NavWindowingDimBg", Crust, 0.5 },
{ "ModalWindowDimBg", Crust, 0.6 },
}
for _, Entry in ipairs(Entries) do
local Name, Color, Alpha = Entry[1], Entry[2], Entry[3]
Style.Colors[ImGuiCol[Name]] = Hex(Color, Alpha)
end
end end
local style = ImGui.GetStyle() local Style = ImGui.GetStyle()
StyleColorsCatppuccinMocha(style) StyleColorsCatppuccinMocha(Style)
style:ScaleAllSizes(main_scale) Style:ScaleAllSizes(MainScale)
style.FontScaleDpi = main_scale Style.FontScaleDpi = MainScale
-- Fonts: ProggyClean stays the default (added first); the rest show up in Style Editor > Fonts IO.Fonts:AddFontDefault()
io.Fonts:AddFontDefault() for _, Name in ipairs(FontNames) do
for _, name in ipairs({ "Cousine-Regular", "DroidSans", "Karla-Regular", "ProggyTiny", "Roboto-Medium" }) do IO.Fonts:AddFontFromFileTTF("fonts/" .. Name .. ".ttf", FontSize)
io.Fonts:AddFontFromFileTTF("fonts/" .. name .. ".ttf", 13.0)
end end
ImGui_ImplRoblox.Init(nil, render_scale) ImGui_ImplRoblox.Init(nil, RenderScale)
ImGui_ImplRoblox.SetRenderRate(60) -- tile redraws per second; imgui logic + your code still run every frame ImGui_ImplRoblox.SetRenderRate(RenderRate)
-- ImGui_ImplRoblox.SetBusyRenderRate(30) -- optional: lower redraw rate while scrolling/resizing/moving windows -- ImGui_ImplRoblox.SetBusyRenderRate(30) -- Optional: lower redraw rate while scrolling/resizing/moving windows
ImGui_ImplRoblox.SetProfiling(true) -- MicroProfiler (Ctrl+F6) labels; turn off when not profiling -- ImGui_ImplRoblox.SetProfiling(true) -- MicroProfiler labels; turn off when not profiling
-- ImGui_ImplRoblox.SetTextProxy(true) -- Optional: hidden TextBox for OS typing (any layout), paste and copy/cut
local function ProfileBegin(Name)
if ImGui_ImplRoblox.IsProfiling() then
debug.profilebegin(Name)
end
end
local function ProfileEnd()
if ImGui_ImplRoblox.IsProfiling() then
debug.profileend()
end
end
local function P(name) if ImGui_ImplRoblox.IsProfiling() then debug.profilebegin(name) end end
local function PE() if ImGui_ImplRoblox.IsProfiling() then debug.profileend() end end
--------------------------------------------------------------------------------------------------------------------
-- Custom widget: ImGui.Embed(id, size, flags, init) -> Frame
-- Reserves a widget-sized area in the current window and shows a real Frame there, so any Instances (UI, a
-- ViewportFrame, ...) can live inside an imgui window. Immediate-mode friendly:
-- * one Frame per id (per window, through the ID stack, like any widget); `init(frame)` runs only when it is created
-- * call it every frame the Frame should be visible; when it isn't called (window closed/collapsed, tab or tree node
-- closed, scrolled fully out of view) the Frame is hidden, never destroyed, so your Instances keep their state
-- * size uses imgui sizing (ImVec2(-1, h) = fill width, 0 = default), style gives padding/rounding/background/border
-- * returns the Frame: edit properties, add or remove children any time
-- ImGui.DestroyEmbed(id) removes it for good (call while the same window is current, like the widget itself).
--------------------------------------------------------------------------------------------------------------------
local EmbedFlags = { local EmbedFlags = {
None = 0, None = 0,
NoClip = 1, -- don't clip to the window's content area (still moves/hides/stacks with the window) NoClip = 1, -- Don't clip to the window's content area (still moves/hides/stacks with the window)
NoBackground = 2, -- no FrameBg behind the Frame NoBackground = 2, -- No FrameBg behind the Frame
NoBorder = 4, -- no style border NoBorder = 4, -- No style border
NoPadding = 8, -- no FramePadding inside the Frame (UIPadding) NoPadding = 8, -- No FramePadding inside the Frame (UIPadding)
NoRounding = 16, -- no FrameRounding corners (UICorner) NoRounding = 16, -- No FrameRounding corners (UICorner)
} }
G.ImGuiEmbedFlags = EmbedFlags G.ImGuiEmbedFlags = EmbedFlags
local embeds = {} -- [ImGuiID] = { Frame, Padding, Corner } local Embeds = {}
function ImGui.Embed(id, size, flags, init) local function HasFlag(Flags, Flag)
flags = flags or EmbedFlags.None return bit32.band(Flags, Flag) ~= 0
local window = ImGui.GetCurrentWindow()
local gid = window:GetID(id)
local e = embeds[gid]
if not e then
local frame = Instance.new("Frame")
frame.Name = tostring(id)
frame.BackgroundTransparency = 1
frame.BorderSizePixel = 0
frame.Visible = false
e = { Frame = frame, Padding = Instance.new("UIPadding"), Corner = Instance.new("UICorner") }
e.Padding.Parent = frame
e.Corner.Parent = frame
embeds[gid] = e
if init then init(frame) end
end
if window.SkipItems then return e.Frame end
local style = ImGui.GetStyle()
local sz = ImGui.CalcItemSize(size or G.ImVec2(0, 0), ImGui.CalcItemWidth(), ImGui.GetFrameHeight() * 4)
local p = window.DC.CursorPos
local bb = G.ImRect(p.x, p.y, p.x + sz.x, p.y + sz.y)
ImGui.ItemSize(sz)
if not ImGui.ItemAdd(bb, gid) then return e.Frame end -- fully clipped: not submitted -> hidden this frame
local rounding = bit32.band(flags, EmbedFlags.NoRounding) == 0 and style.FrameRounding or 0
if bit32.band(flags, EmbedFlags.NoBackground) == 0 then
ImGui.RenderFrame(bb.Min, bb.Max, ImGui.GetColorU32(G.ImGuiCol.FrameBg), bit32.band(flags, EmbedFlags.NoBorder) == 0, rounding)
elseif bit32.band(flags, EmbedFlags.NoBorder) == 0 then
ImGui.RenderFrameBorder(bb.Min, bb.Max, rounding)
end
-- style -> Instances (only written when changed)
local scale = ImGui_ImplRoblox.GetRenderScale()
local px, py = 0, 0
if bit32.band(flags, EmbedFlags.NoPadding) == 0 then px, py = style.FramePadding.x / scale, style.FramePadding.y / scale end
if e.PX ~= px or e.PY ~= py then
e.PX, e.PY = px, py
e.Padding.PaddingLeft, e.Padding.PaddingRight = UDim.new(0, px), UDim.new(0, px)
e.Padding.PaddingTop, e.Padding.PaddingBottom = UDim.new(0, py), UDim.new(0, py)
end
if e.R ~= rounding then e.R = rounding; e.Corner.CornerRadius = UDim.new(0, rounding / scale) end
ImGui_ImplRoblox.SetOverlay(e.Frame, bb.Min.x, bb.Min.y, sz.x, sz.y, bit32.band(flags, EmbedFlags.NoClip) == 0)
return e.Frame
end end
function ImGui.DestroyEmbed(id) local function CreateEmbed(Id)
local gid = ImGui.GetCurrentWindow():GetID(id) local Frame = Instance.new("Frame")
local e = embeds[gid] Frame.Name = tostring(Id)
if e then e.Frame:Destroy(); embeds[gid] = nil end Frame.BackgroundTransparency = 1
Frame.BorderSizePixel = 0
Frame.Visible = false
local Padding = Instance.new("UIPadding")
Padding.Parent = Frame
local Corner = Instance.new("UICorner")
Corner.Parent = Frame
return { Frame = Frame, Padding = Padding, Corner = Corner }
end end
-- Example: Decal Viewer. Loads a decal/image asset's pixels into an imgui texture and shows it in the demo's function ImGui.Embed(Id, Size, Flags, Init)
-- image viewer (zoom with the wheel, drag to pan, toggleable pixel grid). Flags = Flags or EmbedFlags.None
local AssetService = game:GetService("AssetService")
local MarketplaceService = game:GetService("MarketplaceService")
local decal = { input = ImGui.NewTextBuffer("", 32), status = "Enter a decal or image id", tex = nil, w = 0, h = 0, loading = false,
viewer = ExampleImageViewerData(), show = true }
-- A Decal asset wraps an Image asset with a different id; the image is almost always uploaded just before it local Window = ImGui.GetCurrentWindow()
-- (same creator, id slightly lower). ponytail: heuristic scan of 10 ids, InsertService:LoadAsset when owned is exact. local GuiId = Window:GetID(Id)
local function ResolveImageId(id)
local ok, info = pcall(MarketplaceService.GetProductInfo, MarketplaceService, id) local Embed = Embeds[GuiId]
if not ok then return nil, "GetProductInfo failed: " .. tostring(info) end if not Embed then
if info.AssetTypeId == 1 then return id end -- already an Image Embed = CreateEmbed(Id)
if info.AssetTypeId ~= 13 then return nil, "asset is not a decal or image" end Embeds[GuiId] = Embed
for cand = id - 1, id - 10, -1 do
local ok2, ci = pcall(MarketplaceService.GetProductInfo, MarketplaceService, cand) if Init then
if ok2 and ci.AssetTypeId == 1 and ci.Creator and info.Creator and ci.Creator.Id == info.Creator.Id then return cand end Init(Embed.Frame)
end end
return nil, "couldn't find the decal's image id (try the image id directly)" end
if Window.SkipItems then
return Embed.Frame
end
local EmbedStyle = ImGui.GetStyle()
local ItemSize = ImGui.CalcItemSize(
Size or ImVec2(0, 0),
ImGui.CalcItemWidth(),
ImGui.GetFrameHeight() * 4
)
local CursorPos = Window.DC.CursorPos
local BoundingBox = ImRect(CursorPos.x, CursorPos.y, CursorPos.x + ItemSize.x, CursorPos.y + ItemSize.y)
ImGui.ItemSize(ItemSize)
if not ImGui.ItemAdd(BoundingBox, GuiId) then
return Embed.Frame
end
local Rounding = HasFlag(Flags, EmbedFlags.NoRounding) and 0 or EmbedStyle.FrameRounding
local ShowBorder = not HasFlag(Flags, EmbedFlags.NoBorder)
if not HasFlag(Flags, EmbedFlags.NoBackground) then
ImGui.RenderFrame(
BoundingBox.Min,
BoundingBox.Max,
ImGui.GetColorU32(ImGuiCol.FrameBg),
ShowBorder,
Rounding
)
elseif ShowBorder then
ImGui.RenderFrameBorder(BoundingBox.Min, BoundingBox.Max, Rounding)
end
local Scale = ImGui_ImplRoblox.GetRenderScale()
local PaddingX, PaddingY = 0, 0
if not HasFlag(Flags, EmbedFlags.NoPadding) then
PaddingX = EmbedStyle.FramePadding.x / Scale
PaddingY = EmbedStyle.FramePadding.y / Scale
end
if Embed.PaddingX ~= PaddingX or Embed.PaddingY ~= PaddingY then
Embed.PaddingX, Embed.PaddingY = PaddingX, PaddingY
local Padding = Embed.Padding
Padding.PaddingLeft = UDim.new(0, PaddingX)
Padding.PaddingRight = UDim.new(0, PaddingX)
Padding.PaddingTop = UDim.new(0, PaddingY)
Padding.PaddingBottom = UDim.new(0, PaddingY)
end
if Embed.Rounding ~= Rounding then
Embed.Rounding = Rounding
Embed.Corner.CornerRadius = UDim.new(0, Rounding / Scale)
end
ImGui_ImplRoblox.SetOverlay(
Embed.Frame,
BoundingBox.Min.x,
BoundingBox.Min.y,
ItemSize.x,
ItemSize.y,
not HasFlag(Flags, EmbedFlags.NoClip)
)
return Embed.Frame
end end
local function LoadDecal(id) function ImGui.DestroyEmbed(Id)
decal.loading = true local GuiId = ImGui.GetCurrentWindow():GetID(Id)
decal.status = "Loading " .. id .. "..." local Embed = Embeds[GuiId]
task.spawn(function()
local image_id, err = ResolveImageId(id) if Embed then
decal.thumb = nil Embed.Frame:Destroy()
if image_id then Embeds[GuiId] = nil
-- 1) the image itself (needs permission), 2) its 420x420 thumbnail (may be allowed when 1 isn't) end
local ok, img = pcall(AssetService.CreateEditableImageAsync, AssetService, Content.fromUri("rbxassetid://" .. image_id)) end
if not ok then
local ok2, img2 = pcall(AssetService.CreateEditableImageAsync, AssetService, Content.fromUri("rbxthumb://type=Asset&id=" .. id .. "&w=420&h=420"))
if ok2 then ok, img = true, img2 end local Decal = {
end Show = true,
if ok then Input = ImGui.NewTextBuffer("", 32),
local size = img.Size Status = "Enter a decal or image id",
local pixels = img:ReadPixelsBuffer(Vector2.zero, size) Loading = false,
img:Destroy() Texture = nil,
if decal.tex then ImGui_ImplRoblox.DestroyTexture(decal.tex) end Width = 0,
decal.thumb = nil Height = 0,
decal.tex, decal.w, decal.h = ImGui_ImplRoblox.CreateTexture(pixels, size.X, size.Y), size.X, size.Y Viewer = ExampleImageViewerData(),
decal.viewer.ViewReset = true }
decal.status = string.format("Image %d: %dx%d", image_id, size.X, size.Y)
else local function LoadDecal(Id)
-- 3) no pixel access: Roblox still displays any asset, so show its thumbnail in an ImageLabel (no grid) Decal.Loading = true
decal.thumb = "rbxthumb://type=Asset&id=" .. id .. "&w=420&h=420" Decal.Status = "Loading " .. Id .. "..."
decal.status = "No pixel access (asset not owned by this experience's creator): display only, no zoom/grid"
end task.spawn(function()
else local Uri = string.format("rbxthumb://type=Asset&id=%d&w=%d&h=%d", Id, ThumbnailSize, ThumbnailSize)
decal.thumb = "rbxthumb://type=Asset&id=" .. id .. "&w=420&h=420" local Success, Result = pcall(AssetService.CreateEditableImageAsync, AssetService, Content.fromUri(Uri))
decal.status = (err or "") .. " - showing thumbnail"
end if Success then
decal.loading = false local Size = Result.Size
end) local Pixels = Result:ReadPixelsBuffer(Vector2.zero, Size)
Result:Destroy()
if Decal.Texture then
ImGui_ImplRoblox.DestroyTexture(Decal.Texture)
end
Decal.Texture = ImGui_ImplRoblox.CreateTexture(Pixels, Size.X, Size.Y)
Decal.Width, Decal.Height = Size.X, Size.Y
Decal.Viewer.ViewReset = true
Decal.Status = string.format("Thumbnail %d: %dx%d", Id, Size.X, Size.Y)
else
Decal.Status = "Failed to load thumbnail: " .. tostring(Result)
end
Decal.Loading = false
end)
end end
local function ShowDecalViewer() local function ShowDecalViewer()
if not decal.show then return end if not Decal.Show then
decal.show = ImGui.Begin("Decal Viewer", decal.show) return
ImGui.SetNextItemWidth(ImGui.GetFontSize() * 12) end
local enter = ImGui.InputTextWithHint("##id", "decal id", decal.input, 32, G.ImGuiInputTextFlags.CharsDecimal + G.ImGuiInputTextFlags.EnterReturnsTrue)
ImGui.SameLine() Decal.Show = ImGui.Begin("Decal Viewer", Decal.Show)
ImGui.BeginDisabled(decal.loading)
if (ImGui.Button("Load") or enter) and not decal.loading then ImGui.SetNextItemWidth(ImGui.GetFontSize() * 12)
local id = tonumber(ImGui.TextBufferToString(decal.input)) local EnterPressed = ImGui.InputTextWithHint(
if id then LoadDecal(id) else decal.status = "Not a number" end "##id",
end "decal id",
ImGui.EndDisabled() Decal.Input,
ImGui.SameLine(); ImGui.TextDisabled(decal.status) 32,
if decal.thumb then ImGuiInputTextFlags.CharsDecimal + ImGuiInputTextFlags.EnterReturnsTrue
local avail = ImGui.GetContentRegionAvail() )
local frame = ImGui.Embed("decal_thumb", G.ImVec2(-1, math.max(avail.y, 50)), G.ImGuiEmbedFlags.None, function(f)
local img = Instance.new("ImageLabel") ImGui.SameLine()
img.Name = "Image" ImGui.BeginDisabled(Decal.Loading)
img.Size = UDim2.fromScale(1, 1) if (ImGui.Button("Load") or EnterPressed) and not Decal.Loading then
img.BackgroundTransparency = 1 local Id = tonumber(ImGui.TextBufferToString(Decal.Input))
img.ScaleType = Enum.ScaleType.Fit if Id and Id == math.floor(Id) then
img.Parent = f LoadDecal(Id)
end) else
if frame.Image.Image ~= decal.thumb then frame.Image.Image = decal.thumb end Decal.Status = "Not a valid id"
elseif decal.tex then end
ExampleImageViewer_DrawOptions(decal.viewer) end
local avail = ImGui.GetContentRegionAvail() ImGui.EndDisabled()
ExampleImageViewer_DrawCanvas(decal.viewer, G.ImVec2(math.max(avail.x, 50), math.max(avail.y, 50)), decal.tex, decal.w, decal.h)
end ImGui.SameLine()
ImGui.End() ImGui.TextDisabled(Decal.Status)
if Decal.Texture then
ExampleImageViewer_DrawOptions(Decal.Viewer)
local Available = ImGui.GetContentRegionAvail()
ExampleImageViewer_DrawCanvas(
Decal.Viewer,
ImVec2(math.max(Available.x, 50), math.max(Available.y, 50)),
Decal.Texture,
Decal.Width,
Decal.Height
)
end
ImGui.End()
end end
-- Example: a ViewportFrame with a spinning part, edited live from imgui widgets local function ShowHelloWindow()
local spin_speed = 1.0 ImGui.Begin("Hello, world!")
local part_color = ImVec4(0.80, 0.65, 0.97, 1.0)
local spin_angle = 0.0 ImGui.Text("This is some useful text.")
local show_viewport_window = true
local _
_, State.ShowDemoWindow = ImGui.Checkbox("Demo Window", State.ShowDemoWindow)
_, State.ShowAnotherWindow = ImGui.Checkbox("Another Window", State.ShowAnotherWindow)
State.Float = ImGui.SliderFloat("float", State.Float, 0.0, 1.0)
ImGui.ColorEdit3("clear color", State.ClearColor)
if ImGui.Button("Button") then
State.Counter += 1
end
ImGui.SameLine()
ImGui.Text("counter = %d", State.Counter)
ImGui.Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0 / IO.Framerate, IO.Framerate)
ImGui.End()
end
local function ShowAnotherWindow()
if not State.ShowAnotherWindow then
return
end
State.ShowAnotherWindow = ImGui.Begin("Another Window", State.ShowAnotherWindow)
ImGui.Text("Hello from another window!")
if ImGui.Button("Close Me") then
State.ShowAnotherWindow = false
end
ImGui.End()
end
local function CreateSpinnerViewport(Container)
local Viewport = Instance.new("ViewportFrame")
Viewport.Size = UDim2.fromScale(1, 1)
Viewport.BackgroundTransparency = 1
Viewport.Parent = Container
local Camera = Instance.new("Camera")
Camera.CFrame = CFrame.lookAt(Vector3.new(0, 2, 6), Vector3.zero)
Camera.Parent = Viewport
Viewport.CurrentCamera = Camera
local Part = Instance.new("Part")
Part.Name = "Spinner"
Part.Size = Vector3.new(2, 2, 2)
Part.Parent = Viewport
end
local function ShowViewportWindow()
if not Spinner.Show then
return
end
Spinner.Show = ImGui.Begin("Viewport", Spinner.Show)
Spinner.Speed = ImGui.SliderFloat("speed", Spinner.Speed, 0.0, 5.0)
ImGui.ColorEdit3("color", Spinner.Color)
local Frame = ImGui.Embed("viewport", ImVec2(-1, 220), EmbedFlags.None, CreateSpinnerViewport)
Spinner.Angle += Spinner.Speed * IO.DeltaTime
local Part = Frame.ViewportFrame.Spinner
Part.CFrame = CFrame.Angles(0.4, Spinner.Angle, 0)
Part.Color = Color3.new(Spinner.Color.x, Spinner.Color.y, Spinner.Color.z)
ImGui.End()
end
RunService.Stepped:Connect(function() RunService.Stepped:Connect(function()
P("ImGui frame") ProfileBegin("ImGui frame")
ImGui_ImplRoblox.NewFrame() ImGui_ImplRoblox.NewFrame()
P("ImGui.NewFrame"); ImGui.NewFrame(); PE()
P("ImGui UI")
-- Dockspace covering the whole screen; the centre stays transparent so the game shows through ProfileBegin("ImGui.NewFrame")
ImGui.DockSpaceOverViewport(0, nil, G.ImGuiDockNodeFlags.PassthruCentralNode) ImGui.NewFrame()
ProfileEnd()
if show_demo_window then ProfileBegin("ImGui UI")
ImGui.ShowDemoWindow() ImGui.DockSpaceOverViewport(0, nil, ImGuiDockNodeFlags.PassthruCentralNode)
end
do if State.ShowDemoWindow then
ImGui.Begin("Hello, world!") -- For reference: https://github.com/4DBug/imgui-luau/blob/master/imgui_demo.lua
ImGui.ShowDemoWindow()
end
ImGui.Text("This is some useful text.") ShowHelloWindow()
local _ ShowAnotherWindow()
_, show_demo_window = ImGui.Checkbox("Demo Window", show_demo_window) ShowDecalViewer()
_, show_another_window = ImGui.Checkbox("Another Window", show_another_window) ShowViewportWindow()
ProfileEnd()
f = ImGui.SliderFloat("float", f, 0.0, 1.0) ProfileBegin("ImGui.Render")
ImGui.ColorEdit3("clear color", clear_color) ImGui.EndFrame()
ImGui.Render()
ProfileEnd()
if ImGui.Button("Button") then ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
counter = counter + 1 ProfileEnd()
end
ImGui.SameLine()
ImGui.Text("counter = %d", counter)
ImGui.Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0 / io.Framerate, io.Framerate)
ImGui.End()
end
if show_another_window then
show_another_window = ImGui.Begin("Another Window", show_another_window)
ImGui.Text("Hello from another window!")
if ImGui.Button("Close Me") then
show_another_window = false
end
ImGui.End()
end
PE()
ShowDecalViewer()
if show_viewport_window then
show_viewport_window = ImGui.Begin("Viewport", show_viewport_window)
spin_speed = ImGui.SliderFloat("speed", spin_speed, 0.0, 5.0)
ImGui.ColorEdit3("color", part_color)
local frame = ImGui.Embed("viewport", G.ImVec2(-1, 220), G.ImGuiEmbedFlags.None, function(f)
-- runs once
local vpf = Instance.new("ViewportFrame")
vpf.Size = UDim2.fromScale(1, 1)
vpf.BackgroundTransparency = 1
vpf.Parent = f
local cam = Instance.new("Camera")
cam.CFrame = CFrame.lookAt(Vector3.new(0, 2, 6), Vector3.new(0, 0, 0))
cam.Parent = vpf
vpf.CurrentCamera = cam
local part = Instance.new("Part")
part.Name = "Spinner"
part.Size = Vector3.new(2, 2, 2)
part.Parent = vpf
end)
-- every frame: edit what was created once
spin_angle += spin_speed * io.DeltaTime
local part = frame.ViewportFrame.Spinner
part.CFrame = CFrame.Angles(0.4, spin_angle, 0)
part.Color = Color3.new(part_color.x, part_color.y, part_color.z)
ImGui.Text("Frames created: 1 (same Frame every frame)")
ImGui.End()
end
P("ImGui.Render"); ImGui.EndFrame(); ImGui.Render(); PE()
ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
PE()
end) end)
+1 -1
View File
@@ -3,7 +3,7 @@
"tree": { "tree": {
"$className": "DataModel", "$className": "DataModel",
"ReplicatedStorage": { "ReplicatedStorage": {
"ImGui": { "$path": "../../build/ImGui.luau" }, "ImGui": { "$path": "../../build/ImGuiTyped.luau", "Impl": { "$path": "../../build/ImGui.luau" } },
"SharedApp": { "$path": "app.lua" } "SharedApp": { "$path": "app.lua" }
}, },
"StarterPlayer": { "StarterPlayer": {
+3364 -548
View File
File diff suppressed because it is too large Load Diff
+1314 -181
View File
File diff suppressed because it is too large Load Diff
+633 -101
View File
File diff suppressed because it is too large Load Diff
+1169 -701
View File
File diff suppressed because it is too large Load Diff
+889 -615
View File
File diff suppressed because it is too large Load Diff
+732 -84
View File
File diff suppressed because it is too large Load Diff
+1164 -309
View File
File diff suppressed because it is too large Load Diff
+229 -11
View File
@@ -4,6 +4,63 @@
-- ALL TABLES IN THIS FILE ARE 1-BASED! -- ALL TABLES IN THIS FILE ARE 1-BASED!
-- Avoids table creation when possible -- Avoids table creation when possible
-- [DEAR IMGUI]
-- This is a slightly modified version of stb_rect_pack.h 1.01.
-- Grep for [DEAR IMGUI] to find the changes.
--
-- stb_rect_pack.h - v1.01 - public domain - rectangle packing
-- Sean Barrett 2014
--
-- Useful for e.g. packing rectangular textures into an atlas.
-- Does not do rotation.
--
-- Not necessarily the awesomest packing method, but better than
-- the totally naive one in stb_truetype (which is primarily what
-- this is meant to replace).
--
-- Has only had a few tests run, may have issues.
--
-- More docs to come.
--
-- Uses table.sort and assert (STBRP_SORT and STBRP_ASSERT below).
--
-- This library currently uses the Skyline Bottom-Left algorithm.
--
-- Please note: better rectangle packers are welcome! Please
-- implement them to the same API, but with a different init
-- function.
--
-- Credits
--
-- Library
-- Sean Barrett
-- Minor features
-- Martins Mozeiko
-- github:IntellectualKitty
--
-- Bugfixes / warning fixes
-- Jeremy Jaussaud
-- Fabian Giesen
--
-- Version history:
--
-- 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
-- 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
-- 0.99 (2019-02-07) warning fixes
-- 0.11 (2017-03-03) return packing success/fail result
-- 0.10 (2016-10-25) remove cast-away-const to avoid warnings
-- 0.09 (2016-08-27) fix compiler warnings
-- 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
-- 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
-- 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
-- 0.05: added STBRP_ASSERT to allow replacing assert
-- 0.04: fixed minor bug in STBRP_LARGE_RECTS support
-- 0.01: initial release
--
-- LICENSE
--
-- See end of file for license information.
--- The `prev_link` can be stbrp_context or stbrp_node --- The `prev_link` can be stbrp_context or stbrp_node
--- @diagnostic disable --- @diagnostic disable
@@ -13,7 +70,13 @@ local STBRP_SORT = table.sort
local STBRP_ASSERT = assert local STBRP_ASSERT = assert
local STBRP__NOTUSED = function(_) end local STBRP__NOTUSED = function(_) end
-------------------------------------------------------------------------------
--
-- INCLUDE SECTION
--
local STBRP__MAXVAL = 0x7fffffff local STBRP__MAXVAL = 0x7fffffff
-- Mostly for internal use, but this is the maximum supported coordinate value.
--- @enum STBRP_HEURISTIC_Skyline --- @enum STBRP_HEURISTIC_Skyline
local STBRP_HEURISTIC_Skyline = { local STBRP_HEURISTIC_Skyline = {
@@ -44,12 +107,17 @@ local STBRP__PREV_LINK_TYPE = {
--- @nodiscard --- @nodiscard
local function stbrp_rect() local function stbrp_rect()
return { return {
-- reserved for your use:
id = 0, id = 0,
-- input:
w = 0, w = 0,
h = 0, h = 0,
-- output:
x = 0, x = 0,
y = 0, y = 0,
was_packed = 0 was_packed = 0 -- non-zero if valid packing
} }
end end
@@ -92,7 +160,7 @@ local function stbrp_context()
active_head = nil, active_head = nil,
free_head = nil, free_head = nil,
extra = {stbrp_node(), stbrp_node()} extra = {stbrp_node(), stbrp_node()} -- we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
} }
end end
@@ -113,13 +181,14 @@ local function stbrp__findresult()
} }
end end
-------------------------- -------------------------------------------------------------------------------
-------------------------- --
--- -- IMPLEMENTATION SECTION
--- IMPLEMENTATION SECTION --
---
---
-- Optionally select which packing heuristic the library should use. Different
-- heuristics will produce better/worse results for different data sets.
-- If you call init again, this will be reset to the default.
--- @param context stbrp_context --- @param context stbrp_context
--- @param heuristic int --- @param heuristic int
local function stbrp_setup_heuristic(context, heuristic) local function stbrp_setup_heuristic(context, heuristic)
@@ -131,16 +200,49 @@ local function stbrp_setup_heuristic(context, heuristic)
end end
end end
-- Optionally call this function after init but before doing any packing to
-- change the handling of the out-of-temp-memory scenario, described above.
-- If you call init again, this will be reset to the default (false).
--- @param context stbrp_context --- @param context stbrp_context
--- @param allow_out_of_mem boolean --- @param allow_out_of_mem boolean
local function stbrp_setup_allow_out_of_mem(context, allow_out_of_mem) local function stbrp_setup_allow_out_of_mem(context, allow_out_of_mem)
if allow_out_of_mem then if allow_out_of_mem then
-- if it's ok to run out of memory, then don't bother aligning them;
-- this gives better packing, but may fail due to OOM (even though
-- the rectangles easily fit). @TODO a smarter approach would be to only
-- quantize once we've hit OOM, then we could get rid of this parameter.
context.align = 1 context.align = 1
else else
-- if it's not ok to run out of memory, then quantize the widths
-- so that num_nodes is always enough nodes.
--
-- I.e. num_nodes * align >= width
-- align >= width / num_nodes
-- align = ceil(width/num_nodes)
context.align = (context.width + context.num_nodes - 1) / context.num_nodes context.align = (context.width + context.num_nodes - 1) / context.num_nodes
end end
end end
-- Initialize a rectangle packer to:
-- pack a rectangle that is 'width' by 'height' in dimensions
-- using temporary storage provided by the array 'nodes', which is 'num_nodes' long
--
-- You must call this function every time you start packing into a new target.
--
-- There is no "shutdown" function. The 'nodes' memory must stay valid for
-- the following stbrp_pack_rects() call (or calls), but can be freed after
-- the call (or calls) finish.
--
-- Note: to guarantee best results, either:
-- 1. make sure 'num_nodes' >= 'width'
-- or 2. call stbrp_setup_allow_out_of_mem() defined above with 'allow_out_of_mem = true'
--
-- If you don't do either of the above things, widths will be quantized to multiples
-- of small integers to guarantee the algorithm doesn't run out of temporary storage.
--
-- If you do #2, then the non-quantized algorithm will be used, but the algorithm
-- may run out of temporary storage and be unable to pack some rectangles.
--- @param context stbrp_context --- @param context stbrp_context
--- @param width int --- @param width int
--- @param height int --- @param height int
@@ -160,6 +262,7 @@ function stbrp_init_target(context, width, height, nodes, num_nodes)
context.num_nodes = num_nodes context.num_nodes = num_nodes
stbrp_setup_allow_out_of_mem(context, false) stbrp_setup_allow_out_of_mem(context, false)
-- node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
context.extra[1].x = 0 context.extra[1].x = 0
context.extra[1].y = 0 context.extra[1].y = 0
context.extra[1].next = context.extra[2] context.extra[1].next = context.extra[2]
@@ -168,6 +271,8 @@ function stbrp_init_target(context, width, height, nodes, num_nodes)
context.extra[2].next = nil context.extra[2].next = nil
end end
-- find minimum y position if it starts at x1
-- Returns (min_y, waste_area)
--- @param c stbrp_context --- @param c stbrp_context
--- @param first stbrp_node --- @param first stbrp_node
--- @param x0 int --- @param x0 int
@@ -181,7 +286,7 @@ local function stbrp__skyline_find_min_y(c, first, x0, width)
STBRP_ASSERT(first.x <= x0) STBRP_ASSERT(first.x <= x0)
STBRP_ASSERT(node.next.x > x0) STBRP_ASSERT(node.next.x > x0) -- we ended up handling this in the caller for efficiency
STBRP_ASSERT(node.x <= x0) STBRP_ASSERT(node.x <= x0)
@@ -190,14 +295,19 @@ local function stbrp__skyline_find_min_y(c, first, x0, width)
local visited_width = 0 local visited_width = 0
while node.x < x1 do while node.x < x1 do
if node.y > min_y then if node.y > min_y then
-- raise min_y higher.
-- we've accounted for all waste up to min_y,
-- but we'll now add more waste for everything we've visited
waste_area = waste_area + visited_width * (node.y - min_y) waste_area = waste_area + visited_width * (node.y - min_y)
min_y = node.y min_y = node.y
-- the first time through, visited_width might be reduced
if node.x < x0 then if node.x < x0 then
visited_width = visited_width + node.next.x - x0 visited_width = visited_width + node.next.x - x0
else else
visited_width = visited_width + node.next.x - node.x visited_width = visited_width + node.next.x - node.x
end end
else else
-- add waste area
local under_width = node.next.x - node.x local under_width = node.next.x - node.x
if under_width + visited_width > width then if under_width + visited_width > width then
under_width = width - visited_width under_width = width - visited_width
@@ -223,10 +333,12 @@ local function stbrp__skyline_find_best_pos(c, width, height)
local best_prev_link = nil local best_prev_link = nil
local best_prev_link_type = nil local best_prev_link_type = nil
-- align to multiple of c.align
width = width + c.align - 1 width = width + c.align - 1
width = width - width % c.align width = width - width % c.align
STBRP_ASSERT(width % c.align == 0) STBRP_ASSERT(width % c.align == 0)
-- if it can't possibly fit, bail immediately
if width > c.width or height > c.height then if width > c.width or height > c.height then
fr.prev_link = nil fr.prev_link = nil
fr.prev_link_type = nil fr.prev_link_type = nil
@@ -242,7 +354,8 @@ local function stbrp__skyline_find_best_pos(c, width, height)
while (node.x + width <= c.width) do while (node.x + width <= c.width) do
local y, waste = stbrp__skyline_find_min_y(c, node, node.x, width) local y, waste = stbrp__skyline_find_min_y(c, node, node.x, width)
if c.heuristic == STBRP_HEURISTIC_Skyline.BL_sortHeight then if c.heuristic == STBRP_HEURISTIC_Skyline.BL_sortHeight then -- actually just want to test BL
-- bottom left
if y < best_y then if y < best_y then
best_y = y best_y = y
best_prev_link = prev_link best_prev_link = prev_link
@@ -250,7 +363,9 @@ local function stbrp__skyline_find_best_pos(c, width, height)
best_x = node.x best_x = node.x
end end
else else
-- best-fit
if y + height <= c.height then if y + height <= c.height then
-- can only use it if it first vertically
if y < best_y or (y == best_y and waste < best_waste) then if y < best_y or (y == best_y and waste < best_waste) then
best_y = y best_y = y
best_waste = waste best_waste = waste
@@ -266,12 +381,30 @@ local function stbrp__skyline_find_best_pos(c, width, height)
node = node.next node = node.next
end end
-- if doing best-fit (BF), we also have to try aligning right edge to each node position
--
-- e.g, if fitting
--
-- ____________________
-- |____________________|
--
-- into
--
-- | |
-- | ____________|
-- |____________|
--
-- then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
--
-- This makes BF take about 2x the time
if c.heuristic == STBRP_HEURISTIC_Skyline.BF_sortHeight then if c.heuristic == STBRP_HEURISTIC_Skyline.BF_sortHeight then
local tail = c.active_head local tail = c.active_head
local node_scan = c.active_head local node_scan = c.active_head
local prev_link_scan = c local prev_link_scan = c
local prev_link_scan_type = STBRP__PREV_LINK_TYPE.ACTIVE_HEAD local prev_link_scan_type = STBRP__PREV_LINK_TYPE.ACTIVE_HEAD
-- find first node that's admissible
while tail and tail.x < width do while tail and tail.x < width do
tail = tail.next tail = tail.next
end end
@@ -280,6 +413,7 @@ local function stbrp__skyline_find_best_pos(c, width, height)
local xpos = tail.x - width local xpos = tail.x - width
STBRP_ASSERT(xpos >= 0) STBRP_ASSERT(xpos >= 0)
-- find the left position that matches this
while node_scan.next and node_scan.next.x <= xpos do while node_scan.next and node_scan.next.x <= xpos do
prev_link_scan = node_scan prev_link_scan = node_scan
prev_link_scan_type = STBRP__PREV_LINK_TYPE.NEXT prev_link_scan_type = STBRP__PREV_LINK_TYPE.NEXT
@@ -295,6 +429,7 @@ local function stbrp__skyline_find_best_pos(c, width, height)
if y <= best_y then if y <= best_y then
if y < best_y or waste < best_waste or (waste == best_waste and xpos < best_x) then if y < best_y or waste < best_waste or (waste == best_waste and xpos < best_x) then
best_x = xpos best_x = xpos
-- STBRP_ASSERT(y <= best_y); [DEAR IMGUI]
best_y = y best_y = y
best_waste = waste best_waste = waste
best_prev_link = prev_link_scan best_prev_link = prev_link_scan
@@ -320,19 +455,29 @@ end
--- @param height int --- @param height int
--- @return stbrp__findresult --- @return stbrp__findresult
local function stbrp__skyline_pack_rectangle(context, width, height) local function stbrp__skyline_pack_rectangle(context, width, height)
-- find best position according to heuristic
local res = stbrp__skyline_find_best_pos(context, width, height) local res = stbrp__skyline_find_best_pos(context, width, height)
-- bail if:
-- 1. it failed
-- 2. the best node doesn't fit (we don't always check this)
-- 3. we're out of memory
if res.prev_link == nil or res.y + height > context.height or context.free_head == nil then if res.prev_link == nil or res.y + height > context.height or context.free_head == nil then
res.prev_link = nil res.prev_link = nil
res.prev_link_type = nil res.prev_link_type = nil
return res return res
end end
-- on success, create new node
local node = context.free_head local node = context.free_head
node.x = res.x node.x = res.x
node.y = res.y + height node.y = res.y + height
context.free_head = node.next context.free_head = node.next
-- insert the new node into the right starting point, and
-- let 'cur' point to the remaining nodes needing to be
-- stitched back in
local cur local cur
if res.prev_link_type == STBRP__PREV_LINK_TYPE.ACTIVE_HEAD then if res.prev_link_type == STBRP__PREV_LINK_TYPE.ACTIVE_HEAD then
cur = res.prev_link.active_head cur = res.prev_link.active_head
@@ -341,6 +486,7 @@ local function stbrp__skyline_pack_rectangle(context, width, height)
end end
if cur.x < res.x then if cur.x < res.x then
-- preserve the existing one, so start testing with the next one
local next = cur.next local next = cur.next
cur.next = node cur.next = node
cur = next cur = next
@@ -352,13 +498,17 @@ local function stbrp__skyline_pack_rectangle(context, width, height)
end end
end end
-- from here, traverse cur and free the nodes, until we get to one
-- that shouldn't be freed
while cur.next and cur.next.x <= res.x + width do while cur.next and cur.next.x <= res.x + width do
local next = cur.next local next = cur.next
-- move the current node to the free list
cur.next = context.free_head cur.next = context.free_head
context.free_head = cur context.free_head = cur
cur = next cur = next
end end
-- stitch the list back in
node.next = cur node.next = cur
if cur.x < res.x + width then if cur.x < res.x + width then
@@ -390,6 +540,29 @@ local function rect_original_order(a, b)
return a.was_packed < b.was_packed return a.was_packed < b.was_packed
end end
-- Assign packed locations to rectangles. The rectangles are of type
-- 'stbrp_rect' defined above, stored in the array 'rects', and there
-- are 'num_rects' many of them.
--
-- Rectangles which are successfully packed have the 'was_packed' flag
-- set to a non-zero value and 'x' and 'y' store the minimum location
-- on each axis (i.e. bottom-left in cartesian coordinates, top-left
-- if you imagine y increasing downwards). Rectangles which do not fit
-- have the 'was_packed' flag set to 0.
--
-- You should not try to access the 'rects' array from another thread
-- while this function is running, as the function temporarily reorders
-- the array while it executes.
--
-- To pack into another rectangle, you need to call stbrp_init_target
-- again. To continue packing into the same rectangle, you can call
-- this function again. Calling this multiple times with multiple rect
-- arrays will probably produce worse packing results than calling it
-- a single time with the full rectangle array, but the option is
-- available.
--
-- The function returns 1 if all of the rectangles were successfully
-- packed and 0 otherwise.
--- @param context stbrp_context --- @param context stbrp_context
--- @param rects stbrp_rect[] --- @param rects stbrp_rect[]
--- @param num_rects int --- @param num_rects int
@@ -397,15 +570,17 @@ end
local function stbrp_pack_rects(context, rects, num_rects) local function stbrp_pack_rects(context, rects, num_rects)
local all_rects_packed = 1 local all_rects_packed = 1
-- we use the 'was_packed' field internally to allow sorting/unsorting
for i = 1, num_rects do for i = 1, num_rects do
rects[i].was_packed = (i - 1) rects[i].was_packed = (i - 1)
end end
-- sort according to heuristic
STBRP_SORT(rects, rect_height_compare) STBRP_SORT(rects, rect_height_compare)
for i = 1, num_rects do for i = 1, num_rects do
if rects[i].w == 0 or rects[i].h == 0 then if rects[i].w == 0 or rects[i].h == 0 then
rects[i].x = 0 rects[i].x = 0 -- empty rect needs no space
rects[i].y = 0 rects[i].y = 0
else else
local fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h) local fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h)
@@ -419,8 +594,10 @@ local function stbrp_pack_rects(context, rects, num_rects)
end end
end end
-- unsort
STBRP_SORT(rects, rect_original_order) STBRP_SORT(rects, rect_original_order)
-- set was_packed flags and all_rects_packed status
for i = 1, num_rects do for i = 1, num_rects do
rects[i].was_packed = (rects[i].x == STBRP__MAXVAL and rects[i].y == STBRP__MAXVAL) and 0 or 1 rects[i].was_packed = (rects[i].x == STBRP__MAXVAL and rects[i].y == STBRP__MAXVAL) and 0 or 1
if rects[i].was_packed == 0 then if rects[i].was_packed == 0 then
@@ -428,6 +605,7 @@ local function stbrp_pack_rects(context, rects, num_rects)
end end
end end
-- return the all_rects_packed status
return all_rects_packed return all_rects_packed
end end
@@ -441,3 +619,43 @@ return {
setup_allow_out_of_mem = stbrp_setup_allow_out_of_mem, setup_allow_out_of_mem = stbrp_setup_allow_out_of_mem,
setup_heuristic = stbrp_setup_heuristic setup_heuristic = stbrp_setup_heuristic
} }
-- ------------------------------------------------------------------------------
-- This software is available under 2 licenses -- choose whichever you prefer.
-- ------------------------------------------------------------------------------
-- ALTERNATIVE A - MIT License
-- Copyright (c) 2017 Sean Barrett
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
-- ------------------------------------------------------------------------------
-- ALTERNATIVE B - Public Domain (www.unlicense.org)
-- This is free and unencumbered software released into the public domain.
-- Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
-- software, either in source code form or as a compiled binary, for any purpose,
-- commercial or non-commercial, and by any means.
-- In jurisdictions that recognize copyright laws, the author or authors of this
-- software dedicate any and all copyright interest in the software to the public
-- domain. We make this dedication for the benefit of the public at large and to
-- the detriment of our heirs and successors. We intend this dedication to be an
-- overt act of relinquishment in perpetuity of all present and future rights to
-- this software under copyright law.
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-- ------------------------------------------------------------------------------
+336 -35
View File
@@ -1,6 +1,221 @@
--- Dear ImGui --- Dear ImGui
-- This is a Lua port of original `imstb_textedit.h` -- This is a Lua port of original `imstb_textedit.h`
-- [DEAR IMGUI]
-- This is a slightly modified version of stb_textedit.h 1.14.
-- Those changes would need to be pushed into nothings/stb:
-- - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)
-- - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783)
-- - Added name to struct or it may be forward declared in our code.
-- - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925)
-- - Changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
-- Grep for [DEAR IMGUI] to find some changes.
-- - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_*
-- stb_textedit.h - v1.14 - public domain - Sean Barrett
-- Development of this library was sponsored by RAD Game Tools
--
-- This C header file implements the guts of a multi-line text-editing
-- widget; you implement display, word-wrapping, and low-level string
-- insertion/deletion, and stb_textedit will map user inputs into
-- insertions & deletions, plus updates to the cursor position,
-- selection state, and undo state.
--
-- It is intended for use in games and other systems that need to build
-- their own custom widgets and which do not have heavy text-editing
-- requirements (this library is not recommended for use for editing large
-- texts, as its performance does not scale and it has limited undo).
--
-- Non-trivial behaviors are modelled after Windows text controls.
--
--
-- LICENSE
--
-- See end of file for license information.
--
--
-- DEPENDENCIES
--
-- Uses the C runtime function 'memmove', which you can override
-- by defining IMSTB_TEXTEDIT_memmove before the implementation.
-- (In this port: ImStb.TEXTEDIT_memmove.)
--
--
-- VERSION HISTORY
--
-- !!!! (2025-10-23) changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
-- 1.14 (2021-07-11) page up/down, various fixes
-- 1.13 (2019-02-07) fix bug in undo size management
-- 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
-- 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield
-- 1.10 (2016-10-25) suppress warnings about casting away const with -Wcast-qual
-- 1.9 (2016-08-27) customizable move-by-word
-- 1.8 (2016-04-02) better keyboard handling when mouse button is down
-- 1.7 (2015-09-13) change y range handling in case baseline is non-0
-- 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove
-- 1.5 (2014-09-10) add support for secondary keys for OS X
-- 1.4 (2014-08-17) fix signed/unsigned warnings
-- 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary
-- 1.2 (2014-05-27) fix some RAD types that had crept into the new code
-- 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE )
-- 1.0 (2012-07-26) improve documentation, initial public release
-- 0.3 (2012-02-24) bugfixes, single-line mode; insert mode
-- 0.2 (2011-11-28) fixes to undo/redo
-- 0.1 (2010-07-08) initial version
--
-- ADDITIONAL CONTRIBUTORS
--
-- Ulf Winklemann: move-by-word in 1.1
-- Fabian Giesen: secondary key inputs in 1.5
-- Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6
-- Louis Schnellbach: page up/down in 1.14
--
-- Bugfixes:
-- Scott Graham
-- Daniel Keller
-- Omar Cornut
-- Dan Thompson
--
-- USAGE
--
-- The basic concept is that you provide a "string" object which
-- behaves like an array of characters. stb_textedit uses indices to
-- refer to positions in the string, implicitly representing positions
-- in the displayed textedit. This is true for both plain text and
-- rich text; even with rich text stb_truetype interacts with your
-- code as if there was an array of all the displayed characters.
--
-- Symbols you must define for implementation mode (in this port: fields of ImStb, see below):
--
-- STB_TEXTEDIT_STRING the type of object representing a string being edited,
-- typically this is a wrapper object with other data you need
--
-- STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1))
-- STB_TEXTEDIT_LAYOUTROW(r,obj,n) returns the results of laying out a line of characters
-- starting from character #n (see discussion below)
-- STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character
-- to the xpos of the i+1'th char for a line of characters
-- starting at character #n (i.e. accounts for kerning
-- with previous char)
-- STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 1-based in this port
-- STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
-- as manually wordwrapping for end-of-line positioning
--
-- STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i
-- STB_TEXTEDIT_INSERTCHARS(obj,i,c,c_offset,n) try to insert n characters at i (taken from array c starting at c_offset)
-- returns number of characters actually inserted. [DEAR IMGUI]
--
-- STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key
--
-- STB_TEXTEDIT_K_LEFT keyboard input to move cursor left
-- STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right
-- STB_TEXTEDIT_K_UP keyboard input to move cursor up
-- STB_TEXTEDIT_K_DOWN keyboard input to move cursor down
-- STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page
-- STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page
-- STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line -- e.g. HOME
-- STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line -- e.g. END
-- STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text -- e.g. ctrl-HOME
-- STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text -- e.g. ctrl-END
-- STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor
-- STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor
-- STB_TEXTEDIT_K_UNDO keyboard input to perform undo
-- STB_TEXTEDIT_K_REDO keyboard input to perform redo
--
-- STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to
-- STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to
-- STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word -- e.g. ctrl-LEFT
-- STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word -- e.g. ctrl-RIGHT
--
-- To support UTF-8:
--
-- STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character
-- STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character
-- Do NOT define STB_TEXTEDIT_KEYTOTEXT.
-- Instead, call stb_textedit_text() directly for text contents.
--
-- Keyboard input must be encoded as a single integer value; e.g. a character code
-- and some bitflags that represent shift states. to simplify the interface, SHIFT must
-- be a bitflag, so we can test the shifted state of cursor movements to allow selection,
-- i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.
--
-- STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed
-- row of characters assuming they start on the i'th character--the width and
-- the height and the number of characters consumed. This allows this library
-- to traverse the entire layout incrementally. You need to compute word-wrapping
-- here.
--
-- Each textfield keeps its own insert mode state, which is not how normal
-- applications work. To keep an app-wide insert mode, update/copy the
-- "insert_mode" field of STB_TexteditState before/after calling API functions.
--
-- API (in this port: fields of the table returned by this file)
--
-- initialize_state(state, is_single_line)
--
-- click(str, state, x, y)
-- drag(str, state, x, y)
-- cut(str, state) -> bool
-- paste(str, state, text, len) -> bool
-- key(str, state, key)
-- text(str, state, text, text_len)
--
-- Each of these functions potentially updates the string and updates the
-- state.
--
-- initialize_state:
-- set the textedit state to a known good default state when initially
-- constructing the textedit.
--
-- click:
-- call this with the mouse x,y on a mouse down; it will update the cursor
-- and reset the selection start/end to the cursor point. the x,y must
-- be relative to the text widget, with (0,0) being the top left.
--
-- drag:
-- call this with the mouse x,y on a mouse drag/up; it will update the
-- cursor and the selection end point
--
-- cut:
-- call this to delete the current selection; returns true if there was
-- one. you should FIRST copy the current selection to the system paste buffer.
-- (To copy, just copy the current selection out of the string yourself.)
--
-- paste:
-- call this to paste text at the current cursor point or over the current
-- selection if there is one.
--
-- key:
-- call this for keyboard inputs sent to the textfield. you can use it
-- for "key down" events or for "translated" key events.
--
-- text: (added 2025)
-- call this to directly send text input the textfield, which is required
-- for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT()
-- cannot infer text length.
--
--
-- When rendering, you can read the cursor position and selection state from
-- the STB_TexteditState.
--
--
-- Notes:
--
-- This is designed to be usable in IMGUI, so it allows for the possibility of
-- running in an IMGUI that has NOT cached the multi-line layout. For this
-- reason, it provides an interface that is compatible with computing the
-- layout incrementally--we try to make sure we make as few passes through
-- as possible. (For example, to locate the mouse pointer in the text, we
-- could define functions that return the X and Y positions of characters
-- and binary search Y and then X, but if we're doing dynamic layout this
-- will run the layout algorithm many times, so instead we manually search
-- forward in one pass. Similar logic applies to e.g. up-arrow and
-- down-arrow movement.)
--
-- If it's run in a widget that *has* cached the layout, then this is less
-- efficient, but it's not horrible on modern computers. But you wouldn't
-- want to edit million-line files with it.
-- ALL TABLES IN THIS FILE ARE 1-BASED! -- ALL TABLES IN THIS FILE ARE 1-BASED!
-- Character Indexing: -- Character Indexing:
-- idx=n, the n'th char (1-based) -- idx=n, the n'th char (1-based)
@@ -13,6 +228,10 @@ local STB_TEXTEDIT_STRINGLEN = ImStb.TEXTEDIT_STRINGLEN
local STB_TEXTEDIT_GETCHAR = ImStb.TEXTEDIT_GETCHAR local STB_TEXTEDIT_GETCHAR = ImStb.TEXTEDIT_GETCHAR
local STB_TEXTEDIT_GETWIDTH = ImStb.TEXTEDIT_GETWIDTH local STB_TEXTEDIT_GETWIDTH = ImStb.TEXTEDIT_GETWIDTH
local STB_TEXTEDIT_LAYOUTROW = ImStb.TEXTEDIT_LAYOUTROW local STB_TEXTEDIT_LAYOUTROW = ImStb.TEXTEDIT_LAYOUTROW
-- [DEAR IMGUI]
-- Functions must be implemented for UTF8 support
-- Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
-- There is not necessarily a '[DEAR IMGUI]' at the usage sites.
local IMSTB_TEXTEDIT_GETNEXTCHARINDEX = ImStb.TEXTEDIT_GETNEXTCHARINDEX local IMSTB_TEXTEDIT_GETNEXTCHARINDEX = ImStb.TEXTEDIT_GETNEXTCHARINDEX
local IMSTB_TEXTEDIT_GETPREVCHARINDEX = ImStb.TEXTEDIT_GETPREVCHARINDEX local IMSTB_TEXTEDIT_GETPREVCHARINDEX = ImStb.TEXTEDIT_GETPREVCHARINDEX
local STB_TEXTEDIT_MOVEWORDLEFT = ImStb.TEXTEDIT_MOVEWORDLEFT local STB_TEXTEDIT_MOVEWORDLEFT = ImStb.TEXTEDIT_MOVEWORDLEFT
@@ -22,34 +241,35 @@ local STB_TEXTEDIT_MOVELINEEND = ImStb.TEXTEDIT_MOVELINEEND
local STB_TEXTEDIT_DELETECHARS = ImStb.TEXTEDIT_DELETECHARS local STB_TEXTEDIT_DELETECHARS = ImStb.TEXTEDIT_DELETECHARS
local STB_TEXTEDIT_INSERTCHARS = ImStb.TEXTEDIT_INSERTCHARS local STB_TEXTEDIT_INSERTCHARS = ImStb.TEXTEDIT_INSERTCHARS
STB_TEXTEDIT_NEWLINE = 10 STB_TEXTEDIT_NEWLINE = 10 -- the character returned by _GETCHAR() we recognize as manually wordwrapping for end-of-line positioning
STB_TEXTEDIT_K_LEFT = 0x200000 -- keyboard input to move cursor left STB_TEXTEDIT_K_LEFT = 0x200000 -- keyboard input to move cursor left
STB_TEXTEDIT_K_RIGHT = 0x200001 -- keyboard input to move cursor right STB_TEXTEDIT_K_RIGHT = 0x200001 -- keyboard input to move cursor right
STB_TEXTEDIT_K_UP = 0x200002 -- keyboard input to move cursor up STB_TEXTEDIT_K_UP = 0x200002 -- keyboard input to move cursor up
STB_TEXTEDIT_K_DOWN = 0x200003 -- keyboard input to move cursor down STB_TEXTEDIT_K_DOWN = 0x200003 -- keyboard input to move cursor down
STB_TEXTEDIT_K_LINESTART = 0x200004 -- keyboard input to move cursor to start of line STB_TEXTEDIT_K_LINESTART = 0x200004 -- keyboard input to move cursor to start of line -- e.g. HOME
STB_TEXTEDIT_K_LINEEND = 0x200005 -- keyboard input to move cursor to end of line STB_TEXTEDIT_K_LINEEND = 0x200005 -- keyboard input to move cursor to end of line -- e.g. END
STB_TEXTEDIT_K_TEXTSTART = 0x200006 -- keyboard input to move cursor to start of text STB_TEXTEDIT_K_TEXTSTART = 0x200006 -- keyboard input to move cursor to start of text -- e.g. ctrl-HOME
STB_TEXTEDIT_K_TEXTEND = 0x200007 -- keyboard input to move cursor to end of text STB_TEXTEDIT_K_TEXTEND = 0x200007 -- keyboard input to move cursor to end of text -- e.g. ctrl-END
STB_TEXTEDIT_K_DELETE = 0x200008 -- keyboard input to delete selection or character under cursor STB_TEXTEDIT_K_DELETE = 0x200008 -- keyboard input to delete selection or character under cursor
STB_TEXTEDIT_K_BACKSPACE = 0x200009 -- keyboard input to delete selection or character left of cursor STB_TEXTEDIT_K_BACKSPACE = 0x200009 -- keyboard input to delete selection or character left of cursor
STB_TEXTEDIT_K_UNDO = 0x20000A -- keyboard input to perform undo STB_TEXTEDIT_K_UNDO = 0x20000A -- keyboard input to perform undo
STB_TEXTEDIT_K_REDO = 0x20000B -- keyboard input to perform redo STB_TEXTEDIT_K_REDO = 0x20000B -- keyboard input to perform redo
STB_TEXTEDIT_K_WORDLEFT = 0x20000C -- keyboard input to move cursor left one word STB_TEXTEDIT_K_WORDLEFT = 0x20000C -- keyboard input to move cursor left one word -- e.g. ctrl-LEFT
STB_TEXTEDIT_K_WORDRIGHT = 0x20000D -- keyboard input to move cursor right one word STB_TEXTEDIT_K_WORDRIGHT = 0x20000D -- keyboard input to move cursor right one word -- e.g. ctrl-RIGHT
STB_TEXTEDIT_K_PGUP = 0x20000E -- keyboard input to move cursor up a page STB_TEXTEDIT_K_PGUP = 0x20000E -- keyboard input to move cursor up a page
STB_TEXTEDIT_K_PGDOWN = 0x20000F -- keyboard input to move cursor down a page STB_TEXTEDIT_K_PGDOWN = 0x20000F -- keyboard input to move cursor down a page
STB_TEXTEDIT_K_SHIFT = 0x400000 STB_TEXTEDIT_K_SHIFT = 0x400000 -- a power of two that is or'd in to a keyboard input to represent the shift key
IMSTB_TEXTEDIT_UNDOSTATECOUNT = 99 IMSTB_TEXTEDIT_UNDOSTATECOUNT = 99 -- the number of undo states to allow
local IMSTB_TEXTEDIT_UNDOCHARCOUNT = 999 local IMSTB_TEXTEDIT_UNDOCHARCOUNT = 999 -- the number of characters to store in the undo buffer
local IMSTB_TEXTEDIT_memmove = ImStb.TEXTEDIT_memmove local IMSTB_TEXTEDIT_memmove = ImStb.TEXTEDIT_memmove
-- forward declarations
local stb_text_undo local stb_text_undo
local stb_text_redo local stb_text_redo
---------------------------------------------------------------- -------------------------------------------------------------------------------
---------------------------------------------------------------- -------------------------------------------------------------------------------
--- ---
--- STB_TexteditState --- STB_TexteditState
--- ---
@@ -72,6 +292,7 @@ local stb_text_redo
--- @nodiscard --- @nodiscard
local function StbUndoRecord() local function StbUndoRecord()
return { return {
-- private data
where = 1, where = 1,
insert_length = 0, insert_length = 0,
delete_length = 0, delete_length = 0,
@@ -101,6 +322,7 @@ local function StbUndoState()
end end
return { return {
-- private data
undo_rec = undo_rec, undo_rec = undo_rec,
undo_char = undo_char, undo_char = undo_char,
@@ -131,16 +353,34 @@ end
--- @nodiscard --- @nodiscard
function STB_TexteditState() function STB_TexteditState()
return { return {
cursor = 0, ---------------------
--
-- public data
--
select_start = 0, cursor = 0,
-- position of the text cursor within the string
select_start = 0, -- selection start point
select_end = 0, select_end = 0,
-- selection start and end point in characters; if equal, no selection.
-- note that start may be less than or greater than end (e.g. when
-- dragging the mouse, start is where the initial click was, and you
-- can drag in either direction)
insert_mode = false, insert_mode = false,
-- each textfield keeps its own insert mode state. to keep an app-wide
-- insert mode, copy this value in/out of the app state
row_count_per_page = 0, row_count_per_page = 0,
-- page size in number of row.
-- this value MUST be set to >0 for pageup or pagedown in multilines documents.
cursor_at_end_of_line = false, ---------------------
--
-- private data
--
cursor_at_end_of_line = false, -- not implemented yet
initialized = false, initialized = false,
has_preferred_x = false, has_preferred_x = false,
single_line = false, single_line = false,
@@ -148,7 +388,7 @@ function STB_TexteditState()
padding2 = 0, padding2 = 0,
padding3 = 0, padding3 = 0,
preferred_x = 0.0, preferred_x = 0.0, -- this determines where the cursor up/down tries to seek to along x
undostate = StbUndoState() undostate = StbUndoState()
} }
@@ -159,12 +399,20 @@ local function STB_TEXT_HAS_SELECTION(s)
return s.select_start ~= s.select_end return s.select_start ~= s.select_end
end end
-------------------------------------------------------------------------------
---
--- StbTexteditRow
---
--- Result of layout query, used by stb_textedit to determine where
--- the text in each row is.
-- result of layout query
--- @class StbTexteditRow --- @class StbTexteditRow
--- @field x0 float # starting x location --- @field x0 float # starting x location, end x location (allows for align=right, etc)
--- @field x1 float # end x location --- @field x1 float # end x location
--- @field baseline_y_delta float # position of baseline relative to previous row's baseline --- @field baseline_y_delta float # position of baseline relative to previous row's baseline
--- @field ymin float # height of row above baseline --- @field ymin float # height of row above and below baseline
--- @field ymax float # height of row below baseline --- @field ymax float # height of row above and below baseline
--- @field num_chars int --- @field num_chars int
--- @return StbTexteditRow --- @return StbTexteditRow
@@ -196,10 +444,15 @@ end
---------------------------------------- ----------------------------------------
---------------------------------------- ----------------------------------------
--- ---
--- Implementation --- Implementation mode
--- ---
--- ---
-------------------------------------------------------------------------------
---
--- Mouse input handling
---
local stb_text_locate_coord do local stb_text_locate_coord do
-- only create once, reuse later -- only create once, reuse later
@@ -351,6 +604,7 @@ end
--- ---
--- ---
-- forward declarations
local stb_text_makeundo_insert local stb_text_makeundo_insert
local stb_text_makeundo_delete local stb_text_makeundo_delete
local stb_text_makeundo_replace local stb_text_makeundo_replace
@@ -399,8 +653,8 @@ function stb_textedit_find_charpos(find, str, n, single_line)
local z = STB_TEXTEDIT_STRINGLEN(str) local z = STB_TEXTEDIT_STRINGLEN(str)
local first local first
-- special case if it's at the end (may not be needed?)
if n == z + 1 and single_line then if n == z + 1 and single_line then
-- special case if it's at the end (may not be needed?)
STB_TEXTEDIT_LAYOUTROW(r, str, 1) STB_TEXTEDIT_LAYOUTROW(r, str, 1)
find.y = 0 find.y = 0
find.first_char = 1 find.first_char = 1
@@ -420,18 +674,18 @@ function stb_textedit_find_charpos(find, str, n, single_line)
if n < i + r.num_chars - 1 then if n < i + r.num_chars - 1 then
break break
end end
if str.LastMoveDirectionLR == ImGuiDir.Right and str.Stb.cursor > 1 and str.Stb.cursor == i + r.num_chars and STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) ~= STB_TEXTEDIT_NEWLINE then -- [IMGUI] Wrapping point handling if str.LastMoveDirectionLR == ImGuiDir.Right and str.Stb.cursor > 1 and str.Stb.cursor == i + r.num_chars and STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) ~= STB_TEXTEDIT_NEWLINE then -- [DEAR IMGUI] Wrapping point handling
break break
end end
if (i - 1) + r.num_chars == z and z > 0 and STB_TEXTEDIT_GETCHAR(str, z) ~= STB_TEXTEDIT_NEWLINE then -- [IMGUI] special handling for last line if (i - 1) + r.num_chars == z and z > 0 and STB_TEXTEDIT_GETCHAR(str, z) ~= STB_TEXTEDIT_NEWLINE then -- [DEAR IMGUI] special handling for last line
break break -- [DEAR IMGUI]
end end
prev_start = i prev_start = i
i = i + r.num_chars i = i + r.num_chars
find.y = find.y + r.baseline_y_delta find.y = find.y + r.baseline_y_delta
if i == z + 1 then -- [IMGUI] if i == z + 1 then -- [DEAR IMGUI]
r.num_chars = 0 r.num_chars = 0 -- [DEAR IMGUI]
break break -- [DEAR IMGUI]
end end
end end
@@ -479,7 +733,7 @@ local function stb_textedit_delete(str, state, where, len)
state.has_preferred_x = false state.has_preferred_x = false
end end
-- delete the selection -- delete the section
--- @param str IMSTB_TEXTEDIT_STRING --- @param str IMSTB_TEXTEDIT_STRING
--- @param state STB_TexteditState --- @param state STB_TexteditState
local function stb_textedit_delete_selection(str, state) local function stb_textedit_delete_selection(str, state)
@@ -498,7 +752,7 @@ local function stb_textedit_delete_selection(str, state)
end end
end end
-- canonicalize the selection so start <= end -- canoncialize the selection so start <= end
--- @param state STB_TexteditState --- @param state STB_TexteditState
local function stb_textedit_sortselection(state) local function stb_textedit_sortselection(state)
if state.select_end < state.select_start then if state.select_end < state.select_start then
@@ -579,7 +833,7 @@ local function stb_textedit_paste_internal(str, state, text, len)
end end
-- API key: process text input -- API key: process text input
-- [IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility. -- [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.
--- @param str IMSTB_TEXTEDIT_STRING --- @param str IMSTB_TEXTEDIT_STRING
--- @param state STB_TexteditState --- @param state STB_TexteditState
--- @param text IMSTB_TEXTEDIT_CHARTYPE[] --- @param text IMSTB_TEXTEDIT_CHARTYPE[]
@@ -732,7 +986,7 @@ function stb_textedit_key(str, state, key)
break break
end end
-- [IMGUI] -- [DEAR IMGUI]
-- going down while being on the last line shouldn't bring us to that line end -- going down while being on the last line shouldn't bring us to that line end
--if STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) ~= STB_TEXTEDIT_NEWLINE then --if STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) ~= STB_TEXTEDIT_NEWLINE then
-- break -- break
@@ -929,8 +1183,8 @@ end
----------------------------------------------------- -----------------------------------------------------
--- ---
--- Undo processing --- Undo processing
--- OPTIMIZE: the undo/redo buffer should be circular
--- ---
--- @OPTIMIZE: the undo/redo buffer should be circular
--- @param state StbUndoState --- @param state StbUndoState
local function stb_textedit_flush_redo(state) local function stb_textedit_flush_redo(state)
@@ -950,7 +1204,7 @@ local function stb_textedit_discard_undo(state)
IMSTB_TEXTEDIT_memmove(state.undo_char, 1, state.undo_char, n + 1, state.undo_char_point - 1) IMSTB_TEXTEDIT_memmove(state.undo_char, 1, state.undo_char, n + 1, state.undo_char_point - 1)
for i = 1, state.undo_point do for i = 1, state.undo_point do
if state.undo_rec[i].char_storage >= 0 then if state.undo_rec[i].char_storage >= 0 then
state.undo_rec[i].char_storage = state.undo_rec[i].char_storage - n -- OPTIMIZE: get rid of char_storage and infer it state.undo_rec[i].char_storage = state.undo_rec[i].char_storage - n -- @OPTIMIZE: get rid of char_storage and infer it
end end
end end
end end
@@ -983,7 +1237,7 @@ local function stb_textedit_discard_redo(state)
end end
-- now move all the redo records towards the end of the buffer; the first one is at 'redo_point' -- now move all the redo records towards the end of the buffer; the first one is at 'redo_point'
-- [IMGUI] -- [DEAR IMGUI]
local move_size = IMSTB_TEXTEDIT_UNDOSTATECOUNT - state.redo_point - 1 local move_size = IMSTB_TEXTEDIT_UNDOSTATECOUNT - state.redo_point - 1
IMSTB_TEXTEDIT_memmove(state.undo_rec, state.redo_point + 1, state.undo_rec, state.redo_point, move_size) IMSTB_TEXTEDIT_memmove(state.undo_rec, state.redo_point + 1, state.undo_rec, state.redo_point, move_size)
@@ -1129,6 +1383,9 @@ function stb_text_redo(str, state)
local u = s.undo_rec[s.undo_point] local u = s.undo_rec[s.undo_point]
local r = s.undo_rec[s.redo_point] local r = s.undo_rec[s.redo_point]
-- we KNOW there must be room for the undo record, because the redo record
-- was derived from an undo record
u.delete_length = r.insert_length u.delete_length = r.insert_length
u.insert_length = r.delete_length u.insert_length = r.delete_length
u.where = r.where u.where = r.where
@@ -1137,6 +1394,7 @@ function stb_text_redo(str, state)
if r.delete_length > 0 then if r.delete_length > 0 then
-- the redo record requires us to delete characters, so the undo record -- the redo record requires us to delete characters, so the undo record
-- needs to store the characters -- needs to store the characters
if s.undo_char_point + u.insert_length > s.redo_char_point then if s.undo_char_point + u.insert_length > s.redo_char_point then
u.insert_length = 0 u.insert_length = 0
u.delete_length = 0 u.delete_length = 0
@@ -1144,6 +1402,7 @@ function stb_text_redo(str, state)
u.char_storage = s.undo_char_point u.char_storage = s.undo_char_point
s.undo_char_point = s.undo_char_point + u.insert_length s.undo_char_point = s.undo_char_point + u.insert_length
-- now save the characters
for i = 0, u.insert_length - 1 do for i = 0, u.insert_length - 1 do
s.undo_char[u.char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i) s.undo_char[u.char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i)
end end
@@ -1153,6 +1412,7 @@ function stb_text_redo(str, state)
end end
if r.insert_length ~= 0 then if r.insert_length ~= 0 then
-- easy case: need to insert n characters
r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, s.undo_char, r.char_storage, r.insert_length) r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, s.undo_char, r.char_storage, r.insert_length)
s.redo_char_point = s.redo_char_point + r.insert_length s.redo_char_point = s.redo_char_point + r.insert_length
end end
@@ -1199,6 +1459,7 @@ function stb_text_makeundo_replace(str, state, where, old_length, new_length)
end end
end end
-- reset the state to default
--- @param state STB_TexteditState --- @param state STB_TexteditState
--- @param is_single_line bool --- @param is_single_line bool
local function stb_textedit_clear_state(state, is_single_line) local function stb_textedit_clear_state(state, is_single_line)
@@ -1222,7 +1483,7 @@ return {
click = stb_textedit_click, click = stb_textedit_click,
drag = stb_textedit_drag, drag = stb_textedit_drag,
createundo = stb_text_createundo, createundo = stb_text_createundo,
initialize_state = stb_textedit_clear_state, initialize_state = stb_textedit_clear_state, -- API initialize
text = stb_textedit_text, text = stb_textedit_text,
key = stb_textedit_key, key = stb_textedit_key,
@@ -1236,3 +1497,43 @@ return {
HAS_SELECTION = STB_TEXT_HAS_SELECTION HAS_SELECTION = STB_TEXT_HAS_SELECTION
} }
-------------------------------------------------------------------------------
-- This software is available under 2 licenses -- choose whichever you prefer.
-------------------------------------------------------------------------------
-- ALTERNATIVE A - MIT License
-- Copyright (c) 2017 Sean Barrett
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
-------------------------------------------------------------------------------
-- ALTERNATIVE B - Public Domain (www.unlicense.org)
-- This is free and unencumbered software released into the public domain.
-- Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
-- software, either in source code form or as a compiled binary, for any purpose,
-- commercial or non-commercial, and by any means.
-- In jurisdictions that recognize copyright laws, the author or authors of this
-- software dedicate any and all copyright interest in the software to the public
-- domain. We make this dedication for the benefit of the public at large and to
-- the detriment of our heirs and successors. We intend this dedication to be an
-- overt act of relinquishment in perpetuity of all present and future rights to
-- this software under copyright law.
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-------------------------------------------------------------------------------
+374 -54
View File
@@ -1,6 +1,164 @@
--- Dear ImGui --- Dear ImGui
-- This is a Lua port of original `imstb_truetype.h` -- This is a Lua port of original `imstb_truetype.h`
-- [DEAR IMGUI]
-- This is a slightly modified version of stb_truetype.h 1.26.
-- Mostly fixing for compiler and static analyzer warnings.
-- Grep for [DEAR IMGUI] to find the changes.
-- stb_truetype.h - v1.26 - public domain
-- authored from 2009-2021 by Sean Barrett / RAD Game Tools
--
-- =======================================================================
--
-- NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES
--
-- This library does no range checking of the offsets found in the file,
-- meaning an attacker can use it to read arbitrary memory.
--
-- =======================================================================
--
-- This library processes TrueType files:
-- parse files
-- extract glyph metrics
-- extract glyph shapes
-- render glyphs to one-channel bitmaps with antialiasing (box filter)
--
-- Todo:
-- non-MS cmaps
-- crashproof on bad data
-- hinting? (no longer patented)
-- cleartype-style AA?
-- optimize: build edge-list directly from curves
-- optimize: rasterize directly from curves?
--
-- ADDITIONAL CONTRIBUTORS
--
-- Mikko Mononen: compound shape support, more cmap formats
-- Tor Andersson: kerning, subpixel rendering
-- Dougall Johnson: OpenType / Type 2 font handling
-- Daniel Ribeiro Maciel: basic GPOS-based kerning
--
-- Misc other:
-- Ryan Gordon
-- Simon Glass
-- github:IntellectualKitty
-- Imanol Celaya
-- Daniel Ribeiro Maciel
--
-- Bug/warning reports/fixes:
-- "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe
-- Cass Everitt Martins Mozeiko github:aloucks
-- stoiko (Haemimont Games) Cap Petschulat github:oyvindjam
-- Brian Hook Omar Cornut github:vassvik
-- Walter van Niftrik Ryan Griege
-- David Gow Peter LaValle
-- David Given Sergey Popov
-- Ivan-Assen Ivanov Giumo X. Clanjor
-- Anthony Pesch Higor Euripedes
-- Johan Duparc Thomas Fields
-- Hou Qiming Derek Vinyard
-- Rob Loach Cort Stratton
-- Kenney Phillis Jr. Brian Costabile
-- Ken Voskuil (kaesve)
--
-- USAGE
--
-- "Load" a font file from a memory buffer (you have to keep the buffer loaded)
-- stbtt_InitFont()
-- stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections
--
-- Character advance/positioning
-- stbtt_GetFontVMetrics()
--
-- Starting with version 1.06, the rasterizer was replaced with a new,
-- faster and generally-more-precise rasterizer. The new rasterizer more
-- accurately measures pixel coverage for anti-aliasing, except in the case
-- where multiple shapes overlap, in which case it overestimates the AA pixel
-- coverage. Thus, anti-aliasing of intersecting shapes may look wrong.
-- (This port only implements the new rasterizer.)
--
-- ADDITIONAL DOCUMENTATION
--
-- Some important concepts to understand to use this library:
--
-- Codepoint
-- Characters are defined by unicode codepoints, e.g. 65 is
-- uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is
-- the hiragana for "ma".
--
-- Glyph
-- A visual character shape (every codepoint is rendered as
-- some glyph)
--
-- Glyph index
-- A font-specific integer ID representing a glyph
--
-- Baseline
-- Glyph shapes are defined relative to a baseline, which is the
-- bottom of uppercase characters. Characters extend both above
-- and below the baseline.
--
-- Current Point
-- As you draw text to the screen, you keep track of a "current point"
-- which is the origin of each character. The current point's vertical
-- position is the baseline. Even "baked fonts" use this model.
--
-- Vertical Font Metrics
-- The vertical qualities of the font, used to vertically position
-- and space the characters. See docs for stbtt_GetFontVMetrics.
--
-- Font Size in Pixels or Points
-- The preferred interface for specifying font sizes in stb_truetype
-- is to specify how tall the font's vertical extent should be in pixels.
--
-- DETAILED USAGE:
--
-- Scale:
-- Select how high you want the font to be, in pixels.
-- Call ScaleForPixelHeight to compute a scale factor SF that will be
-- used by all other functions.
--
-- Current point:
-- Set the current point where the first character will appear. The
-- first character could extend left of the current point; this is font
-- dependent. You can either choose a current point that is the leftmost
-- point and hope, or add some padding, or check the bounding box or
-- left-side-bearing of the first character to be displayed and set
-- the current point based on that.
--
-- Displaying a character:
-- Compute the bounding box of the character. It will contain signed values
-- relative to <current_point, baseline>. I.e. if it returns x0,y0,x1,y1,
-- then the character should be displayed in the rectangle from
-- <current_point+SF*x0, baseline+SF*y0> to <current_point+SF*x1,baseline+SF*y1).
--
-- Advancing for the next character:
-- Call GlyphHMetrics, and compute 'current_point += SF * advance'.
--
--
-- ADVANCED USAGE
--
-- Quality:
--
-- - Use the functions with Subpixel at the end to allow your characters
-- to have subpixel positioning. Since the font is anti-aliased, not
-- hinted, this is very import for quality. (This is not possible with
-- baked fonts.)
--
-- Performance:
--
-- - Convert Unicode codepoints to glyph indexes and operate on the glyphs;
-- if you don't do this, stb_truetype is forced to do the conversion on
-- every call.
--
-- NOTES
--
-- The system uses the raw data found in the .ttf file without changing it
-- and without building auxiliary data structures. This is a bit inefficient
-- on little-endian systems (the data is big-endian), but assuming you're
-- caching the bitmaps or glyph shapes this shouldn't be a big deal.
--- STBTT_RASTERIZER_VERSION == 2 --- STBTT_RASTERIZER_VERSION == 2
local stbtt_fontinfo local stbtt_fontinfo
@@ -21,7 +179,7 @@ local stbtt_FindGlyphIndex
local STBTT_MAX_OVERSAMPLE = 8 local STBTT_MAX_OVERSAMPLE = 8
--- @enum STBTT_PLATFORM_ID --- @enum STBTT_PLATFORM_ID
local STBTT_PLATFORM_ID = { local STBTT_PLATFORM_ID = { -- platformID
UNICODE = 0, UNICODE = 0,
MAC = 1, MAC = 1,
ISO = 2, ISO = 2,
@@ -29,7 +187,7 @@ local STBTT_PLATFORM_ID = {
} }
--- @enum STBTT_MS_EID --- @enum STBTT_MS_EID
local STBTT_MS_EID = { local STBTT_MS_EID = { -- encodingID for STBTT_PLATFORM_ID_MICROSOFT
SYMBOL = 0, SYMBOL = 0,
UNICODE_BMP = 1, UNICODE_BMP = 1,
SHIFTJIS = 2, SHIFTJIS = 2,
@@ -75,6 +233,7 @@ local stbtt_int8 = ImS8
local stbtt_uint8 = ImU8 local stbtt_uint8 = ImU8
local unsigned_char = stbtt_uint8 local unsigned_char = stbtt_uint8
-- private structure
--- @class stbtt__buf --- @class stbtt__buf
--- @field data? table # 1-based byte table --- @field data? table # 1-based byte table
--- @field offset int # Every access to `data` will be offset by this --- @field offset int # Every access to `data` will be offset by this
@@ -93,6 +252,8 @@ local function stbtt__buf(initial_offset)
} }
end end
-- The following structure is defined publicly so you can declare one on
-- the stack or as a global or etc, but you should treat it as opaque.
--- @class stbtt_fontinfo --- @class stbtt_fontinfo
--- @field userdata any --- @field userdata any
--- @field data table # pointer to .ttf file --- @field data table # pointer to .ttf file
@@ -283,6 +444,7 @@ local function stbtt__new_active(e, off_x, start_point)
return z return z
end end
-- @TODO: don't expose this structure
--- @class stbtt__bitmap --- @class stbtt__bitmap
--- @field w int --- @field w int
--- @field h int --- @field h int
@@ -496,7 +658,7 @@ end
------------------------------------- -------------------------------------
------------------------------------- -------------------------------------
--- ---
--- Accessors to parse data from file --- accessors to parse data from file
--- ---
--- ---
@@ -535,14 +697,16 @@ local function stbtt_tag4(p, c0, c1, c2, c3, offset) offset = offset or 0; retur
local function stbtt_tag(p, str, offset) local c0, c1, c2, c3 = string.byte(str, 1, 4); return stbtt_tag4(p, c0, c1, c2, c3, offset) end local function stbtt_tag(p, str, offset) local c0, c1, c2, c3 = string.byte(str, 1, 4); return stbtt_tag4(p, c0, c1, c2, c3, offset) end
local function stbtt__isfont(font) local function stbtt__isfont(font)
if stbtt_tag4(font, 49, 0, 0, 0) then return true end -- check the version number
if stbtt_tag(font, "typ1") then return true end if stbtt_tag4(font, 49, 0, 0, 0) then return true end -- TrueType 1
if stbtt_tag(font, "OTTO") then return true end if stbtt_tag(font, "typ1") then return true end -- TrueType with type 1 font -- we don't support this!
if stbtt_tag4(font, 0, 1, 0, 0) then return true end if stbtt_tag(font, "OTTO") then return true end -- OpenType with CFF
if stbtt_tag(font, "true") then return true end if stbtt_tag4(font, 0, 1, 0, 0) then return true end -- OpenType 1.0
if stbtt_tag(font, "true") then return true end -- Apple specification for TrueType fonts
return false return false
end end
-- @OPTIMIZE: binary search
local function stbtt__find_table(data, font_start, tag) local function stbtt__find_table(data, font_start, tag)
local num_tables = ttUSHORT(data, font_start + 4) local num_tables = ttUSHORT(data, font_start + 4)
local tabledir = font_start + 12 local tabledir = font_start + 12
@@ -556,11 +720,14 @@ local function stbtt__find_table(data, font_start, tag)
end end
local function stbtt_GetFontOffsetForIndex_internal(font_collection, index) local function stbtt_GetFontOffsetForIndex_internal(font_collection, index)
-- if it's just a font, there's only one valid index
if stbtt__isfont(font_collection) then if stbtt__isfont(font_collection) then
if index == 0 then return 0 else return -1 end if index == 0 then return 0 else return -1 end
end end
-- check if it's a TTC
if stbtt_tag(font_collection, "ttcf") then if stbtt_tag(font_collection, "ttcf") then
-- version 1?
if ttULONG(font_collection, 4) == 0x00010000 or ttULONG(font_collection, 4) == 0x00020000 then if ttULONG(font_collection, 4) == 0x00010000 or ttULONG(font_collection, 4) == 0x00020000 then
local n = ttLONG(font_collection, 8) local n = ttLONG(font_collection, 8)
if index >= n then if index >= n then
@@ -598,23 +765,25 @@ local function stbtt_InitFont_internal(info, data, fontstart)
info.fontstart = fontstart info.fontstart = fontstart
info.cff = stbtt__new_buf(nil, 0) info.cff = stbtt__new_buf(nil, 0)
cmap = stbtt__find_table(data, fontstart, "cmap") cmap = stbtt__find_table(data, fontstart, "cmap") -- required
info.loca = stbtt__find_table(data, fontstart, "loca") info.loca = stbtt__find_table(data, fontstart, "loca") -- required
info.head = stbtt__find_table(data, fontstart, "head") info.head = stbtt__find_table(data, fontstart, "head") -- required
info.glyf = stbtt__find_table(data, fontstart, "glyf") info.glyf = stbtt__find_table(data, fontstart, "glyf") -- required
info.hhea = stbtt__find_table(data, fontstart, "hhea") info.hhea = stbtt__find_table(data, fontstart, "hhea") -- required
info.hmtx = stbtt__find_table(data, fontstart, "hmtx") info.hmtx = stbtt__find_table(data, fontstart, "hmtx") -- required
info.kern = stbtt__find_table(data, fontstart, "kern") info.kern = stbtt__find_table(data, fontstart, "kern") -- not required
info.gpos = stbtt__find_table(data, fontstart, "GPOS") info.gpos = stbtt__find_table(data, fontstart, "GPOS") -- not required
if (cmap == 0 or info.head == 0 or info.hhea == 0 or info.hmtx == 0) then if (cmap == 0 or info.head == 0 or info.hhea == 0 or info.hmtx == 0) then
return false return false
end end
if info.glyf ~= 0 then if info.glyf ~= 0 then
-- required for truetype
if info.loca == 0 then if info.loca == 0 then
return false return false
end end
else else
-- initialization for CFF / Type2 fonts (OTF)
local cff = stbtt__find_table(data, fontstart, "CFF ") local cff = stbtt__find_table(data, fontstart, "CFF ")
if cff == 0 then if cff == 0 then
return false return false
@@ -623,14 +792,16 @@ local function stbtt_InitFont_internal(info, data, fontstart)
info.fontdicts = stbtt__new_buf(nil, 0) info.fontdicts = stbtt__new_buf(nil, 0)
info.fdselect = stbtt__new_buf(nil, 0) info.fdselect = stbtt__new_buf(nil, 0)
info.cff = stbtt__new_buf(data, 64 * 1024 * 1024, cff) -- TODO: this should use size from table -- @TODO this should use size from table (not 512MB)
info.cff = stbtt__new_buf(data, 64 * 1024 * 1024, cff) -- PORT: 64MB here instead of 512MB
local b = info.cff local b = info.cff
-- read the header -- read the header
stbtt__buf_skip(b, 2) stbtt__buf_skip(b, 2)
stbtt__buf_seek(b, stbtt__buf_get8(b)) stbtt__buf_seek(b, stbtt__buf_get8(b)) -- hdrsize
-- TODO: the name INDEX could list multiple fonts, but we just use the first one. -- @TODO the name INDEX could list multiple fonts,
-- but we just use the first one.
stbtt__cff_get_index(b) -- name INDEX stbtt__cff_get_index(b) -- name INDEX
local topdictidx = stbtt__cff_get_index(b) local topdictidx = stbtt__cff_get_index(b)
local topdict = stbtt__cff_index_get(topdictidx, 0) local topdict = stbtt__cff_index_get(topdictidx, 0)
@@ -643,6 +814,7 @@ local function stbtt_InitFont_internal(info, data, fontstart)
local fdselectoff = stbtt__dict_get_ints(topdict, bit32.bor(0x100, 37), 1) local fdselectoff = stbtt__dict_get_ints(topdict, bit32.bor(0x100, 37), 1)
info.subrs = stbtt__get_subrs(b, topdict) info.subrs = stbtt__get_subrs(b, topdict)
-- we only support Type 2 charstrings
if cstype[1] ~= 2 then if cstype[1] ~= 2 then
return false return false
end end
@@ -651,6 +823,7 @@ local function stbtt_InitFont_internal(info, data, fontstart)
end end
if fdarrayoff[1] ~= 0 then if fdarrayoff[1] ~= 0 then
-- looks like a CID font
if fdselectoff[1] == 0 then if fdselectoff[1] == 0 then
return false return false
end end
@@ -673,19 +846,26 @@ local function stbtt_InitFont_internal(info, data, fontstart)
info.svg = -1 info.svg = -1
-- find a cmap encoding table we understand *now* to avoid searching
-- later. (todo: could make this installable)
-- the same regardless of glyph.
numTables = ttUSHORT(data, cmap + 2) numTables = ttUSHORT(data, cmap + 2)
info.index_map = 0 info.index_map = 0
for i = 0, numTables - 1 do for i = 0, numTables - 1 do
local encoding_record = cmap + 4 + 8 * i local encoding_record = cmap + 4 + 8 * i
-- find an encoding we understand:
local platform_id = ttUSHORT(data, encoding_record) local platform_id = ttUSHORT(data, encoding_record)
if platform_id == STBTT_PLATFORM_ID.MICROSOFT then if platform_id == STBTT_PLATFORM_ID.MICROSOFT then
local ms_eid = ttUSHORT(data, encoding_record + 2) local ms_eid = ttUSHORT(data, encoding_record + 2)
if ms_eid == STBTT_MS_EID.UNICODE_BMP or ms_eid == STBTT_MS_EID.UNICODE_FULL then if ms_eid == STBTT_MS_EID.UNICODE_BMP or ms_eid == STBTT_MS_EID.UNICODE_FULL then
-- MS/Unicode
info.index_map = cmap + ttULONG(data, encoding_record + 4) info.index_map = cmap + ttULONG(data, encoding_record + 4)
end end
elseif platform_id == STBTT_PLATFORM_ID.UNICODE then elseif platform_id == STBTT_PLATFORM_ID.UNICODE then
-- Mac/iOS has these
-- all the encodingIDs are unicode, so we don't bother to check it
info.index_map = cmap + ttULONG(data, encoding_record + 4) info.index_map = cmap + ttULONG(data, encoding_record + 4)
end end
end end
@@ -697,6 +877,11 @@ local function stbtt_InitFont_internal(info, data, fontstart)
return true return true
end end
-- If you're going to perform multiple operations on the same character
-- and you want a speed-up, call this function with the character you're
-- going to process, then use glyph-based functions instead of the
-- codepoint-based functions.
-- Returns 0 if the character codepoint is not defined in the font.
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param unicode_codepoint int --- @param unicode_codepoint int
function stbtt_FindGlyphIndex(info, unicode_codepoint) function stbtt_FindGlyphIndex(info, unicode_codepoint)
@@ -704,7 +889,7 @@ function stbtt_FindGlyphIndex(info, unicode_codepoint)
local index_map = info.index_map local index_map = info.index_map
local format = ttUSHORT(data, index_map + 0) local format = ttUSHORT(data, index_map + 0)
if format == 0 then if format == 0 then -- apple byte encoding
local bytes = ttUSHORT(data, index_map + 2) local bytes = ttUSHORT(data, index_map + 2)
if unicode_codepoint < bytes - 6 then if unicode_codepoint < bytes - 6 then
return ttBYTE(data, index_map + 6 + unicode_codepoint) return ttBYTE(data, index_map + 6 + unicode_codepoint)
@@ -718,14 +903,15 @@ function stbtt_FindGlyphIndex(info, unicode_codepoint)
end end
return 0 return 0
elseif format == 2 then elseif format == 2 then
STBTT_assert(false) -- TODO: high-byte mapping for japanese/chinese/korean STBTT_assert(false) -- @TODO: high-byte mapping for japanese/chinese/korean
return 0 return 0
elseif format == 4 then elseif format == 4 then -- standard mapping for windows fonts: binary search collection of ranges
local segcount = bit32.rshift(ttUSHORT(data, index_map + 6), 1) local segcount = bit32.rshift(ttUSHORT(data, index_map + 6), 1)
local searchRange = bit32.rshift(ttUSHORT(data, index_map + 8), 1) local searchRange = bit32.rshift(ttUSHORT(data, index_map + 8), 1)
local entrySelector = ttUSHORT(data, index_map + 10) local entrySelector = ttUSHORT(data, index_map + 10)
local rangeShift = bit32.rshift(ttUSHORT(data, index_map + 12), 1) local rangeShift = bit32.rshift(ttUSHORT(data, index_map + 12), 1)
-- do a binary search of the segments
local endCount = index_map + 14 local endCount = index_map + 14
local search = endCount local search = endCount
@@ -733,10 +919,13 @@ function stbtt_FindGlyphIndex(info, unicode_codepoint)
return 0 return 0
end end
-- they lie from endCount .. endCount + segCount
-- but searchRange is the nearest power of two, so...
if unicode_codepoint >= ttUSHORT(data, search + rangeShift * 2) then if unicode_codepoint >= ttUSHORT(data, search + rangeShift * 2) then
search = search + rangeShift * 2 search = search + rangeShift * 2
end end
-- now decrement to bias correctly to find smallest
search = search - 2 search = search - 2
while entrySelector ~= 0 do while entrySelector ~= 0 do
searchRange = bit32.rshift(searchRange, 1) searchRange = bit32.rshift(searchRange, 1)
@@ -768,8 +957,9 @@ function stbtt_FindGlyphIndex(info, unicode_codepoint)
local ngroups = ttULONG(data, index_map + 12) local ngroups = ttULONG(data, index_map + 12)
local low = 0 local low = 0
local high = ngroups local high = ngroups
-- Binary search the right group.
while low < high do while low < high do
local mid = low + bit32.rshift(high - low, 1) local mid = low + bit32.rshift(high - low, 1) -- rounds down, so low <= mid < high
local start_char = ttULONG(data, index_map + 16 + mid * 12) local start_char = ttULONG(data, index_map + 16 + mid * 12)
local end_char = ttULONG(data, index_map + 16 + mid * 12 + 4) local end_char = ttULONG(data, index_map + 16 + mid * 12 + 4)
if unicode_codepoint < start_char then if unicode_codepoint < start_char then
@@ -785,10 +975,10 @@ function stbtt_FindGlyphIndex(info, unicode_codepoint)
end end
end end
end end
return 0 return 0 -- not found
end end
-- TODO: -- @TODO
STBTT_assert(false) STBTT_assert(false)
return 0 return 0
end end
@@ -810,8 +1000,8 @@ end
local function stbtt__GetGlyfOffset(info, glyph_index) local function stbtt__GetGlyfOffset(info, glyph_index)
STBTT_assert(info.cff.size == 0) STBTT_assert(info.cff.size == 0)
if glyph_index >= info.numGlyphs then return -1 end if glyph_index >= info.numGlyphs then return -1 end -- glyph index out of range
if info.indexToLocFormat >= 2 then return -1 end if info.indexToLocFormat >= 2 then return -1 end -- unknown index->glyph map format
local g1, g2 local g1, g2
if info.indexToLocFormat == 0 then if info.indexToLocFormat == 0 then
@@ -822,9 +1012,10 @@ local function stbtt__GetGlyfOffset(info, glyph_index)
g2 = info.glyf + ttULONG(info.data, info.loca + glyph_index * 4 + 4) g2 = info.glyf + ttULONG(info.data, info.loca + glyph_index * 4 + 4)
end end
if g1 == g2 then return -1 else return g1 end if g1 == g2 then return -1 else return g1 end -- if length is 0, return -1
end end
-- Gets the bounding box of the visible part of the glyph, in unscaled coordinates
--- @return integer, integer?, integer?, integer?, integer? --- @return integer, integer?, integer?, integer?, integer?
local function stbtt_GetGlyphBox(info, glyph_index) local function stbtt_GetGlyphBox(info, glyph_index)
local n, x0, y0, x1, y1 local n, x0, y0, x1, y1
@@ -896,7 +1087,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
local points = (endPtsOfContours + numberOfContours * 2 + 2 + ins) + 1 -- 1-based idx local points = (endPtsOfContours + numberOfContours * 2 + 2 + ins) + 1 -- 1-based idx
local n = 1 + ttUSHORT(data, endPtsOfContours + numberOfContours * 2 - 2) local n = 1 + ttUSHORT(data, endPtsOfContours + numberOfContours * 2 - 2)
local m = n + 2 * numberOfContours local m = n + 2 * numberOfContours -- a loose bound on how many vertices we might need
vertices = {} vertices = {}
for i = 1, m do vertices[i] = stbtt_vertex() end for i = 1, m do vertices[i] = stbtt_vertex() end
@@ -907,7 +1098,11 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
local next_move = 0 local next_move = 0
local flagcount = 0 local flagcount = 0
local off = m - n -- in first pass, we load uninterpreted data into the allocated array
-- above, shifted to the end of the array so we won't overwrite it when
-- we create our final data starting from the front
local off = m - n -- starting offset for uninterpreted data, regardless of how m ends up being calculated
-- first load flags -- first load flags
local flags = 0 local flags = 0
@@ -934,7 +1129,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
if bit32.band(flags, 2) ~= 0 then if bit32.band(flags, 2) ~= 0 then
local dx = data[points] local dx = data[points]
points = points + 1 points = points + 1
if bit32.band(flags, 16) ~= 0 then if bit32.band(flags, 16) ~= 0 then -- ???
x = x + dx x = x + dx
else else
x = x - dx x = x - dx
@@ -956,7 +1151,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
if bit32.band(flags, 4) ~= 0 then if bit32.band(flags, 4) ~= 0 then
local dy = data[points] local dy = data[points]
points = points + 1 points = points + 1
if bit32.band(flags, 32) ~= 0 then if bit32.band(flags, 32) ~= 0 then -- ???
y = y + dy y = y + dy
else else
y = y - dy y = y - dy
@@ -987,15 +1182,19 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
-- now start the new one -- now start the new one
start_off = (bit32.band(flags, 1) == 0) start_off = (bit32.band(flags, 1) == 0)
if start_off then if start_off then
-- if we start off with an off-curve point, then when we need to find a point on the curve
-- where we can start, and we need to save some state for when we wraparound.
scx = x scx = x
scy = y scy = y
if bit32.band(vertices[off + i + 1].type, 1) == 0 then if bit32.band(vertices[off + i + 1].type, 1) == 0 then
-- next point is also a curve point, so interpolate an on-point curve
sx = bit32.rshift(x + stbtt_int32(vertices[off + i + 1].x), 1) sx = bit32.rshift(x + stbtt_int32(vertices[off + i + 1].x), 1)
sy = bit32.rshift(y + stbtt_int32(vertices[off + i + 1].y), 1) sy = bit32.rshift(y + stbtt_int32(vertices[off + i + 1].y), 1)
else else
-- otherwise just use the next point as our start point
sx = stbtt_int32(vertices[off + i + 1].x) sx = stbtt_int32(vertices[off + i + 1].x)
sy = stbtt_int32(vertices[off + i + 1].y) sy = stbtt_int32(vertices[off + i + 1].y)
i = i + 1 i = i + 1 -- we're using point i+1 as the starting point, so skip it
end end
else else
sx = x sx = x
@@ -1007,8 +1206,8 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
next_move = 1 + ttUSHORT(data, endPtsOfContours + j * 2) next_move = 1 + ttUSHORT(data, endPtsOfContours + j * 2)
j = j + 1 j = j + 1
else else
if bit32.band(flags, 1) == 0 then if bit32.band(flags, 1) == 0 then -- if it's a curve
if was_off then if was_off then -- two off-curve control points in a row means interpolate an on-curve midpoint
stbtt_setvertex(vertices[num_vertices + 1], STBTT_VTX.CURVE, bit32.rshift(cx + x, 1), bit32.rshift(cy + y, 1), cx, cy) stbtt_setvertex(vertices[num_vertices + 1], STBTT_VTX.CURVE, bit32.rshift(cx + x, 1), bit32.rshift(cy + y, 1), cx, cy)
num_vertices = num_vertices + 1 num_vertices = num_vertices + 1
end end
@@ -1032,6 +1231,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx, sy, scx, scy, cx, cy) num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx, sy, scx, scy, cx, cy)
elseif numberOfContours < 0 then elseif numberOfContours < 0 then
-- Compound shapes.
local more = 1 local more = 1
local comp = g + 10 local comp = g + 10
num_vertices = 0 num_vertices = 0
@@ -1047,8 +1247,8 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
flags = ttSHORT(data, comp) comp = comp + 2 flags = ttSHORT(data, comp) comp = comp + 2
gidx = ttSHORT(data, comp) comp = comp + 2 gidx = ttSHORT(data, comp) comp = comp + 2
if bit32.band(flags, 2) ~= 0 then if bit32.band(flags, 2) ~= 0 then -- XY values
if bit32.band(flags, 1) ~= 0 then if bit32.band(flags, 1) ~= 0 then -- shorts
mtx[5] = ttSHORT(data, comp) comp = comp + 2 mtx[5] = ttSHORT(data, comp) comp = comp + 2
mtx[6] = ttSHORT(data, comp) comp = comp + 2 mtx[6] = ttSHORT(data, comp) comp = comp + 2
else else
@@ -1056,7 +1256,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
mtx[6] = ttCHAR(data, comp) comp = comp + 1 mtx[6] = ttCHAR(data, comp) comp = comp + 1
end end
else else
-- TODO: handle matching point -- @TODO handle matching point
STBTT_assert(false) STBTT_assert(false)
end end
@@ -1083,11 +1283,14 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
mtx[4] = ttSHORT(data, comp) / 16384.0 comp = comp + 2 mtx[4] = ttSHORT(data, comp) / 16384.0 comp = comp + 2
end end
-- Find transformation scales.
m = STBTT_sqrt(mtx[1] * mtx[1] + mtx[2] * mtx[2]) m = STBTT_sqrt(mtx[1] * mtx[1] + mtx[2] * mtx[2])
n = STBTT_sqrt(mtx[3] * mtx[3] + mtx[4] * mtx[4]) n = STBTT_sqrt(mtx[3] * mtx[3] + mtx[4] * mtx[4])
-- Get indexed glyph.
comp_num_vertices, comp_verts = stbtt_GetGlyphShape(info, gidx) comp_num_vertices, comp_verts = stbtt_GetGlyphShape(info, gidx)
if comp_num_vertices > 0 then if comp_num_vertices > 0 then
-- Transform vertices.
for i = 1, comp_num_vertices do for i = 1, comp_num_vertices do
local v = comp_verts[i] local v = comp_verts[i]
local x = v.x local x = v.x
@@ -1100,6 +1303,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
v.cy = stbtt_int16(n * (mtx[2] * x + mtx[4] * y + mtx[6])) v.cy = stbtt_int16(n * (mtx[2] * x + mtx[4] * y + mtx[6]))
end end
-- Append vertices.
if num_vertices == 0 then -- LUA: No malloc if num_vertices == 0 then -- LUA: No malloc
vertices = comp_verts vertices = comp_verts
else else
@@ -1111,6 +1315,7 @@ local function stbtt__GetGlyphShapeTT(info, glyph_index)
num_vertices = num_vertices + comp_num_vertices num_vertices = num_vertices + comp_num_vertices
end end
-- More components ?
more = bit32.band(flags, bit32.lshift(1, 5)) more = bit32.band(flags, bit32.lshift(1, 5))
end end
end end
@@ -1266,7 +1471,7 @@ local function stbtt__run_charstring(info, glyph_index, c)
clear_stack = 1 clear_stack = 1
b0 = stbtt__buf_get8(b) b0 = stbtt__buf_get8(b)
-- TODO: implement hinting -- @TODO implement hinting
if b0 == 0x13 or b0 == 0x14 then -- hintmask or cntrmask if b0 == 0x13 or b0 == 0x14 then -- hintmask or cntrmask
if in_header then if in_header then
maskbits = maskbits + trunc(sp / 2) -- implicit "vstem" maskbits = maskbits + trunc(sp / 2) -- implicit "vstem"
@@ -1415,6 +1620,8 @@ local function stbtt__run_charstring(info, glyph_index, c)
elseif b0 == 0x0C then -- two-byte escape elseif b0 == 0x0C then -- two-byte escape
local b1 = stbtt__buf_get8(b) local b1 = stbtt__buf_get8(b)
-- @TODO These "flex" implementations ignore the flex-depth and resolution,
-- and always draw beziers.
if b1 == 0x22 then -- hflex if b1 == 0x22 then -- hflex
if sp < 7 then return STBTT__CSERR("hflex stack") end if sp < 7 then return STBTT__CSERR("hflex stack") end
local dx1 = s[0] local dx1 = s[0]
@@ -1547,6 +1754,15 @@ function stbtt__GetGlyphInfoT2(info, glyph_index)
return ((r ~= 0) and c.num_vertices or 0), x0, y0, x1, y1 return ((r ~= 0) and c.num_vertices or 0), x0, y0, x1, y1
end end
-- returns # of vertices and fills *vertices with the pointer to them
-- these are expressed in "unscaled" coordinates
--
-- The shape is a series of contours. Each one starts with
-- a STBTT_moveto, then consists of a series of mixed
-- STBTT_lineto and STBTT_curveto segments. A lineto
-- draws a line from previous endpoint to its x,y; a curveto
-- draws a quadratic bezier from previous endpoint to
-- its x,y, using cx,cy as the bezier control point.
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param glyph_index int --- @param glyph_index int
function stbtt_GetGlyphShape(info, glyph_index) function stbtt_GetGlyphShape(info, glyph_index)
@@ -1557,6 +1773,9 @@ function stbtt_GetGlyphShape(info, glyph_index)
end end
end end
-- leftSideBearing is the offset from the current horizontal position to the left edge of the character
-- advanceWidth is the offset from the current horizontal position to the next horizontal position
-- these are expressed in unscaled coordinates
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param glyph_index int --- @param glyph_index int
function stbtt_GetGlyphHMetrics(info, glyph_index) function stbtt_GetGlyphHMetrics(info, glyph_index)
@@ -1574,6 +1793,12 @@ function stbtt_GetGlyphHMetrics(info, glyph_index)
return advanceWidth, leftSideBearing return advanceWidth, leftSideBearing
end end
-- ascent is the coordinate above the baseline the font extends; descent
-- is the coordinate below the baseline the font extends (i.e. it is typically negative)
-- lineGap is the spacing between one row's descent and the next row's ascent...
-- so you should advance the vertical position by "ascent - descent + lineGap"
-- these are expressed in unscaled coordinates, so you must multiply by
-- the scale factor for a given size
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
function stbtt_GetFontVMetrics(info) function stbtt_GetFontVMetrics(info)
local ascent = ttSHORT(info.data, info.hhea + 4) local ascent = ttSHORT(info.data, info.hhea + 4)
@@ -1583,6 +1808,12 @@ function stbtt_GetFontVMetrics(info)
return ascent, descent, lineGap return ascent, descent, lineGap
end end
-- computes a scale factor to produce a font whose "height" is 'pixels' tall.
-- Height is measured as the distance from the highest ascender to the lowest
-- descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics
-- and computing:
-- scale = pixels / (ascent - descent)
-- so if you prefer to measure height by the ascent only, use a similar calculation.
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param height float --- @param height float
function stbtt_ScaleForPixelHeight(info, height) function stbtt_ScaleForPixelHeight(info, height)
@@ -1593,10 +1824,12 @@ end
------------------------------------ ------------------------------------
------------------------------------ ------------------------------------
--- ---
--- Antialiasing software rasterizer --- antialiasing software rasterizer
--- ---
--- ---
-- same as stbtt_GetGlyphBitmapBox, but you can specify a subpixel
-- shift for the character
--- @param font stbtt_fontinfo --- @param font stbtt_fontinfo
--- @param glyph int --- @param glyph int
--- @param scale_x float --- @param scale_x float
@@ -1624,6 +1857,11 @@ function stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y, shift_x,
return ix0, iy0, ix1, iy1 return ix0, iy0, ix1, iy1
end end
-- get the bbox of the bitmap centered around the glyph origin; so the
-- bitmap width is ix1-ix0, height is iy1-iy0, and location to place
-- the bitmap top left is (leftSideBearing*scale,iy0).
-- (Note that the bitmap uses y-increases-down, but the shape uses
-- y-increases-up, so GlyphBitmapBox and GlyphBox are inverted.)
--- @param font stbtt_fontinfo --- @param font stbtt_fontinfo
--- @param glyph int --- @param glyph int
--- @param scale_x float --- @param scale_x float
@@ -1646,6 +1884,8 @@ end
--- stbtt_MakeGlyphBitmap --- stbtt_MakeGlyphBitmap
--- stbtt_GetGlyphBitmapSubpixel --- stbtt_GetGlyphBitmapSubpixel
-- the edge passed in here does not cross the vertical line at x or the vertical line at x+1
-- (i.e. it has already been clipped to those)
--- @param scanline float[] --- @param scanline float[]
--- @param x int --- @param x int
--- @param e stbtt__active_edge --- @param e stbtt__active_edge
@@ -1687,7 +1927,7 @@ local function stbtt__handle_clipped_edge(scanline, x, e, x0, y0, x1, y1)
-- do nothing -- do nothing
else else
STBTT_assert(x0 >= x and x0 <= x + 1 and x1 >= x and x1 <= x + 1) STBTT_assert(x0 >= x and x0 <= x + 1 and x1 >= x and x1 <= x + 1)
scanline[x] = scanline[x] + e.direction * (y1 - y0) * (1 - ((x0 - x) + (x1 - x)) / 2) scanline[x] = scanline[x] + e.direction * (y1 - y0) * (1 - ((x0 - x) + (x1 - x)) / 2) -- coverage = 1 - average x position
end end
end end
@@ -1725,6 +1965,7 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
while e do while e do
-- brute force every pixel -- brute force every pixel
-- compute intersection points with top & bottom -- compute intersection points with top & bottom
STBTT_assert(e.ey >= y_top) STBTT_assert(e.ey >= y_top)
@@ -1768,8 +2009,8 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
if x_top >= 0 and x_bottom >= 0 and x_top < len and x_bottom < len then if x_top >= 0 and x_bottom >= 0 and x_top < len and x_bottom < len then
-- from here on, we don't have to range check x values -- from here on, we don't have to range check x values
if trunc(x_top) == trunc(x_bottom) then if trunc(x_top) == trunc(x_bottom) then
-- simple case, only spans one pixel
local height local height
-- simple case, only spans one pixel
local x = trunc(x_top) local x = trunc(x_top)
height = (sy1 - sy0) * e.direction height = (sy1 - sy0) * e.direction
STBTT_assert(x >= 0 and x < len) STBTT_assert(x >= 0 and x < len)
@@ -1801,8 +2042,25 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
-- compute intersection with y axis at x2 -- compute intersection with y axis at x2
y_final = y_top + dy * (x2 - x0) y_final = y_top + dy * (x2 - x0)
-- x1 x_top x2 x_bottom
-- y_top +------|-----+------------+------------+--------|---+------------+
-- | | | | | |
-- | | | | | |
-- sy0 | Txxxxx|............|............|............|............|
-- y_crossing | *xxxxx.......|............|............|............|
-- | | xxxxx..|............|............|............|
-- | | /- xx*xxxx........|............|............|
-- | | dy < | xxxxxx..|............|............|
-- y_final | | \- | xx*xxx.........|............|
-- sy1 | | | | xxxxxB...|............|
-- | | | | | |
-- | | | | | |
-- y_bottom +------------+------------+------------+------------+------------+
--
-- goal is to measure the area covered by '.' in each pixel
-- if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 -- if x2 is right at the right edge of x1, y_crossing can blow up, github #1057
-- TODO: maybe test against sy1 rather than y_bottom? -- @TODO: maybe test against sy1 rather than y_bottom?
if y_crossing > y_bottom then if y_crossing > y_bottom then
y_crossing = y_bottom y_crossing = y_bottom
end end
@@ -1824,6 +2082,16 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
end end
end end
-- in second pixel, area covered by line segment found in first pixel
-- is always a rectangle 1 wide * the height of that line segment; this
-- is exactly what the variable 'area' stores. it also gets a contribution
-- from the line segment within it. the THIRD pixel will get the first
-- pixel's rectangle contribution, the second pixel's rectangle contribution,
-- and its own contribution. the 'own contribution' is the same in every pixel except
-- the leftmost and rightmost, a trapezoid that slides down in each pixel.
-- the second pixel's contribution to the third pixel will be the
-- rectangle 1 wide times the height change in the second pixel, which is dy.
step = sign * dy * 1 -- dy is dy/dx, change in y for every 1 change in x, step = sign * dy * 1 -- dy is dy/dx, change in y for every 1 change in x,
-- which multiplied by 1-pixel-width is how much pixel area changes for each step in x -- which multiplied by 1-pixel-width is how much pixel area changes for each step in x
-- so the area advances by 'step' every time -- so the area advances by 'step' every time
@@ -1843,7 +2111,27 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
scanline_fill[x2 + 1] = scanline_fill[x2 + 1] + sign * (sy1 - sy0) scanline_fill[x2 + 1] = scanline_fill[x2 + 1] + sign * (sy1 - sy0)
end end
else else
-- if edge goes outside of box we're drawing, we require
-- clipping logic. since this does not match the intended use
-- of this library, we use a different, very slow brute
-- force implementation
-- note though that this does happen some of the time because
-- x_top and x_bottom can be extrapolated at the top & bottom of
-- the shape and actually lie outside the bounding box
for x = 0, len - 1 do for x = 0, len - 1 do
-- cases:
--
-- there can be up to two intersections with the pixel. any intersection
-- with left or right edges can be handled by splitting into two (or three)
-- regions. intersections with top & bottom do not necessitate case-wise logic.
--
-- the old way of doing this found the intersections with the left & right edges,
-- then used some simple logic to produce up to three segments in sorted order
-- from top-to-bottom. however, this had a problem: if an x edge was epsilon
-- across the x border, then the corresponding y position might not be distinct
-- from the other y segment, and it might ignored as an empty segment. to avoid
-- that, we need to explicitly produce segments based on x positions.
-- rename variables to clearly-defined pairs -- rename variables to clearly-defined pairs
local y0 = y_top local y0 = y_top
local x1 = x local x1 = x
@@ -1887,6 +2175,7 @@ local function stbtt__fill_active_edges_new(scanline, scanline_fill, len, e, y_t
end end
end end
-- directly AA rasterize edges w/o supersampling
--- @param result stbtt__bitmap --- @param result stbtt__bitmap
--- @param e stbtt__edge --- @param e stbtt__edge
--- @param n int --- @param n int
@@ -1914,28 +2203,33 @@ local function stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, of
for i = 0, result.w - 1 do scanline[i] = 0 end for i = 0, result.w - 1 do scanline[i] = 0 end
for i = 0, result.w do scanline2[i] = 0 end for i = 0, result.w do scanline2[i] = 0 end
-- update all active edges;
-- remove all active edges that terminate before the top of this scanline
local prev = nil local prev = nil
local curr = active local curr = active
while curr do while curr do
local next = curr.next local next = curr.next
if curr.ey <= scan_y_top then if curr.ey <= scan_y_top then
if prev then prev.next = next if prev then prev.next = next -- delete from list
else active = next end else active = next end
curr.direction = 0 curr.direction = 0
else else
prev = curr prev = curr -- advance through list
end end
curr = next curr = next
end end
-- insert all edges that start before the bottom of this scanline
local edge = e[p] local edge = e[p]
while edge.y0 <= scan_y_bottom do while edge.y0 <= scan_y_bottom do
if edge.y0 ~= edge.y1 then if edge.y0 ~= edge.y1 then
local z = stbtt__new_active(edge, off_x, scan_y_top) local z = stbtt__new_active(edge, off_x, scan_y_top)
if j == 0 and off_y ~= 0 and z.ey < scan_y_top then if j == 0 and off_y ~= 0 and z.ey < scan_y_top then
-- this can happen due to subpixel positioning and some kind of fp rounding error i think
z.ey = scan_y_top z.ey = scan_y_top
end end
STBTT_assert(z.ey >= scan_y_top) STBTT_assert(z.ey >= scan_y_top) -- if we get really unlucky a tiny bit of an edge can be out of bounds
-- insert at front
z.next = active z.next = active
active = z active = z
end end
@@ -1943,6 +2237,7 @@ local function stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, of
edge = e[p] edge = e[p]
end end
-- now process all active edges
if active then if active then
stbtt__fill_active_edges_new(scanline, scanline2, result.w, active, scan_y_top) stbtt__fill_active_edges_new(scanline, scanline2, result.w, active, scan_y_top)
end end
@@ -1961,10 +2256,11 @@ local function stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, of
end end
end end
-- advance all the edges
step = active step = active
while step do while step do
step.fx = step.fx + step.fdx step.fx = step.fx + step.fdx -- advance to position for current scanline
step = step.next step = step.next -- advance through list
end end
y = y + 1 y = y + 1
@@ -1999,6 +2295,7 @@ end
local function stbtt__rasterize(result, pts, wcount, windings, scale_x, scale_y, shift_x, shift_y, off_x, off_y, invert) local function stbtt__rasterize(result, pts, wcount, windings, scale_x, scale_y, shift_x, shift_y, off_x, off_y, invert)
local y_scale_inv = (invert) and -scale_y or scale_y local y_scale_inv = (invert) and -scale_y or scale_y
local vsubsample = 1 -- STBTT_RASTERIZER_VERSION == 2 local vsubsample = 1 -- STBTT_RASTERIZER_VERSION == 2
-- vsubsample should divide 255 evenly; otherwise we won't reach full opacity
-- now we have to blow out the windings into explicit edge lists -- now we have to blow out the windings into explicit edge lists
local n = 0 local n = 0
@@ -2066,8 +2363,7 @@ local function stbtt__add_point(points, n, x, y)
points[n + 1].y = y points[n + 1].y = y
end end
-- tessellate until threshold p is happy... -- tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching
-- TODO: warped to compensate for non-linear stretching
--- @param points? stbtt__point[] --- @param points? stbtt__point[]
--- @param num_points int --- @param num_points int
--- @param x0 float --- @param x0 float
@@ -2116,7 +2412,7 @@ end
--- @param n int --- @param n int
--- @return int num_points # updated num_points passed in --- @return int num_points # updated num_points passed in
local function stbtt__tesselate_cubic(points, num_points, x0, y0, x1, y1, x2, y2, x3, y3, objspace_flatness_squared, n) local function stbtt__tesselate_cubic(points, num_points, x0, y0, x1, y1, x2, y2, x3, y3, objspace_flatness_squared, n)
-- TODO: this "flatness" calculation is just made-up nonsense that seems to work well enough -- @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough
local dx0 = x1 - x0 local dx0 = x1 - x0
local dy0 = y1 - y0 local dy0 = y1 - y0
local dx1 = x2 - x1 local dx1 = x2 - x1
@@ -2159,6 +2455,7 @@ local function stbtt__tesselate_cubic(points, num_points, x0, y0, x1, y1, x2, y2
return num_points return num_points
end end
-- returns number of contours
--- @param vertices stbtt_vertex[] --- @param vertices stbtt_vertex[]
--- @param num_verts int --- @param num_verts int
--- @param objspace_flatness float --- @param objspace_flatness float
@@ -2235,6 +2532,7 @@ local function stbtt_FlattenCurves(vertices, num_verts, objspace_flatness)
return points, num_contours, contour_lengths return points, num_contours, contour_lengths
end end
-- rasterize a shape with quadratic beziers into a bitmap
--- @param result stbtt__bitmap --- @param result stbtt__bitmap
--- @param flatness_in_pixels float --- @param flatness_in_pixels float
--- @param vertices stbtt_vertex[] --- @param vertices stbtt_vertex[]
@@ -2258,6 +2556,11 @@ local function stbtt_Rasterize(result, flatness_in_pixels, vertices, num_verts,
end end
end end
-- renders the glyph into storage you pass in for the bitmap,
-- in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap
-- is clipped to out_w/out_h bytes. Call stbtt_GetGlyphBitmapBox to get the
-- width and height and positioning info for it first. You can also specify
-- a subpixel shift for the character.
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param output unsigned_char[] --- @param output unsigned_char[]
--- @param out_w int --- @param out_w int
@@ -2286,7 +2589,7 @@ end
----------------- -----------------
----------------- -----------------
--- ---
--- Bitmap baking --- bitmap baking
--- ---
--- ---
@@ -2298,7 +2601,7 @@ end
--------------------------- ---------------------------
--------------------------- ---------------------------
--- ---
--- Bitmap baking / packing --- bitmap baking / packing
--- ---
--- ---
@@ -2380,9 +2683,16 @@ local function stbtt__oversample_shift(oversample)
if oversample == 0 then if oversample == 0 then
return 0.0 return 0.0
end end
-- The prefilter is a box filter of width "oversample",
-- which shifts phase by (oversample - 1)/2 pixels in
-- oversampled space. We want to shift in the opposite
-- direction to counter this.
return -(oversample - 1) / (2.0 * oversample) return -(oversample - 1) / (2.0 * oversample)
end end
-- same as stbtt_MakeGlyphBitmapSubpixel, but prefiltering
-- is performed. Returns (sub_x, sub_y).
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param output unsigned_char[] --- @param output unsigned_char[]
--- @param out_w int --- @param out_w int
@@ -2415,7 +2725,7 @@ end
------------------- -------------------
------------------- -------------------
--- ---
--- SDF computation --- sdf computation
--- ---
--- ---
@@ -2441,10 +2751,20 @@ end
--- stbtt__matchpair --- stbtt__matchpair
--- stbtt__matches --- stbtt__matches
-- Each .ttf/.ttc file may have more than one font. Each font has a sequential
-- index number starting from 0. Call this function to get the font offset for
-- a given index; it returns -1 if the index is out of range. A regular .ttf
-- file will only define one font and it always be at offset 0, so it will
-- return '0' for index 0, and -1 for all other indices.
--- @param data table --- @param data table
--- @param index int --- @param index int
function stbtt_GetFontOffsetForIndex(data, index) return stbtt_GetFontOffsetForIndex_internal(data, index) end function stbtt_GetFontOffsetForIndex(data, index) return stbtt_GetFontOffsetForIndex_internal(data, index) end
-- Given an offset into the file that defines a font, this function builds
-- the necessary cached info for the rest of the system. You must allocate
-- the stbtt_fontinfo yourself (see stbtt_fontinfo()), and stbtt_InitFont will fill it out. You don't
-- need to do anything special to free it, because the contents are pure
-- value data with no additional data structures. Returns false on failure.
--- @param info stbtt_fontinfo --- @param info stbtt_fontinfo
--- @param data table --- @param data table
--- @param offset int --- @param offset int
+5
View File
@@ -6,6 +6,7 @@
# Why: ModuleScripts don't share globals, and the setfenv() alternative turns off Luau's fast paths / native codegen. # Why: ModuleScripts don't share globals, and the setfenv() alternative turns off Luau's fast paths / native codegen.
# Each source file becomes a function in __FILES; IM_INCLUDE(name) runs it once and caches the result. # Each source file becomes a function in __FILES; IM_INCLUDE(name) runs it once and caches the result.
# The module returns a table with every global the library defines (ImGui, ImVec2, ImGuiConfigFlags, ImGui_ImplRoblox, ...). # The module returns a table with every global the library defines (ImGui, ImVec2, ImGuiConfigFlags, ImGui_ImplRoblox, ...).
# Roblox target also writes ImGuiTyped.luau (types + require(script.Impl), see tools/gen_types.py).
import os, re import os, re
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -64,4 +65,8 @@ NAME = "imgui_love.lua" if LOVE else "ImGui.luau"
open(os.path.join(OUT, NAME), "w").write("\n".join(out) + "\n") open(os.path.join(OUT, NAME), "w").write("\n".join(out) + "\n")
import json import json
json.dump(linemap, open(os.path.join(OUT, NAME.rsplit(".", 1)[0] + ".linemap.json"), "w")) json.dump(linemap, open(os.path.join(OUT, NAME.rsplit(".", 1)[0] + ".linemap.json"), "w"))
if not LOVE:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from gen_types import wrapper
open(os.path.join(OUT, "ImGuiTyped.luau"), "w").write(wrapper(FILES))
print("wrote build/%s (%d files, %d exports)" % (NAME, len(FILES), len(names))) print("wrote build/%s (%d files, %d exports)" % (NAME, len(FILES), len(names)))
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
# Typed entry module for the Roblox build: bundle.py writes build/ImGuiTyped.luau = Luau types + `require(script.Impl)`,
# where Impl is the untouched build/ImGui.luau (the bundle itself is too large for Luau's type inference to finish,
# so the types live in this small wrapper instead). Library sources and the LÖVE build are not affected.
# Public ImGui.* signatures come from upstream imgui.h (namespace ImGui), mapped onto the port's own parameter lists.
# Anything ambiguous (overloads, char* buffers, arrays, multiple out-params, count mismatch) falls back to `any`,
# so the types never reject code that runs. Enum tables (ImGuiWindowFlags, ImGuiCol, ...) get their exact keys.
# Self-check: python3 tools/gen_types.py (prints a few signatures)
import os, re
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
NUM = re.compile(r"^(int|float|double|unsigned int|unsigned short|short|size_t|ImU32|ImS32|ImU64|ImS64|ImWchar|ImGuiID|Im\w*Flags|ImGui(Col|Cond|StyleVar|Key|MouseButton|MouseCursor|Dir|DataType|SortDirection|TableBgTarget|Axis))$")
VEC = {"ImVec2": "ImVec2", "ImVec4": "ImVec4"}
def lua_type(ctype):
t = re.sub(r"\bconst\b|&", "", ctype).strip()
t = re.sub(r"\s+", " ", t)
if t == "char*": return "string" if "const" in ctype else None
if t == "bool": return "boolean"
if NUM.match(t): return "number"
return VEC.get(t)
def split_params(s):
out, depth, cur = [], 0, ""
for ch in s:
if ch in "([": depth += 1
if ch in ")]": depth -= 1
if ch == "," and depth == 0: out.append(cur); cur = ""
else: cur += ch
if cur.strip(): out.append(cur)
return [p.strip() for p in out]
def upstream_decls():
src = open(os.path.join(ROOT, "tools", "test", "upstream", "imgui.h")).read()
body = src[src.index("namespace ImGui\n"):src.index("} // namespace ImGui")]
decls = {}
for m in re.finditer(r"IMGUI_API\s+([\w\s\*&]+?)\s*\b(\w+)\((.*?)\)\s*(?:IM_FMT\w+\(\d+\))?\s*;", body):
decls.setdefault(m.group(2), []).append((m.group(1).strip(), split_params(m.group(3)) if m.group(3).strip() not in ("", "void") else []))
return decls
LOOSE = "(...any) -> ...any"
PORT_ONLY = { # port additions that aren't in imgui.h
"SetNextWindowCached": "(key: any) -> ()",
"NewTextBuffer": "(str: string, size: number?) -> {number}",
"TextBufferToString": "(buf: {number}) -> string",
}
def signature(port_params, ret, cparams):
if any(p == "..." for p in cparams):
if port_params and port_params[-1] == "...": port_params = port_params[:-1]
cparams = [p for p in cparams if p != "..."]; vararg = True
else: vararg = False
if len(port_params) != len(cparams): return LOOSE
args, outs = [], []
for name, cp in zip(port_params, cparams):
decl, _, default = cp.partition("=")
m = re.match(r"(.*?)\b(\w+)\s*(\[\w*\])?\s*$", decl.strip())
if not m: return LOOSE
ctype, array = m.group(1).strip(), m.group(3)
if "(*" in cp: return LOOSE
ptr = ctype.endswith("*") and not re.search(r"\bchar\s*\*$", ctype)
if array: t = None; outs.append("array")
elif ptr and re.search(r"\bconst\b", ctype): t = None # input array (e.g. ColorPicker4 ref_col)
elif ptr:
t = lua_type(ctype[:-1])
if t in ("boolean", "number"): outs.append(t)
else: t = None; outs.append("other")
else: t = lua_type(ctype)
if name == "...": args.append("...any"); continue
opt = "?" if (default.strip() or (ptr and t == "boolean")) else ""
args.append("%s: %s%s" % (name, t or "any", opt if t else ""))
if vararg: args.append("...any")
r = re.sub(r"\bconst\b|&|IMGUI_API", "", ret).strip()
if r == "void": rt = "...any" if outs else "()"
elif r == "bool":
scal = [o for o in outs if o != "array"]
if not scal: rt = "boolean"
elif len(scal) == 1 and scal[0] == "boolean": rt = "(boolean, boolean?)"
elif len(scal) == 1 and scal[0] == "number": rt = "(number, boolean)"
else: rt = "...any"
else:
rt = lua_type(r) or "any"
if outs: rt = "...any"
return "(%s) -> %s" % (", ".join(args), rt)
def port_functions(files):
fns = {}
for src in files.values():
for m in re.finditer(r"^function ImGui\.(\w+)\((.*?)\)", src, re.M):
fns[m.group(1)] = [p.strip() for p in m.group(2).split(",") if p.strip()]
return fns
def enums(files):
src = files.get("imgui_h.lua", "")
out = {}
for m in re.finditer(r"^(Im\w+) = \{\n(.*?)^\}", src, re.M | re.S):
keys = re.findall(r"^\s+(\w+)\s*=", m.group(2), re.M)
if keys: out[m.group(1)] = keys
for f in files.values(): # keys added later (ImGuiKey letters in loops are covered by the indexer below)
for name, key in re.findall(r"^\s*(Im\w+)\.(\w+)\s*=", f, re.M):
if name in out and key not in out[name]: out[name].append(key)
return out
def types_header(files):
decls, fns = upstream_decls(), port_functions(files)
lines = ["export type ImVec2 = { x: number, y: number, [any]: any }",
"export type ImVec4 = { x: number, y: number, z: number, w: number, [any]: any }",
"export type ImGuiAPI = {"]
for name in sorted(fns):
d = decls.get(name)
sig = PORT_ONLY.get(name) or (signature(fns[name], *d[0]) if d and len(d) == 1 else LOOSE)
lines.append(" %s: %s," % (name, sig))
lines += [" [string]: any,", "}"]
for name, keys in sorted(enums(files).items()):
lines.append("export type %s = { %s, [string]: number }" % (name, ", ".join("%s: number" % k for k in keys)))
return lines + module_type(set(enums(files)))
def wrapper(files):
return "\n".join(["--!strict", "-- GENERATED by tools/gen_types.py - do not edit. The library is the child ModuleScript 'Impl'.", ""]
+ types_header(files) + ["", "return (require(script:WaitForChild(\"Impl\")) :: any) :: ImGuiModule", ""])
def module_type(enum_names):
# the bundle is too large for Luau to infer, so its return value is cast to this explicit type
lines = ["export type ImGuiModule = {", " ImGui: ImGuiAPI,"]
lines += [" %s: (x: number?, y: number?, z: number?, w: number?) -> %s," % (n, n) for n in ("ImVec2", "ImVec4")]
lines += [" %s: %s," % (n, n) for n in sorted(enum_names)]
return lines + [" [string]: any,", "}"]
if __name__ == "__main__":
import glob
files = {os.path.basename(f): open(f).read() for f in glob.glob(os.path.join(ROOT, "*.lua"))}
lines = types_header(files)
for l in lines:
if re.match(r" (Begin|Checkbox|SliderFloat|SliderFloat3|Text|Button|Combo|GetCursorPos|InputText|DragFloatRange2):", l): print(l)
loose = sum(LOOSE in l for l in lines); print("%d functions, %d loose" % (sum(l.startswith(" ") for l in lines), loose))
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
# Minifies a Lua/Luau file: drops comments, indentation, blank lines and spaces that don't separate tokens.
# Kept: strings (verbatim), "--!" directive lines (--!optimize, --!native, ...), @native attributes, and one newline
# wherever the source had one (so no ambiguous-call issues, and error line numbers stay roughly meaningful).
# python3 tools/minify.py IN OUT # write minified IN to OUT
# python3 tools/minify.py --same-code F.. # check F.. have the same code tokens as `git show HEAD:F` (comment-only edits)
# python3 tools/minify.py --same-code-as OLD NEW # same check between two files
# python3 tools/minify.py --test # self-check
import re, subprocess, sys
from functools import lru_cache
TOKEN = re.compile(r"""
(?P<ws>[ \t\r\n\f\v]+)
| (?P<lcom>--\[(?P<ceq>=*)\[.*?\](?P=ceq)\])
| (?P<com>--[^\n]*)
| (?P<lstr>\[(?P<seq>=*)\[.*?\](?P=seq)\])
| (?P<str>"(?:\\.|\\\n|[^"\\\n])*"|'(?:\\.|\\\n|[^'\\\n])*'|`(?:\\.|\\\n|[^`\\])*`)
| (?P<num>0[xX][0-9a-fA-F_]*(?:\.[0-9a-fA-F_]*)?(?:[pP][+-]?\d+)?[uUlLi]*|0[bB][01_]+|(?:\d[\d_]*(?:\.[\d_]*)?|\.\d[\d_]*)(?:[eE][+-]?\d+)?[uUlLi]*)
| (?P<name>@?[A-Za-z_]\w*)
| (?P<op>\.\.\.|\.\.=|//=|\.\.|==|~=|<=|>=|//|::|->|[-+*/%^]=|.)
""", re.S | re.X)
def tokenize(src):
"""[(kind, text, newline_before)]; comments/whitespace folded into newline_before ("--!" lines kept as 'dir')."""
out, nl, pos = [], False, 0
while pos < len(src):
m = TOKEN.match(src, pos)
kind, text = m.lastgroup, m.group(0)
if kind in ("ceq", "seq"): kind = "lcom" if text.startswith("--") else "lstr"
pos = m.end()
if kind == "ws" or kind == "lcom" or (kind == "com" and not text.startswith("--!")):
nl = nl or "\n" in text
continue
if kind == "com": kind = "dir"
if kind == "lstr" or kind == "str": kind = "str"
out.append((kind, text, nl))
nl = False
return out
@lru_cache(maxsize=None)
def glue_ok(a, b):
"""True if `a..b` with no space still tokenizes as exactly a, b."""
if re.match(r"\w", a[-1]) and re.match(r"\w", b[0]): return False # real lexers read `0then`/`x1` as one token
return [t for _, t, _ in tokenize(a + b)] == [a, b]
def minify(src):
toks, out = tokenize(src), []
prev = None
for kind, text, nl in toks:
if prev is not None:
if nl or prev[0] == "dir" or kind == "dir": out.append("\n")
elif kind == "str" or prev[0] == "str": # strings are self-delimiting; only `[` + `[[` would fuse
if kind == "str" and text[0] == "[" and prev[1].endswith("["): out.append(" ")
elif not glue_ok(prev[1], text):
out.append(" ")
out.append(text)
prev = (kind, text)
return "".join(out) + "\n"
def code(src):
return [(k, t) for k, t, _ in tokenize(src)]
def test():
cases = {
"local a = 1 -- c\nlocal b = a - -1\n": "local a=1\nlocal b=a- -1\n",
"x = 1 .. 2": "x=1 ..2\n",
"x = t[ [[s]] ]": "x=t[ [[s]]]\n",
"x = t[ [==[s]==] ]": "x=t[ [==[s]==]]\n",
"--!optimize 2\n--[[ long\ncomment ]] local s = 'a -- b' .. \"c\\\"d\"": "--!optimize 2\nlocal s='a -- b'..\"c\\\"d\"\n",
"@native\nfunction f(...) return ... end": "@native\nfunction f(...)return...end\n",
"a = b == c and d ~= e or f <= g": "a=b==c and d~=e or f<=g\n",
"x = 0x10 + 1e-5 + .5": "x=0x10+1e-5+.5\n",
"f(a)\n\n\n(g)(b)": "f(a)\n(g)(b)\n",
"s = [[a\n-- not a comment\n]]": "s=[[a\n-- not a comment\n]]\n",
"x = a / / b": "x=a/ /b\n",
"x = a . . b": "x=a. .b\n",
"if a == 0 then return 1 end": "if a==0 then return 1 end\n",
"x = 1 .. y": "x=1 ..y\n",
}
for src, want in cases.items():
got = minify(src)
assert got == want, (src, got, want)
assert code(minify(src)) == code(src), src
print("minify OK")
if __name__ == "__main__":
if sys.argv[1] == "--test":
test()
elif sys.argv[1] in ("--same-code", "--same-code-as"):
bad = 0
pairs = [(sys.argv[2], sys.argv[3])] if sys.argv[1] == "--same-code-as" else [(None, f) for f in sys.argv[2:]]
for ref, f in pairs:
old = open(ref).read() if ref else subprocess.run(["git", "show", "HEAD:" + f], capture_output=True, text=True, check=True).stdout
a, b = code(old), code(open(f).read())
if a != b:
bad += 1
i = next((i for i, (x, y) in enumerate(zip(a, b)) if x != y), min(len(a), len(b)))
print("%s: CODE CHANGED at token %d: %r -> %r" % (f, i, a[i:i + 6], b[i:i + 6]))
else:
print("%s: same code" % f)
sys.exit(1 if bad else 0)
else:
src = open(sys.argv[1], encoding="utf-8").read()
dst = minify(src)
assert code(dst) == code(src), "minify changed code tokens"
open(sys.argv[2], "w", encoding="utf-8").write(dst)
print("wrote %s (%d -> %d bytes, %.0f%%)" % (sys.argv[2], len(src.encode()), len(dst.encode()), 100.0 * len(dst) / len(src)))
+2
View File
@@ -16,6 +16,8 @@ G = "game, workspace, Instance, Enum, Vector2, Vector3, UDim2, Color3, Content,
def build_run(args, flags): def build_run(args, flags):
subprocess.run([sys.executable, os.path.join(ROOT, "tools", "bundle.py"), TMP] + (["--no-demo"] if os.environ.get("NO_DEMO") else []), check=True, stdout=subprocess.DEVNULL) subprocess.run([sys.executable, os.path.join(ROOT, "tools", "bundle.py"), TMP] + (["--no-demo"] if os.environ.get("NO_DEMO") else []), check=True, stdout=subprocess.DEVNULL)
if os.environ.get("MINIFY"): # test the minified bundle instead (error line numbers won't map back to sources)
subprocess.run([sys.executable, os.path.join(ROOT, "tools", "minify.py"), os.path.join(TMP, "ImGui.luau"), os.path.join(TMP, "ImGui.luau")], check=True, stdout=subprocess.DEVNULL)
out = ["--" if args.roblox else "--!native", "--!optimize 2", flags, "local SOURCE_FNS = {}"] out = ["--" if args.roblox else "--!native", "--!optimize 2", flags, "local SOURCE_FNS = {}"]
def add(key, path): def add(key, path):
s = "\n".join(l for l in open(path).read().split("\n") if not l.startswith("--!")) s = "\n".join(l for l in open(path).read().split("\n") if not l.startswith("--!"))
+58
View File
@@ -0,0 +1,58 @@
-- SetNextWindowCached: the same scripted scenario runs twice, window "Win##A" uncached then "Win##B" cached.
-- Every frame the window's draw list must hash identically in both phases, while B's body runs far less often.
-- run: python3 tools/test/run.py --main tools/test/scripts/t_cached_window.luau --frames 130 --quiet --roblox
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext()
G.ImGui_ImplRoblox.Init()
G.ImGui_ImplRoblox.SetRenderRate(0)
local io = ImGui.GetIO()
local P = 60 -- frames per phase
local frame, hashes, runs = 0, {}, { A = 0, B = 0 }
local function hash(dl)
local h = 0
local function mix(v) h = (h * 31 + math.floor(v * 64 + 0.5)) % 2147483647 end
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
return h
end
game:GetService("RunService").Stepped:Connect(function()
frame += 1
if frame > 2 * P then return end
local phase = frame <= P and "A" or "B"
local t = (frame - 1) % P + 1 -- frame within the phase
G.ImGui_ImplRoblox.NewFrame()
io.DeltaTime = 1 / 60
-- mouse: away, over the button (hover highlight), away again
if t >= 20 and t < 26 then io:AddMousePosEvent(60, 75) else io:AddMousePosEvent(700, 500) end
local key = math.floor(t / 15) -- data version: changes at t = 15, 30, 45
ImGui.NewFrame()
if t == 3 then ImGui.SetWindowFocus(nil) end -- focused -> unfocused title bar must not stay stale
if t == 1 then ImGui.SetNextWindowFocus() end
ImGui.SetNextWindowPos(ImVec2(20, 20), G.ImGuiCond.Always)
ImGui.SetNextWindowSize(ImVec2(220, 140), G.ImGuiCond.Always)
if phase == "B" then ImGui.SetNextWindowCached(key) end
local _, visible = ImGui.Begin("Win##" .. phase)
if visible then
runs[phase] += 1
ImGui.Button("Button")
ImGui.Text("value %d", key)
ImGui.SliderFloat("slider", key * 0.1, 0, 1)
end
ImGui.End()
ImGui.Render()
local h = hash(ImGui.FindWindowByName("Win##" .. phase).DrawList)
if phase == "A" then hashes[t] = h
elseif h ~= hashes[t] then error(("frame %d: cached draw list differs from uncached"):format(t)) end
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if frame == 2 * P then
print(("runs: uncached %d, cached %d"):format(runs.A, runs.B))
assert(runs.A == P, "uncached window must run every frame")
assert(runs.B < P / 3, "cached window ran too often")
print("CACHED OK")
end
end)
+68
View File
@@ -0,0 +1,68 @@
-- Docked windows (floating split node) stop being submitted: the host lingers one frame, its bodies must be filled
-- with the windows' bg colour (node.LastBgColor), not white, and everything must be gone within a few frames.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2
ImGui.CreateContext()
local io = ImGui.GetIO()
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.DockingEnable)
G.ImGui_ImplRoblox.Init()
local frame, closed_at, done = 0, 31, false
RunService.Stepped:Connect(function()
if done then return end
frame += 1
G.ImGui_ImplRoblox.NewFrame()
ImGui.NewFrame()
if frame == 2 then
local id = ImGui.GetID("floating")
ImGui.DockBuilderRemoveNode(id)
ImGui.DockBuilderAddNode(id, 0)
ImGui.DockBuilderSetNodePos(id, ImVec2(100, 100))
ImGui.DockBuilderSetNodeSize(id, ImVec2(600, 400))
local left, right = ImGui.DockBuilderSplitNode(id, G.ImGuiDir.Left, 0.5)
ImGui.DockBuilderDockWindow("A", left)
ImGui.DockBuilderDockWindow("B", right)
ImGui.DockBuilderFinish(id)
end
if frame < closed_at then
for _, n in { "A", "B" } do
if ImGui.Begin(n) then ImGui.Text("window " .. n) end
ImGui.End()
end
end
if frame == closed_at - 1 then
local a, b = ImGui.FindWindowByName("A"), ImGui.FindWindowByName("B")
assert(a.DockNode and b.DockNode and a.DockNode ~= b.DockNode and a.DockNode.HostWindow, "A, B docked side by side")
end
ImGui.Render()
local dd = ImGui.GetDrawData()
if frame >= closed_at then
for _, dl in dd.CmdLists:iter() do
-- white vertices spanning a big area = a white body fill (white text glyphs are small)
local vb, x0, y0, x1, y1 = dl.VtxBuffer.Buf, math.huge, math.huge, -math.huge, -math.huge
for _, pcmd in dl.CmdBuffer:iter() do
for i = pcmd.IdxOffset + 1, pcmd.IdxOffset + pcmd.ElemCount do
local o = (pcmd.VtxOffset + dl.IdxBuffer.Data[i] - 1) * G.VTX_STRIDE
if buffer.readu32(vb, o + 32) == 0xFFFFFFFF then
local x, y = buffer.readf64(vb, o), buffer.readf64(vb, o + 8)
x0, y0, x1, y1 = math.min(x0, x), math.min(y0, y), math.max(x1, x), math.max(y1, y)
end
end
end
assert(not (x1 - x0 > 100 and y1 - y0 > 100), "white fill on frame " .. frame)
end
if dd.TotalVtxCount == 0 then
G.ImGui_ImplRoblox.RenderDrawData(dd)
for _, set in io.BackendPlatformUserData.Layers do
for kind = 1, 3 do assert(not (set[kind] and set[kind].Visible), "backend layer still visible after close") end
end
print("DOCK CLOSE OK after", frame - closed_at + 1, "frames")
done = true
return
else
assert(frame < closed_at + 3, "docked windows still drawn " .. (frame - closed_at + 1) .. " frames after close")
end
end
G.ImGui_ImplRoblox.RenderDrawData(dd)
end)
+57
View File
@@ -0,0 +1,57 @@
-- Regressions vs upstream: clipboard paste filter (InputTextEx), ImParseFormatTrimDecorations (Ctrl+click temp input), ShrinkWidths tie order
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
-- ShrinkWidths: equal widths sort by Index descending (upstream ShrinkWidthItemComparer), so the rounding remainder goes to the highest Index first
local items = {}
for i = 1, 4 do local it = G.ImGuiShrinkWidthItem(); it.Index = i - 1; it.Width = 100; it.InitialWidth = 100; items[i] = it end
ImGui.ShrinkWidths(items, 1, 4, 2.0, 10.0)
local w = {}
for _, it in items do w[it.Index] = it.Width end
check(w[3] == 100 and w[2] == 100 and w[1] == 99 and w[0] == 99, ("ShrinkWidths widths %s %s %s %s"):format(w[0], w[1], w[2], w[3]))
local function edit_text()
local st = ImGui.GetCurrentContext().InputTextState
return G.ImGui._ByteArrayToString(st.TextA.Data, 1, st.TextLen + 1)
end
local buf = ImGui.NewTextBuffer("", 64)
local f, sf, di = 0, 45.0, 50
local rects = {}
local function rect() local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); return { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end -- copies: GetItemRectMin() returns the live LastItemData.Rect.Min
local function click(name, ctrl)
local r = rects[name]
io:AddMousePosEvent((r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2)
if ctrl then io:AddKeyEvent(G.ImGuiMod_Ctrl, true) end
io:AddMouseButtonEvent(0, true)
end
local function release() io:AddMouseButtonEvent(0, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
local function key(k) io:AddKeyEvent(k, true); io:AddKeyEvent(k, false) end
game:GetService("RunService").Stepped:Connect(function()
f += 1
G.ImGui_ImplRoblox.NewFrame()
if f == 3 then click("t") elseif f == 4 then release() end
-- clipboard: tab is filtered out (single line, no AllowTabInput), é is 2 bytes
if f == 6 then ImGui.SetClipboardText("ab\tc\u{e9}"); io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.V, true) end
if f == 7 then io:AddKeyEvent(G.ImGuiKey.V, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
if f == 9 then key(G.ImGuiKey.Escape) end
if f == 11 then click("s", true) elseif f == 12 then release() end
if f == 14 then key(G.ImGuiKey.Escape) end
if f == 16 then click("d", true) elseif f == 17 then release() end
ImGui.NewFrame()
ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(300, 200))
ImGui.Begin("T")
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()
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
end)
+13 -1
View File
@@ -124,12 +124,24 @@ local function MyTool()
end end
-- [/snippet] -- [/snippet]
-- [snippet:cached]
local stats = { version = 0, kills = 0 }
local function StatsWindow()
ImGui.SetNextWindowCached(stats.version) -- bump stats.version whenever stats change
local _, visible = ImGui.Begin("Stats")
if visible then -- false while the cached contents are reused
ImGui.Text("Kills: %d", stats.kills)
end
ImGui.End()
end
-- [/snippet]
local frame = 0 local frame = 0
RunService.Stepped:Connect(function() RunService.Stepped:Connect(function()
frame += 1 frame += 1
G.ImGui_ImplRoblox.NewFrame() G.ImGui_ImplRoblox.NewFrame()
ImGui.NewFrame() ImGui.NewFrame()
MyFirstTool(); MyTool(); Conventions(); MenusAndTable(); Plot(); CustomDrawing() MyFirstTool(); MyTool(); Conventions(); MenusAndTable(); Plot(); CustomDrawing(); StatsWindow()
ImGui.Render() ImGui.Render()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData()) G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if frame == 20 then if frame == 20 then
+1 -1
View File
@@ -2,7 +2,7 @@
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui")) local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2 = G.ImGui, G.ImVec2 local ImGui, ImVec2 = G.ImGui, G.ImVec2
local UIS = game:GetService("UserInputService") local UIS = game:GetService("UserInputService")
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init() ImGui.CreateContext(); G.ImGui_ImplRoblox.Init(); G.ImGui_ImplRoblox.SetTextProxy(true)
local io = ImGui.GetIO() local io = ImGui.GetIO()
local buf = ImGui.NewTextBuffer("", 64) local buf = ImGui.NewTextBuffer("", 64)
local proxy local proxy
+10
View File
@@ -0,0 +1,10 @@
[package]
name = "4dbug/imgui-luau"
version = "0.1.4"
registry = "https://github.com/UpliftGames/wally-index"
realm = "shared"
license = "MIT"
description = "Dear ImGui (docking branch) ported to Luau for Roblox"
repository = "https://github.com/4DBug/imgui-luau"
[dependencies]