Compare commits
32
Commits
2558c30d32
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1696c799f | ||
|
|
447230e688 | ||
|
|
a1aa5cbb2d | ||
|
|
dad1a0c0f1 | ||
|
|
b71e62b4d9 | ||
|
|
92dbd9a580 | ||
|
|
e3840a938a | ||
|
|
9be05aeb5c | ||
|
|
f2c694bac5 | ||
|
|
93a3c4faf5 | ||
|
|
9872d43fd6 | ||
|
|
af2026113a | ||
|
|
a111187591 | ||
|
|
1e63710e33 | ||
|
|
d18bc714fc | ||
|
|
049e160852 | ||
|
|
33ab5a1d84 | ||
|
|
9c8c1b018e | ||
|
|
b929de5206 | ||
|
|
ac4398ee2a | ||
|
|
ff86ea5aa2 | ||
|
|
4ac57cb571 | ||
|
|
15dc766d82 | ||
|
|
ed53d0683e | ||
|
|
b71e29ee35 | ||
|
|
bcf383a079 | ||
|
|
ce82582e37 | ||
|
|
090525076f | ||
|
|
95fdf94912 | ||
|
|
e502866075 | ||
|
|
1431d2e19d | ||
|
|
7eb7d5fcbb |
+5
-6
@@ -116,7 +116,7 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
|
|||||||
|
|
||||||
### Getting Started & Integration (Roblox)
|
### Getting Started & Integration (Roblox)
|
||||||
|
|
||||||
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/imgui-luau@0.1.2"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source:
|
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/imgui-luau@0.3.0"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source:
|
||||||
|
|
||||||
1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped.
|
1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped.
|
||||||
2. Sync with [Rojo](https://rojo.space): [`default.project.json`](/default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`.
|
2. Sync with [Rojo](https://rojo.space): [`default.project.json`](/default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`.
|
||||||
@@ -151,7 +151,8 @@ Backend options:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). |
|
| `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). |
|
||||||
| `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. |
|
| `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. |
|
||||||
| `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default 60, `0` = every frame). Your code still runs every frame. |
|
| `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default `60`; `0` = every frame). Your code still runs every frame. |
|
||||||
|
| `ImGui_ImplRoblox.SetRedrawBudget(ms)` | Max milliseconds per frame spent redrawing changed tiles (opt-in; default `0` = no limit, every change is drawn the same frame). With a budget, tiles over it keep their old pixels for a few frames, so big changes are spread over frames instead of one long frame (parts of the UI may update a frame or two apart). |
|
||||||
| `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). |
|
| `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). |
|
||||||
| `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. |
|
| `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. |
|
||||||
| `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. |
|
| `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. |
|
||||||
@@ -178,7 +179,7 @@ The cost is CPU time in Luau, so the backend is built around **not drawing**:
|
|||||||
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
|
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
|
||||||
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
|
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
|
||||||
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
|
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
|
||||||
6. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
|
6. **Redraw budget.** Changed tiles are redrawn in the frame they change. Optionally, `SetRedrawBudget(ms)` caps the redraw time per frame; tiles over it keep their old pixels and follow on the next frames. Redraws are capped at 60 per second by default (`SetRenderRate(hz)`, `0` = uncapped), optionally lower while busy with `SetBusyRenderRate(hz)`. Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
|
||||||
|
|
||||||
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
|
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
|
||||||
|
|
||||||
@@ -204,12 +205,10 @@ love examples/example_love
|
|||||||
|
|
||||||
Roblox stays the primary target: the Roblox build is untouched by this (no shims, `@native` kept); the LÖVE build strips `@native` and adds small `bit32`/`buffer`/`utf8`/`table` shims (the COMPAT section of [backends/imgui_impl_love.lua](/backends/imgui_impl_love.lua)). `tools/test/love/run.sh` checks that LuaJIT produces exactly the same draw data as Luau.
|
Roblox stays the primary target: the Roblox build is untouched by this (no shims, `@native` kept); the LÖVE build strips `@native` and adds small `bit32`/`buffer`/`utf8`/`table` shims (the COMPAT section of [backends/imgui_impl_love.lua](/backends/imgui_impl_love.lua)). `tools/test/love/run.sh` checks that LuaJIT produces exactly the same draw data as Luau.
|
||||||
|
|
||||||
[examples/example_shared/app.lua](/examples/example_shared/app.lua) is one UI that runs unchanged on both: `roblox.client.luau` (Rojo: `examples/example_shared/default.project.json`) and `main.lua` (`love examples/example_shared`) are the only platform code. Write shared UI code in plain Lua 5.1 syntax (no `+=`, `//`, `continue`).
|
|
||||||
|
|
||||||
#### Prebuilt releases
|
#### Prebuilt releases
|
||||||
|
|
||||||
Every push to `master` publishes a rolling `latest` release, and every `v*` tag a versioned release ([.github/workflows/release.yml](/.github/workflows/release.yml)), with:
|
Every push to `master` publishes a rolling `latest` release, and every `v*` tag a versioned release ([.github/workflows/release.yml](/.github/workflows/release.yml)), with:
|
||||||
`ImGui.luau` / `ImGui-nodemo.luau` (Roblox ModuleScript), `imgui_love.lua` / `imgui_love-nodemo.lua` (LÖVE), and `imgui-luau-roblox-*.zip` / `imgui-luau-love-*.zip` (bundle + examples).
|
`ImGui.luau` (Roblox ModuleScript), `imgui_love.lua` (LÖVE), their minified `.min` variants, and `imgui-luau-roblox-*.zip` / `imgui-luau-love-*.zip` (bundle + examples). For a library-only build without the demo, build it yourself with `--no-demo`.
|
||||||
|
|
||||||
### Differences from C++ Dear ImGui
|
### Differences from C++ Dear ImGui
|
||||||
|
|
||||||
|
|||||||
@@ -30,27 +30,39 @@ jobs:
|
|||||||
unzip -q luau.zip -d luau && chmod +x luau/* && echo "$PWD/luau" >> "$GITHUB_PATH"
|
unzip -q luau.zip -d luau && chmod +x luau/* && echo "$PWD/luau" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
|
env:
|
||||||
|
IMGUI_BUNDLE_STRICT: "1" # fail if enum folding (needs the luau CLI above) can't run
|
||||||
run: |
|
run: |
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
python3 tools/bundle.py dist/roblox # ImGui.luau (library + demo)
|
python3 tools/bundle.py dist/roblox # ImGui.luau (library + demo)
|
||||||
python3 tools/bundle.py --no-demo dist/roblox-nodemo # ImGui.luau (library only)
|
|
||||||
python3 tools/bundle.py --target love dist/love # imgui_love.lua (library + demo)
|
python3 tools/bundle.py --target love dist/love # imgui_love.lua (library + demo)
|
||||||
python3 tools/bundle.py --target love --no-demo dist/love-nodemo
|
# minified variants (comments/whitespace stripped, same code): tools/minify.py
|
||||||
|
python3 tools/minify.py dist/roblox/ImGui.luau dist/ImGui.min.luau
|
||||||
|
python3 tools/minify.py dist/love/imgui_love.lua dist/imgui_love.min.lua
|
||||||
|
|
||||||
- name: Check
|
- name: Check
|
||||||
run: |
|
run: |
|
||||||
python3 tools/sync_github_readme.py --check
|
python3 tools/sync_github_readme.py --check
|
||||||
luau-compile --null dist/roblox/ImGui.luau
|
luau-compile --null dist/roblox/ImGui.luau
|
||||||
luau-compile --null dist/roblox-nodemo/ImGui.luau
|
|
||||||
sed 's|require(script:WaitForChild("Impl"))|nil|' dist/roblox/ImGuiTyped.luau > /tmp/ImGuiTyped.luau && luau-analyze /tmp/ImGuiTyped.luau
|
sed 's|require(script:WaitForChild("Impl"))|nil|' dist/roblox/ImGuiTyped.luau > /tmp/ImGuiTyped.luau && luau-analyze /tmp/ImGuiTyped.luau
|
||||||
luajit -bl dist/love/imgui_love.lua > /dev/null
|
luajit -bl dist/love/imgui_love.lua > /dev/null
|
||||||
luajit -bl dist/love-nodemo/imgui_love.lua > /dev/null
|
python3 tools/minify.py --test
|
||||||
|
luau-compile --null dist/ImGui.min.luau
|
||||||
|
luajit -bl dist/imgui_love.min.lua > /dev/null
|
||||||
python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/r.txt
|
python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/r.txt
|
||||||
grep -q "README OK" /tmp/r.txt
|
grep -q "README OK" /tmp/r.txt
|
||||||
python3 tools/test/run.py --main tools/test/scripts/t_cached_window.luau --frames 130 --quiet --roblox --timeout 300 | tee /tmp/c.txt
|
python3 tools/test/run.py --main tools/test/scripts/t_cached_window.luau --frames 130 --quiet --roblox --timeout 300 | tee /tmp/c.txt
|
||||||
grep -q "CACHED OK" /tmp/c.txt
|
grep -q "CACHED OK" /tmp/c.txt
|
||||||
|
MINIFY=1 python3 tools/test/run.py --main tools/test/scripts/t_readme.luau --frames 30 --quiet --roblox --timeout 300 | tee /tmp/rm.txt
|
||||||
|
grep -q "README OK" /tmp/rm.txt
|
||||||
|
python3 tools/test/run.py --main tools/test/scripts/t_fixes.luau --frames 55 --quiet --roblox --timeout 300 | tee /tmp/f.txt
|
||||||
|
grep -q "FIXES OK" /tmp/f.txt
|
||||||
python3 tools/bundle.py --target love >/dev/null
|
python3 tools/bundle.py --target love >/dev/null
|
||||||
luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic
|
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
|
||||||
@@ -58,16 +70,14 @@ jobs:
|
|||||||
V="${GITHUB_REF_NAME}"
|
V="${GITHUB_REF_NAME}"
|
||||||
cd dist
|
cd dist
|
||||||
cp roblox/ImGui.luau ImGui.luau
|
cp roblox/ImGui.luau ImGui.luau
|
||||||
cp roblox-nodemo/ImGui.luau ImGui-nodemo.luau
|
|
||||||
cp love/imgui_love.lua imgui_love.lua
|
cp love/imgui_love.lua imgui_love.lua
|
||||||
cp love-nodemo/imgui_love.lua imgui_love-nodemo.lua
|
|
||||||
# Roblox: module + example LocalScript + Rojo project
|
# Roblox: module + example LocalScript + Rojo project
|
||||||
mkdir -p pkg-roblox/build && cp ImGui.luau roblox/ImGuiTyped.luau pkg-roblox/build/ && cp ../default.project.json pkg-roblox/
|
mkdir -p pkg-roblox/build && cp ImGui.luau roblox/ImGuiTyped.luau pkg-roblox/build/ && cp ../default.project.json pkg-roblox/
|
||||||
mkdir -p pkg-roblox/examples && cp -r ../examples/example_roblox ../examples/example_shared pkg-roblox/examples/
|
mkdir -p pkg-roblox/examples && cp -r ../examples/example_roblox pkg-roblox/examples/
|
||||||
(cd pkg-roblox && zip -qr ../imgui-luau-roblox-$V.zip .)
|
(cd pkg-roblox && zip -qr ../imgui-luau-roblox-$V.zip .)
|
||||||
# LÖVE: bundle + fonts + examples (run: love examples/example_love)
|
# LÖVE: bundle + fonts + examples (run: love examples/example_love)
|
||||||
mkdir -p pkg-love/build && cp imgui_love.lua pkg-love/build/ && cp -r ../fonts pkg-love/ && rm -f pkg-love/fonts/*.lua
|
mkdir -p pkg-love/build && cp imgui_love.lua pkg-love/build/ && cp -r ../fonts pkg-love/ && rm -f pkg-love/fonts/*.lua
|
||||||
mkdir -p pkg-love/examples && cp -r ../examples/example_love ../examples/example_shared pkg-love/examples/
|
mkdir -p pkg-love/examples && cp -r ../examples/example_love pkg-love/examples/
|
||||||
(cd pkg-love && zip -qr ../imgui-luau-love-$V.zip .)
|
(cd pkg-love && zip -qr ../imgui-luau-love-$V.zip .)
|
||||||
ls -la
|
ls -la
|
||||||
|
|
||||||
@@ -77,9 +87,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
dist/ImGui.luau
|
dist/ImGui.luau
|
||||||
dist/ImGui-nodemo.luau
|
|
||||||
dist/imgui_love.lua
|
dist/imgui_love.lua
|
||||||
dist/imgui_love-nodemo.lua
|
dist/*.min.luau
|
||||||
|
dist/*.min.lua
|
||||||
dist/*.zip
|
dist/*.zip
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
|
||||||
@@ -105,4 +115,4 @@ jobs:
|
|||||||
gh release delete latest --yes --cleanup-tag || true
|
gh release delete latest --yes --cleanup-tag || true
|
||||||
gh release create latest --latest --title "Latest build (${GITHUB_SHA::7})" \
|
gh release create latest --latest --title "Latest build (${GITHUB_SHA::7})" \
|
||||||
--notes "Automatic build of ${GITHUB_SHA} on ${GITHUB_REF_NAME}. Tag a commit v* for a versioned release." \
|
--notes "Automatic build of ${GITHUB_SHA} on ${GITHUB_REF_NAME}. Tag a commit v* for a versioned release." \
|
||||||
dist/ImGui.luau dist/ImGui-nodemo.luau dist/imgui_love.lua dist/imgui_love-nodemo.lua dist/*.zip
|
dist/ImGui.luau dist/imgui_love.lua dist/*.min.luau dist/*.min.lua dist/*.zip
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
|
|||||||
|
|
||||||
### Getting Started & Integration (Roblox)
|
### Getting Started & Integration (Roblox)
|
||||||
|
|
||||||
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/imgui-luau@0.1.2"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source:
|
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/imgui-luau@0.3.0"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source:
|
||||||
|
|
||||||
1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped.
|
1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out) and `build/ImGuiTyped.luau`, a small typed entry module (Luau types for the public `ImGui.*` API, `ImVec2`/`ImVec4` and the flag enums) that requires `build/ImGui.luau` as its child `Impl`. The Wally package and the Rojo projects use that layout, so you get autocomplete and type errors in `--!strict` scripts. The single-file `ImGui.luau` also works on its own, just untyped.
|
||||||
2. Sync with [Rojo](https://rojo.space): [`default.project.json`](default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`.
|
2. Sync with [Rojo](https://rojo.space): [`default.project.json`](default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`.
|
||||||
@@ -148,7 +148,8 @@ Backend options:
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). |
|
| `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). |
|
||||||
| `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. |
|
| `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. |
|
||||||
| `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default 60, `0` = every frame). Your code still runs every frame. |
|
| `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default `60`; `0` = every frame). Your code still runs every frame. |
|
||||||
|
| `ImGui_ImplRoblox.SetRedrawBudget(ms)` | Max milliseconds per frame spent redrawing changed tiles (opt-in; default `0` = no limit, every change is drawn the same frame). With a budget, tiles over it keep their old pixels for a few frames, so big changes are spread over frames instead of one long frame (parts of the UI may update a frame or two apart). |
|
||||||
| `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). |
|
| `ImGui_ImplRoblox.SetBusyRenderRate(hz)` | Optional lower redraw rate while scrolling, resizing or moving windows (default off). |
|
||||||
| `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. |
|
| `ImGui_ImplRoblox.SetProfiling(true)` | MicroProfiler (Ctrl+F6) labels for every backend stage. |
|
||||||
| `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. |
|
| `ImGui_ImplRoblox.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. |
|
||||||
@@ -175,7 +176,7 @@ The cost is CPU time in Luau, so the backend is built around **not drawing**:
|
|||||||
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
|
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
|
||||||
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
|
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
|
||||||
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
|
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
|
||||||
6. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
|
6. **Redraw budget.** Changed tiles are redrawn in the frame they change. Optionally, `SetRedrawBudget(ms)` caps the redraw time per frame; tiles over it keep their old pixels and follow on the next frames. Redraws are capped at 60 per second by default (`SetRenderRate(hz)`, `0` = uncapped), optionally lower while busy with `SetBusyRenderRate(hz)`. Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
|
||||||
|
|
||||||
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
|
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
|
||||||
|
|
||||||
@@ -201,12 +202,10 @@ love examples/example_love
|
|||||||
|
|
||||||
Roblox stays the primary target: the Roblox build is untouched by this (no shims, `@native` kept); the LÖVE build strips `@native` and adds small `bit32`/`buffer`/`utf8`/`table` shims (the COMPAT section of [backends/imgui_impl_love.lua](backends/imgui_impl_love.lua)). `tools/test/love/run.sh` checks that LuaJIT produces exactly the same draw data as Luau.
|
Roblox stays the primary target: the Roblox build is untouched by this (no shims, `@native` kept); the LÖVE build strips `@native` and adds small `bit32`/`buffer`/`utf8`/`table` shims (the COMPAT section of [backends/imgui_impl_love.lua](backends/imgui_impl_love.lua)). `tools/test/love/run.sh` checks that LuaJIT produces exactly the same draw data as Luau.
|
||||||
|
|
||||||
[examples/example_shared/app.lua](examples/example_shared/app.lua) is one UI that runs unchanged on both: `roblox.client.luau` (Rojo: `examples/example_shared/default.project.json`) and `main.lua` (`love examples/example_shared`) are the only platform code. Write shared UI code in plain Lua 5.1 syntax (no `+=`, `//`, `continue`).
|
|
||||||
|
|
||||||
#### Prebuilt releases
|
#### Prebuilt releases
|
||||||
|
|
||||||
Every push to `master` publishes a rolling `latest` release, and every `v*` tag a versioned release ([.github/workflows/release.yml](.github/workflows/release.yml)), with:
|
Every push to `master` publishes a rolling `latest` release, and every `v*` tag a versioned release ([.github/workflows/release.yml](.github/workflows/release.yml)), with:
|
||||||
`ImGui.luau` / `ImGui-nodemo.luau` (Roblox ModuleScript), `imgui_love.lua` / `imgui_love-nodemo.lua` (LÖVE), and `imgui-luau-roblox-*.zip` / `imgui-luau-love-*.zip` (bundle + examples).
|
`ImGui.luau` (Roblox ModuleScript), `imgui_love.lua` (LÖVE), their minified `.min` variants, and `imgui-luau-roblox-*.zip` / `imgui-luau-love-*.zip` (bundle + examples). For a library-only build without the demo, build it yourself with `--no-demo`.
|
||||||
|
|
||||||
### Differences from C++ Dear ImGui
|
### Differences from C++ Dear ImGui
|
||||||
|
|
||||||
|
|||||||
@@ -431,6 +431,8 @@ function ImGui_ImplLOVE_Init(opts)
|
|||||||
local io = ImGui.GetIO()
|
local io = ImGui.GetIO()
|
||||||
local bd = { Time = love.timer.getTime(), Root = opts.root or "", Textures = {}, TextureCount = 0, Meshes = {}, FrameNo = 0 }
|
local bd = { Time = love.timer.getTime(), Root = opts.root or "", Textures = {}, TextureCount = 0, Meshes = {}, FrameNo = 0 }
|
||||||
io.BackendPlatformUserData = bd
|
io.BackendPlatformUserData = bd
|
||||||
|
io.BackendPlatformName = "imgui_impl_love"
|
||||||
|
io.BackendRendererName = "imgui_impl_love"
|
||||||
io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset, ImGuiBackendFlags.HasMouseCursors)
|
io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset, ImGuiBackendFlags.HasMouseCursors)
|
||||||
|
|
||||||
local platform_io = ImGui.GetPlatformIO()
|
local platform_io = ImGui.GetPlatformIO()
|
||||||
@@ -449,6 +451,8 @@ function ImGui_ImplLOVE_Shutdown()
|
|||||||
for _, m in pairs(bd.Meshes) do m.Mesh:release() end
|
for _, m in pairs(bd.Meshes) do m.Mesh:release() end
|
||||||
for _, t in pairs(bd.Textures) do t:release() end
|
for _, t in pairs(bd.Textures) do t:release() end
|
||||||
io.BackendPlatformUserData = nil
|
io.BackendPlatformUserData = nil
|
||||||
|
io.BackendPlatformName = nil
|
||||||
|
io.BackendRendererName = nil
|
||||||
io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor(ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset, ImGuiBackendFlags.HasMouseCursors)))
|
io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor(ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset, ImGuiBackendFlags.HasMouseCursors)))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+374
-62
@@ -142,6 +142,7 @@ end
|
|||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
|
|
||||||
-- Copies a region of ImTextureData pixels (1-based byte table) into our RGBA8 buffer
|
-- Copies a region of ImTextureData pixels (1-based byte table) into our RGBA8 buffer
|
||||||
|
@native
|
||||||
local function CopyTexturePixels(tex, dst, x, y, w, h)
|
local function CopyTexturePixels(tex, dst, x, y, w, h)
|
||||||
local bpp, tex_w = tex.BytesPerPixel, tex.Width
|
local bpp, tex_w = tex.BytesPerPixel, tex.Width
|
||||||
for row = 0, h - 1 do
|
for row = 0, h - 1 do
|
||||||
@@ -208,6 +209,8 @@ end
|
|||||||
-- Blending is non-premultiplied "over" with a transparent (alpha 0) background, so the game stays visible behind ImGui.
|
-- Blending is non-premultiplied "over" with a transparent (alpha 0) background, so the game stays visible behind ImGui.
|
||||||
|
|
||||||
local buf, buf_w -- buffer/width of the tile being drawn
|
local buf, buf_w -- buffer/width of the tile being drawn
|
||||||
|
local rect_cols = table.create(256, 0) -- scratch: texel column offsets for DrawRect
|
||||||
|
local sub_bufs, n_sub_bufs = {}, 0 -- scratch: partial tile upload buffers by size
|
||||||
local TILE_SIZE -- Vector2, set below
|
local TILE_SIZE -- Vector2, set below
|
||||||
|
|
||||||
-- Blends one pixel (r, g, b, a are 0..255). One u32 read/write, one division in the general case.
|
-- Blends one pixel (r, g, b, a are 0..255). One u32 read/write, one division in the general case.
|
||||||
@@ -337,18 +340,30 @@ local function DrawRect(x0, y0, x1, y1, u0, v0, u1, v1, col, t, cx0, cy0, cx1, c
|
|||||||
local du, dv = (u1 - u0) / (x1 - x0), (v1 - v0) / (y1 - y0)
|
local du, dv = (u1 - u0) / (x1 - x0), (v1 - v0) / (y1 - y0)
|
||||||
local tw, th, tbuf = t.Width, t.Height, t.Buffer
|
local tw, th, tbuf = t.Width, t.Height, t.Buffer
|
||||||
local r255, g255, b255, a255 = r / 255, g / 255, b / 255, a / 255
|
local r255, g255, b255, a255 = r / 255, g / 255, b / 255, a / 255
|
||||||
|
-- The texel column only depends on x: compute it once per column instead of once per pixel
|
||||||
|
local ncol = px1 - px0
|
||||||
|
local cols = rect_cols
|
||||||
|
for c = 1, ncol do
|
||||||
|
local tx = floor((u0 + (px0 + c - 1 + 0.5 - x0) * du) * tw)
|
||||||
|
if tx < 0 then tx = 0 elseif tx >= tw then tx = tw - 1 end
|
||||||
|
cols[c] = tx * 4
|
||||||
|
end
|
||||||
for y = py0, py1 - 1 do
|
for y = py0, py1 - 1 do
|
||||||
local ty = floor((v0 + (y + 0.5 - y0) * dv) * th)
|
local ty = floor((v0 + (y + 0.5 - y0) * dv) * th)
|
||||||
if ty < 0 then ty = 0 elseif ty >= th then ty = th - 1 end
|
if ty < 0 then ty = 0 elseif ty >= th then ty = th - 1 end
|
||||||
local trow = ty * tw
|
local trow4 = ty * tw * 4
|
||||||
local o = (y * buf_w + px0) * 4
|
local o = (y * buf_w + px0) * 4
|
||||||
for x = px0, px1 - 1 do
|
for c = 1, ncol do
|
||||||
local tx = floor((u0 + (x + 0.5 - x0) * du) * tw)
|
local to = trow4 + cols[c]
|
||||||
if tx < 0 then tx = 0 elseif tx >= tw then tx = tw - 1 end
|
|
||||||
local to = (trow + tx) * 4
|
|
||||||
local ta = readu8(tbuf, to + 3)
|
local ta = readu8(tbuf, to + 3)
|
||||||
if ta > 0 then
|
if ta > 0 then
|
||||||
BlendPixel(o, readu8(tbuf, to) * r255, readu8(tbuf, to + 1) * g255, readu8(tbuf, to + 2) * b255, ta * a255)
|
local sa = ta * a255
|
||||||
|
local sr, sg, sb = readu8(tbuf, to) * r255, readu8(tbuf, to + 1) * g255, readu8(tbuf, to + 2) * b255
|
||||||
|
if sa >= 255 then -- (BlendPixel's opaque case, inlined: most glyph texels)
|
||||||
|
writeu32(buf, o, floor(sr + 0.5) + floor(sg + 0.5) * 256 + floor(sb + 0.5) * 65536 + 4278190080)
|
||||||
|
else
|
||||||
|
BlendPixel(o, sr, sg, sb, sa)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
o = o + 4
|
o = o + 4
|
||||||
end
|
end
|
||||||
@@ -442,6 +457,7 @@ local P1, P2 = 2147483647, 2147483629 -- two independent 31 bit hashes per tile
|
|||||||
local KEY_W = 64 -- tile key = ty * KEY_W + tx (layers up to 16384 px wide)
|
local KEY_W = 64 -- tile key = ty * KEY_W + tx (layers up to 16384 px wide)
|
||||||
local CLIP_PAD = 2048 -- content up to this far off screen is kept, so dragging a window past the edge doesn't redraw it
|
local CLIP_PAD = 2048 -- content up to this far off screen is kept, so dragging a window past the edge doesn't redraw it
|
||||||
local LAYER_KEEP_FRAMES = 120 -- unused layers are destroyed after this many rendered frames
|
local LAYER_KEEP_FRAMES = 120 -- unused layers are destroyed after this many rendered frames
|
||||||
|
local MAX_DEFER_FRAMES = 8 -- a tile is redrawn at the latest this many frames after it changed, even over the redraw budget
|
||||||
|
|
||||||
-- Primitive arrays (reused between frames, no per-frame allocation)
|
-- Primitive arrays (reused between frames, no per-frame allocation)
|
||||||
local pr_a, pr_b, pr_c, pr_d, pr_t, pr_g = {}, {}, {}, {}, {}, {} -- vertices (pr_d == false -> triangle), texture, gradient kind
|
local pr_a, pr_b, pr_c, pr_d, pr_t, pr_g = {}, {}, {}, {}, {}, {} -- vertices (pr_d == false -> triangle), texture, gradient kind
|
||||||
@@ -451,6 +467,7 @@ local pr_tex = {} -- texture id
|
|||||||
local pr_vb = {} -- vertex buffer (draw list VtxBuffer.Buf)
|
local pr_vb = {} -- vertex buffer (draw list VtxBuffer.Buf)
|
||||||
local pr_k = {} -- layer kind (1..3)
|
local pr_k = {} -- layer kind (1..3)
|
||||||
local plist1, plist2, plist3 = {}, {}, {} -- primitive indices per layer kind
|
local plist1, plist2, plist3 = {}, {}, {} -- primitive indices per layer kind
|
||||||
|
local cm_free, cm_kind, cm_x0, cm_y0, cm_x1, cm_y1 = {}, {}, {}, {}, {}, {} -- per draw command (collect -> Snapshot)
|
||||||
|
|
||||||
@native
|
@native
|
||||||
local function HashVertex(h1, h2, vbuf, v, ox, oy)
|
local function HashVertex(h1, h2, vbuf, v, ox, oy)
|
||||||
@@ -540,7 +557,7 @@ local function GetLayer(bd, draw_list, kind)
|
|||||||
else
|
else
|
||||||
frame = NewFrame(bd.Gui, false); root = frame
|
frame = NewFrame(bd.Gui, false); root = frame
|
||||||
end
|
end
|
||||||
layer = { Root = root, Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, TX = {}, TY = {}, Touched = {}, NTouched = 0,
|
layer = { Root = root, Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, TL1 = {}, TL2 = {}, TLF = {}, TX = {}, TY = {}, Touched = {}, NTouched = 0,
|
||||||
Visible = false, X = false, Y = false, Z = -1, CX0 = false, CY0 = false, CX1 = false, CY1 = false }
|
Visible = false, X = false, Y = false, Z = -1, CX0 = false, CY0 = false, CX1 = false, CY1 = false }
|
||||||
set[kind] = layer
|
set[kind] = layer
|
||||||
return layer
|
return layer
|
||||||
@@ -567,7 +584,8 @@ local function GetTile(bd, layer, key, tx, ty)
|
|||||||
label.Visible = false
|
label.Visible = false
|
||||||
label.Active = bd.TilesActive
|
label.Active = bd.TilesActive
|
||||||
label.Parent = layer.Frame
|
label.Parent = layer.Frame
|
||||||
tile = { Image = img, Label = label, Buffer = buffer.create(TILE * TILE * 4), H1 = -1, H2 = -1, Visible = false, BX0 = TILE, BY0 = TILE, BX1 = 0, BY1 = 0 }
|
tile = { Image = img, Label = label, Buffer = buffer.create(TILE * TILE * 4), H1 = -1, H2 = -1, Visible = false, BX0 = TILE, BY0 = TILE, BX1 = 0, BY1 = 0,
|
||||||
|
ON = -1, OH1 = {}, OH2 = {}, OF = {}, OB0 = {}, OB1 = {}, OB2 = {}, OB3 = {} } -- last drawn primitive list (hashes, bounds in tile)
|
||||||
layer.Tiles[key] = tile
|
layer.Tiles[key] = tile
|
||||||
return tile
|
return tile
|
||||||
end
|
end
|
||||||
@@ -587,31 +605,129 @@ local function DestroyTiles(bd)
|
|||||||
bd.Layers = {}
|
bd.Layers = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Exact signature of everything in a draw list that affects its layers (nil = can't skip: user callbacks)
|
-- Snapshot of everything in a draw list that affects its layers, compared exactly with the next frame's list:
|
||||||
local sig_buf = buffer.create(4096)
|
-- 1 = identical (only restack), 2 = everything moved by the same whole pixels (dx, dy) (only move the layers: a window
|
||||||
|
-- being dragged), 0 = changed (full path). Compared in place (no strings built): early exit at the first difference.
|
||||||
|
local writef64 = buffer.writef64
|
||||||
|
local CMD_STRIDE = 70 -- clip (4 f64), ElemCount, IdxOffset, VtxOffset (u32), tex id (f64), kind (u8), free (u8), bounds of its primitives (4 x i32)
|
||||||
|
|
||||||
@native
|
@native
|
||||||
local function DrawListSignature(draw_list, ox, oy)
|
local function Snapshot(set, draw_list, ox, oy, sw, sh)
|
||||||
|
local nv = draw_list.VtxBuffer.Size * VTX_STRIDE
|
||||||
local idx, ni = draw_list.IdxBuffer.Data, draw_list.IdxBuffer.Size
|
local idx, ni = draw_list.IdxBuffer.Data, draw_list.IdxBuffer.Size
|
||||||
local ncmd = draw_list.CmdBuffer.Size
|
local ncmd = draw_list.CmdBuffer.Size
|
||||||
local need = ni * 4 + ncmd * 64 + 64
|
local sv, si, sc = set.SV, set.SI, set.SC
|
||||||
if buffer.len(sig_buf) < need then sig_buf = buffer.create(need * 2) end
|
if not sv or buffer.len(sv) < nv then sv = buffer.create(max(nv, 64) * 2); set.SV = sv end
|
||||||
local b, o = sig_buf, 0
|
if not si or buffer.len(si) < ni * 4 then si = buffer.create(max(ni, 16) * 8); set.SI = si end
|
||||||
for i = 1, ni do writeu32(b, o, idx[i]); o += 4 end
|
if not sc or buffer.len(sc) < ncmd * CMD_STRIDE then sc = buffer.create(max(ncmd, 4) * CMD_STRIDE * 2); set.SC = sc end
|
||||||
|
buffer.copy(sv, 0, draw_list.VtxBuffer.Buf, 0, nv)
|
||||||
|
for i = 1, ni do writeu32(si, (i - 1) * 4, idx[i]) end
|
||||||
|
local o, ci = 0, 0
|
||||||
for _, pcmd in draw_list.CmdBuffer:iter() do
|
for _, pcmd in draw_list.CmdBuffer:iter() do
|
||||||
if pcmd.UserCallback ~= nil then return nil end
|
if pcmd.UserCallback ~= nil then set.SnapOK = false; return end
|
||||||
local c = pcmd.ClipRect
|
local c = pcmd.ClipRect
|
||||||
buffer.writef64(b, o, c.x); buffer.writef64(b, o + 8, c.y); buffer.writef64(b, o + 16, c.z); buffer.writef64(b, o + 24, c.w)
|
writef64(sc, o, c.x); writef64(sc, o + 8, c.y); writef64(sc, o + 16, c.z); writef64(sc, o + 24, c.w)
|
||||||
writeu32(b, o + 32, pcmd.ElemCount); writeu32(b, o + 36, pcmd.IdxOffset); writeu32(b, o + 40, pcmd.VtxOffset)
|
writeu32(sc, o + 32, pcmd.ElemCount); writeu32(sc, o + 36, pcmd.IdxOffset); writeu32(sc, o + 40, pcmd.VtxOffset)
|
||||||
buffer.writef64(b, o + 44, pcmd:GetTexID())
|
writef64(sc, o + 44, pcmd:GetTexID())
|
||||||
o += 52
|
ci += 1
|
||||||
|
buffer.writeu8(sc, o + 52, cm_kind[ci] or 0); buffer.writeu8(sc, o + 53, cm_free[ci] and 1 or 0)
|
||||||
|
if cm_free[ci] then
|
||||||
|
buffer.writei32(sc, o + 54, cm_x0[ci]); buffer.writei32(sc, o + 58, cm_y0[ci]); buffer.writei32(sc, o + 62, cm_x1[ci]); buffer.writei32(sc, o + 66, cm_y1[ci])
|
||||||
|
end
|
||||||
|
o += CMD_STRIDE
|
||||||
|
end
|
||||||
|
set.SVN, set.SIN, set.SCN = nv, ni, ncmd
|
||||||
|
set.SOX, set.SOY, set.SSW, set.SSH = ox, oy, sw, sh
|
||||||
|
set.SCC = draw_list._ContentClipX0 ~= nil
|
||||||
|
set.SCX0, set.SCY0, set.SCX1, set.SCY1 = draw_list._ContentClipX0 or 0, draw_list._ContentClipY0 or 0, draw_list._ContentClipX1 or 0, draw_list._ContentClipY1 or 0
|
||||||
|
set.SSX, set.SSY = draw_list._ScrollOriginX or 0, draw_list._ScrollOriginY or 0
|
||||||
|
set.SnapOK = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- a + d == b exactly, with no rounding in a + d (so every later "x - origin" gives the same result as before the move)
|
||||||
|
@native
|
||||||
|
local function ShiftedExact(a, d, b)
|
||||||
|
local s = a + d
|
||||||
|
if s ~= b then return false end
|
||||||
|
local bb = s - a
|
||||||
|
return (a - (s - bb)) + (d - bb) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
@native
|
||||||
|
local function CompareDrawList(set, draw_list, ox, oy, sw, sh)
|
||||||
|
if not set or not set.SnapOK then return 0 end
|
||||||
|
local nv = draw_list.VtxBuffer.Size * VTX_STRIDE
|
||||||
|
local ni, ncmd = draw_list.IdxBuffer.Size, draw_list.CmdBuffer.Size
|
||||||
|
if nv ~= set.SVN or ni ~= set.SIN or ncmd ~= set.SCN or ox ~= set.SOX or oy ~= set.SOY or sw ~= set.SSW or sh ~= set.SSH then return 0 end
|
||||||
|
if (draw_list._ContentClipX0 ~= nil) ~= set.SCC then return 0 end
|
||||||
|
local vb, sv = draw_list.VtxBuffer.Buf, set.SV
|
||||||
|
if nv == 0 then return 1 end
|
||||||
|
-- translation from the first vertex: whole pixels only, and only with DisplayPos at 0 (so it stays exact)
|
||||||
|
local dx, dy = readf64(vb, 0) - readf64(sv, 0), readf64(vb, 8) - readf64(sv, 8)
|
||||||
|
if dx ~= dx or dy ~= dy then return 0 end
|
||||||
|
if dx ~= 0 or dy ~= 0 then
|
||||||
|
if dx ~= floor(dx) or dy ~= floor(dy) or ox ~= 0 or oy ~= 0 or not set.SLimFree then return 0 end
|
||||||
|
-- everything must stay as far from the clip limits as before (nothing newly clamped)
|
||||||
|
if set.SEX0 + dx < -CLIP_PAD or set.SEY0 + dy < -CLIP_PAD or set.SEX1 + dx > sw + CLIP_PAD or set.SEY1 + dy > sh + CLIP_PAD then return 0 end
|
||||||
|
end
|
||||||
|
local idx, si = draw_list.IdxBuffer.Data, set.SI
|
||||||
|
for i = 1, ni do if idx[i] ~= readu32(si, (i - 1) * 4) then return 0 end end
|
||||||
|
local sc, o = set.SC, 0
|
||||||
|
local ccx0, ccy0, ccx1, ccy1 = draw_list._ContentClipX0, draw_list._ContentClipY0, draw_list._ContentClipX1, draw_list._ContentClipY1
|
||||||
|
local seen_content = false
|
||||||
|
for _, pcmd in draw_list.CmdBuffer:iter() do
|
||||||
|
if pcmd.UserCallback ~= nil then return 0 end
|
||||||
|
if pcmd.ElemCount ~= readu32(sc, o + 32) or pcmd.IdxOffset ~= readu32(sc, o + 36) or pcmd.VtxOffset ~= readu32(sc, o + 40) or pcmd:GetTexID() ~= readf64(sc, o + 44) then return 0 end
|
||||||
|
local c = pcmd.ClipRect
|
||||||
|
if dx == 0 and dy == 0 then
|
||||||
|
if c.x ~= readf64(sc, o) or c.y ~= readf64(sc, o + 8) or c.z ~= readf64(sc, o + 16) or c.w ~= readf64(sc, o + 24) then return 0 end
|
||||||
|
elseif not (ShiftedExact(readf64(sc, o), dx, c.x) and ShiftedExact(readf64(sc, o + 8), dy, c.y)
|
||||||
|
and ShiftedExact(readf64(sc, o + 16), dx, c.z) and ShiftedExact(readf64(sc, o + 24), dy, c.w)) then
|
||||||
|
-- An unchanged clip rect (e.g. the viewport) is fine if it clipped none of this command's primitives before
|
||||||
|
-- the move and still clips none after it
|
||||||
|
if c.x ~= readf64(sc, o) or c.y ~= readf64(sc, o + 8) or c.z ~= readf64(sc, o + 16) or c.w ~= readf64(sc, o + 24)
|
||||||
|
or buffer.readu8(sc, o + 53) == 0 then return 0 end
|
||||||
|
if buffer.readi32(sc, o + 54) + dx < floor(c.x) or buffer.readi32(sc, o + 58) + dy < floor(c.y)
|
||||||
|
or buffer.readi32(sc, o + 62) + dx > floor(c.z) or buffer.readi32(sc, o + 66) + dy > floor(c.w) then return 0 end
|
||||||
|
end
|
||||||
|
-- same layer kind as before (same rule as the collect pass)
|
||||||
|
if pcmd.ElemCount > 0 then
|
||||||
|
local kind = 1
|
||||||
|
if ccx0 and c.x >= ccx0 and c.y >= ccy0 and c.z <= ccx1 and c.w <= ccy1 then kind = 2; seen_content = true
|
||||||
|
elseif seen_content then kind = 3 end
|
||||||
|
if kind ~= buffer.readu8(sc, o + 52) then return 0 end
|
||||||
|
end
|
||||||
|
o += CMD_STRIDE
|
||||||
|
end
|
||||||
|
if dx == 0 and dy == 0 then
|
||||||
|
if set.SCC and (draw_list._ContentClipX0 ~= set.SCX0 or draw_list._ContentClipY0 ~= set.SCY0 or draw_list._ContentClipX1 ~= set.SCX1
|
||||||
|
or draw_list._ContentClipY1 ~= set.SCY1 or draw_list._ScrollOriginX ~= set.SSX or draw_list._ScrollOriginY ~= set.SSY) then return 0 end
|
||||||
|
for v = 0, nv - VTX_STRIDE, VTX_STRIDE do
|
||||||
|
if readf64(vb, v) ~= readf64(sv, v) or readf64(vb, v + 8) ~= readf64(sv, v + 8) or readf64(vb, v + 16) ~= readf64(sv, v + 16)
|
||||||
|
or readf64(vb, v + 24) ~= readf64(sv, v + 24) or readu32(vb, v + 32) ~= readu32(sv, v + 32) then return 0 end
|
||||||
|
end
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
if set.SCC and not (ShiftedExact(set.SCX0, dx, draw_list._ContentClipX0) and ShiftedExact(set.SCY0, dy, draw_list._ContentClipY0)
|
||||||
|
and ShiftedExact(set.SCX1, dx, draw_list._ContentClipX1) and ShiftedExact(set.SCY1, dy, draw_list._ContentClipY1)
|
||||||
|
and ShiftedExact(set.SSX, dx, draw_list._ScrollOriginX) and ShiftedExact(set.SSY, dy, draw_list._ScrollOriginY)) then return 0 end
|
||||||
|
for v = 0, nv - VTX_STRIDE, VTX_STRIDE do
|
||||||
|
if not ShiftedExact(readf64(sv, v), dx, readf64(vb, v)) or not ShiftedExact(readf64(sv, v + 8), dy, readf64(vb, v + 8))
|
||||||
|
or readf64(vb, v + 16) ~= readf64(sv, v + 16) or readf64(vb, v + 24) ~= readf64(sv, v + 24) or readu32(vb, v + 32) ~= readu32(sv, v + 32) then return 0 end
|
||||||
|
end
|
||||||
|
return 2, dx, dy
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A window moved by whole pixels: its tiles are unchanged, only the layer frames move
|
||||||
|
local function ShiftLayer(layer, dx, dy, scale)
|
||||||
|
layer.X += dx; layer.Y += dy
|
||||||
|
local root = layer.Root
|
||||||
|
if root ~= layer.Frame then
|
||||||
|
layer.CX0 += dx; layer.CY0 += dy; layer.CX1 += dx; layer.CY1 += dy
|
||||||
|
root.Position = UDim2.fromOffset(layer.CX0 / scale, layer.CY0 / scale)
|
||||||
|
else
|
||||||
|
root.Position = UDim2.fromOffset(layer.X / scale, layer.Y / scale)
|
||||||
end
|
end
|
||||||
buffer.writef64(b, o, ox); buffer.writef64(b, o + 8, oy)
|
|
||||||
buffer.writef64(b, o + 16, draw_list._ContentClipX0 or -1); buffer.writef64(b, o + 24, draw_list._ContentClipY0 or -1)
|
|
||||||
buffer.writef64(b, o + 32, draw_list._ContentClipX1 or -1); buffer.writef64(b, o + 40, draw_list._ContentClipY1 or -1)
|
|
||||||
buffer.writef64(b, o + 48, draw_list._ContentClipX0 and draw_list._ScrollOriginX or -1)
|
|
||||||
buffer.writef64(b, o + 56, draw_list._ContentClipX0 and draw_list._ScrollOriginY or -1)
|
|
||||||
o += 64
|
|
||||||
return readstring(draw_list.VtxBuffer.Buf, 0, draw_list.VtxBuffer.Size * VTX_STRIDE) .. readstring(b, 0, o)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Overlays: user Instances (e.g. a Frame holding a ViewportFrame) placed inside a window, registered every frame with
|
-- Overlays: user Instances (e.g. a Frame holding a ViewportFrame) placed inside a window, registered every frame with
|
||||||
@@ -690,9 +806,11 @@ local function PE() if profiling then profileend() end end
|
|||||||
@native
|
@native
|
||||||
local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_x0, clip_y0, clip_x1, clip_y1)
|
local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_x0, clip_y0, clip_x1, clip_y1)
|
||||||
local scale = bd.Scale
|
local scale = bd.Scale
|
||||||
|
local deferred = false
|
||||||
-- Pass 2: hash primitives into layer-relative tiles
|
-- Pass 2: hash primitives into layer-relative tiles
|
||||||
PB("ImGui tile hash")
|
PB("ImGui tile hash")
|
||||||
local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched
|
local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched
|
||||||
|
local tl1, tl2, tlf = layer.TL1, layer.TL2, layer.TLF -- per-tile list of the primitives' hashes (for partial redraws)
|
||||||
for k = 1, layer.NTouched do tn[touched[k]] = 0 end
|
for k = 1, layer.NTouched do tn[touched[k]] = 0 end
|
||||||
local nt = 0
|
local nt = 0
|
||||||
for pi = 1, np do
|
for pi = 1, np do
|
||||||
@@ -701,23 +819,55 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
|
|||||||
-- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative)
|
-- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative)
|
||||||
-- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved.
|
-- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved.
|
||||||
local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0
|
local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0
|
||||||
local h1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1
|
|
||||||
local h2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
|
|
||||||
local vax, vay = lx0 + ox, ly0 + oy
|
|
||||||
local vbuf = pr_vb[p]
|
local vbuf = pr_vb[p]
|
||||||
h1, h2 = HashVertex(h1, h2, vbuf, pr_a[p], vax, vay)
|
local va, vd = pr_a[p], pr_d[p]
|
||||||
h1, h2 = HashVertex(h1, h2, vbuf, pr_b[p], vax, vay)
|
-- Flat rect (single colour, no gradient, constant uv): its pixels in a tile are exactly its clipped pixel rect
|
||||||
h1, h2 = HashVertex(h1, h2, vbuf, pr_c[p], vax, vay)
|
-- inside that tile filled with one colour, so hash that per tile instead of the vertices. Resizing a window
|
||||||
local vd = pr_d[p]
|
-- then only changes the tiles its edges cross, not every tile its background covers.
|
||||||
if vd then h1, h2 = HashVertex(h1, h2, vbuf, vd, vax, vay) end
|
local flat_rect, rcol, ruv = false, 0, 0
|
||||||
|
if vd and not pr_g[p] then
|
||||||
|
local u0, v0 = readf64(vbuf, va + 16), readf64(vbuf, va + 24)
|
||||||
|
local vc = pr_c[p]
|
||||||
|
if not pr_t[p] or (u0 == readf64(vbuf, vc + 16) and v0 == readf64(vbuf, vc + 24)) then
|
||||||
|
flat_rect, rcol, ruv = true, readu32(vbuf, va + 32), u0 * 8192 + v0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local h1, h2 = 0, 0
|
||||||
|
local fsig = -1 -- flat rect: colour/uv/texture without the geometry (-1: not a flat rect)
|
||||||
|
if flat_rect then fsig = ((rcol * 8191 + ruv * 4096) % P1 * 64 + tex_id) % P1 end
|
||||||
|
if not flat_rect then
|
||||||
|
h1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1
|
||||||
|
h2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
|
||||||
|
local vax, vay = lx0 + ox, ly0 + oy
|
||||||
|
if vd then
|
||||||
|
-- Axis aligned quad: the collect pass checked that TR/BL (positions, uvs, colours) follow from TL/BR
|
||||||
|
-- (and the gradient kind), so TL + BR + kind determine its pixels
|
||||||
|
h1, h2 = HashVertex(h1, h2, vbuf, va, vax, vay)
|
||||||
|
h1, h2 = HashVertex(h1, h2, vbuf, pr_c[p], vax, vay)
|
||||||
|
local gk = pr_g[p]
|
||||||
|
if gk then h1 = (h1 * 48271 + gk) % P1; h2 = (h2 * 16807 + gk * 3) % P2 end
|
||||||
|
else
|
||||||
|
h1, h2 = HashVertex(h1, h2, vbuf, va, vax, vay)
|
||||||
|
h1, h2 = HashVertex(h1, h2, vbuf, pr_b[p], vax, vay)
|
||||||
|
h1, h2 = HashVertex(h1, h2, vbuf, pr_c[p], vax, vay)
|
||||||
|
end
|
||||||
|
end
|
||||||
for ty = floor((pr_by0[p] - ly0) / TILE), floor((pr_by1[p] - 1 - ly0) / TILE) do
|
for ty = floor((pr_by0[p] - ly0) / TILE), floor((pr_by1[p] - 1 - ly0) / TILE) do
|
||||||
for tx = floor((pr_bx0[p] - lx0) / TILE), floor((pr_bx1[p] - 1 - lx0) / TILE) do
|
for tx = floor((pr_bx0[p] - lx0) / TILE), floor((pr_bx1[p] - 1 - lx0) / TILE) do
|
||||||
|
if flat_rect then
|
||||||
|
local tx0, ty0 = tx * TILE, ty * TILE
|
||||||
|
local ix0, iy0 = max(cx0 - tx0, 0), max(cy0 - ty0, 0)
|
||||||
|
local ix1, iy1 = min(cx1 - tx0, TILE), min(cy1 - ty0, TILE)
|
||||||
|
local r = ((ix0 * 131 + iy0) * 131 + ix1) * 131 + iy1
|
||||||
|
h1 = ((r * 48271 + rcol) % P1 * 48271 + ruv * 4096 + 7) % P1
|
||||||
|
h2 = ((r * 16807 + rcol + tex_id) % P2 * 16807 + ruv * 65521 + 11) % P2
|
||||||
|
end
|
||||||
local key = ty * KEY_W + tx
|
local key = ty * KEY_W + tx
|
||||||
local k = tn[key]
|
local k = tn[key]
|
||||||
if not k or k == 0 then
|
if not k or k == 0 then
|
||||||
k = 0
|
k = 0
|
||||||
th1[key], th2[key] = 0, 0
|
th1[key], th2[key] = 0, 0
|
||||||
if not tl[key] then tl[key] = {} end
|
if not tl[key] then tl[key] = {}; tl1[key] = {}; tl2[key] = {}; tlf[key] = {} end
|
||||||
nt = nt + 1
|
nt = nt + 1
|
||||||
touched[nt] = key
|
touched[nt] = key
|
||||||
layer.TX[key], layer.TY[key] = tx, ty
|
layer.TX[key], layer.TY[key] = tx, ty
|
||||||
@@ -725,6 +875,7 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
|
|||||||
k = k + 1
|
k = k + 1
|
||||||
tn[key] = k
|
tn[key] = k
|
||||||
tl[key][k] = p
|
tl[key][k] = p
|
||||||
|
tl1[key][k], tl2[key][k], tlf[key][k] = h1, h2, fsig
|
||||||
th1[key] = (th1[key] * 48271 + h1) % P1
|
th1[key] = (th1[key] * 48271 + h1) % P1
|
||||||
th2[key] = (th2[key] * 16807 + h2 + k) % P2
|
th2[key] = (th2[key] * 16807 + h2 + k) % P2
|
||||||
end
|
end
|
||||||
@@ -758,7 +909,7 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
|
|||||||
PB("ImGui raster+upload")
|
PB("ImGui raster+upload")
|
||||||
for key, tile in pairs(layer.Tiles) do
|
for key, tile in pairs(layer.Tiles) do
|
||||||
if not tn[key] or tn[key] == 0 then
|
if not tn[key] or tn[key] == 0 then
|
||||||
if tile.Visible then tile.Visible = false; tile.Label.Visible = false; tile.H1 = -1 end
|
if tile.Visible then tile.Visible = false; tile.Label.Visible = false; tile.H1 = -1; tile.ON = -1 end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for kk = 1, nt do
|
for kk = 1, nt do
|
||||||
@@ -766,14 +917,27 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
|
|||||||
local count = tn[key]
|
local count = tn[key]
|
||||||
local tx, ty = layer.TX[key], layer.TY[key]
|
local tx, ty = layer.TX[key], layer.TY[key]
|
||||||
local tile = GetTile(bd, layer, key, tx, ty)
|
local tile = GetTile(bd, layer, key, tx, ty)
|
||||||
if force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key] then
|
local dirty = force or tile.H1 ~= th1[key] or tile.H2 ~= th2[key]
|
||||||
|
if dirty and bd.RedrawStarted and (tile.Deferred or 0) < MAX_DEFER_FRAMES
|
||||||
|
and ((bd.RedrawBudget > 0 and os.clock() - bd.FrameT0 >= bd.RedrawBudget) or (bd.TileLimit and bd.FrameTiles >= bd.TileLimit)) then -- (TileLimit: test hook, tools/test/scripts/t_pixels.luau)
|
||||||
|
-- Over this frame's redraw budget: keep showing the old pixels, redraw on a following frame. The tile's
|
||||||
|
-- stored hashes/list still describe what it shows, so the next comparison is against the real content.
|
||||||
|
tile.Deferred = (tile.Deferred or 0) + 1
|
||||||
|
if force then tile.H1 = -1; tile.ON = -1 end -- full redraw later (force doesn't persist)
|
||||||
|
deferred = true
|
||||||
|
dirty = false
|
||||||
|
end
|
||||||
|
if dirty then
|
||||||
|
bd.RedrawStarted = true
|
||||||
|
bd.FrameTiles += 1
|
||||||
|
tile.Deferred = 0
|
||||||
tile.H1, tile.H2 = th1[key], th2[key]
|
tile.H1, tile.H2 = th1[key], th2[key]
|
||||||
local x0, y0 = lx0 + tx * TILE, ly0 + ty * TILE
|
local x0, y0 = lx0 + tx * TILE, ly0 + ty * TILE
|
||||||
local x1, y1 = x0 + TILE, y0 + TILE
|
local x1, y1 = x0 + TILE, y0 + TILE
|
||||||
buf, buf_w = tile.Buffer, TILE
|
buf, buf_w = tile.Buffer, TILE
|
||||||
local list = tl[key]
|
local list, lh1, lh2 = tl[key], tl1[key], tl2[key]
|
||||||
-- Dirty region = old content bounds + new content bounds. Outside it the tile is already
|
local oh1, oh2, ob0, ob1, ob2, ob3, on = tile.OH1, tile.OH2, tile.OB0, tile.OB1, tile.OB2, tile.OB3, tile.ON
|
||||||
-- transparent, so only this region is cleared and uploaded (edge tiles are often mostly empty).
|
-- New content bounds (union of the primitives' bounds in this tile)
|
||||||
local nx0, ny0, nx1, ny1 = TILE, TILE, 0, 0
|
local nx0, ny0, nx1, ny1 = TILE, TILE, 0, 0
|
||||||
for k = 1, count do
|
for k = 1, count do
|
||||||
local p = list[k]
|
local p = list[k]
|
||||||
@@ -785,8 +949,63 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
|
|||||||
if d > ny1 then ny1 = d end
|
if d > ny1 then ny1 = d end
|
||||||
end
|
end
|
||||||
nx0, ny0, nx1, ny1 = max(nx0, 0), max(ny0, 0), min(nx1, TILE), min(ny1, TILE)
|
nx0, ny0, nx1, ny1 = max(nx0, 0), max(ny0, 0), min(nx1, TILE), min(ny1, TILE)
|
||||||
local rx0, ry0, rx1, ry1 = min(nx0, tile.BX0), min(ny0, tile.BY0), max(nx1, tile.BX1), max(ny1, tile.BY1)
|
local rx0, ry0, rx1, ry1
|
||||||
|
if force or on < 0 then
|
||||||
|
-- Dirty region = old content bounds + new content bounds. Outside it the tile is already
|
||||||
|
-- transparent, so only this region is cleared and uploaded (edge tiles are often mostly empty).
|
||||||
|
rx0, ry0, rx1, ry1 = min(nx0, tile.BX0), min(ny0, tile.BY0), max(nx1, tile.BX1), max(ny1, tile.BY1)
|
||||||
|
else
|
||||||
|
-- Partial redraw: the primitives before the first and after the last changed one are the same as last
|
||||||
|
-- time. A pixel that no changed primitive (old or new) touches is covered by the same primitives in the
|
||||||
|
-- same order, so it is already correct: only the bounds of the changed ones are cleared and redrawn.
|
||||||
|
local i, lim = 1, min(on, count)
|
||||||
|
while i <= lim and oh1[i] == lh1[i] and oh2[i] == lh2[i] do i += 1 end
|
||||||
|
local j = 0
|
||||||
|
while j <= lim - i and oh1[on - j] == lh1[count - j] and oh2[on - j] == lh2[count - j] do j += 1 end
|
||||||
|
rx0, ry0, rx1, ry1 = TILE, TILE, 0, 0
|
||||||
|
local of, lf = tile.OF, tlf[key]
|
||||||
|
local paired = on - j - i == count - j - i
|
||||||
|
for k = i, count - j do
|
||||||
|
local p = list[k]
|
||||||
|
local a, b, c, d = max(pr_bx0[p] - x0, 0), max(pr_by0[p] - y0, 0), min(pr_bx1[p] - x0, TILE), min(pr_by1[p] - y0, TILE)
|
||||||
|
local oa, ob, oc, od = ob0[k], ob1[k], ob2[k], ob3[k]
|
||||||
|
if paired and lf[k] >= 0 and lf[k] == of[k] then
|
||||||
|
-- Same flat rect (colour/uv/texture) with new bounds: pixels inside both are unchanged, only the
|
||||||
|
-- strips between the old and new edges can differ (e.g. a window background growing on resize)
|
||||||
|
local uy0, uy1 = min(b, ob), max(d, od)
|
||||||
|
local ux0, ux1 = min(a, oa), max(c, oc)
|
||||||
|
if a ~= oa then ux0 = min(a, oa); local e = max(a, oa); rx0 = min(rx0, ux0); rx1 = max(rx1, e); ry0 = min(ry0, uy0); ry1 = max(ry1, uy1) end
|
||||||
|
if c ~= oc then local e = min(c, oc); rx0 = min(rx0, e); rx1 = max(rx1, max(c, oc)); ry0 = min(ry0, uy0); ry1 = max(ry1, uy1) end
|
||||||
|
if b ~= ob then ry0 = min(ry0, min(b, ob)); ry1 = max(ry1, max(b, ob)); rx0 = min(rx0, ux0); rx1 = max(rx1, ux1) end
|
||||||
|
if d ~= od then ry0 = min(ry0, min(d, od)); ry1 = max(ry1, max(d, od)); rx0 = min(rx0, ux0); rx1 = max(rx1, ux1) end
|
||||||
|
else
|
||||||
|
if a < rx0 then rx0 = a end
|
||||||
|
if b < ry0 then ry0 = b end
|
||||||
|
if c > rx1 then rx1 = c end
|
||||||
|
if d > ry1 then ry1 = d end
|
||||||
|
if k <= on - j then
|
||||||
|
if oa < rx0 then rx0 = oa end
|
||||||
|
if ob < ry0 then ry0 = ob end
|
||||||
|
if oc > rx1 then rx1 = oc end
|
||||||
|
if od > ry1 then ry1 = od end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for k = count - j + 1, on - j do -- old middle longer than the new one
|
||||||
|
if ob0[k] < rx0 then rx0 = ob0[k] end
|
||||||
|
if ob1[k] < ry0 then ry0 = ob1[k] end
|
||||||
|
if ob2[k] > rx1 then rx1 = ob2[k] end
|
||||||
|
if ob3[k] > ry1 then ry1 = ob3[k] end
|
||||||
|
end
|
||||||
|
end
|
||||||
tile.BX0, tile.BY0, tile.BX1, tile.BY1 = nx0, ny0, nx1, ny1
|
tile.BX0, tile.BY0, tile.BX1, tile.BY1 = nx0, ny0, nx1, ny1
|
||||||
|
-- Remember this list for the next partial redraw
|
||||||
|
for k = 1, count do
|
||||||
|
local p = list[k]
|
||||||
|
oh1[k], oh2[k], tile.OF[k] = lh1[k], lh2[k], tlf[key][k]
|
||||||
|
ob0[k], ob1[k], ob2[k], ob3[k] = max(pr_bx0[p] - x0, 0), max(pr_by0[p] - y0, 0), min(pr_bx1[p] - x0, TILE), min(pr_by1[p] - y0, TILE)
|
||||||
|
end
|
||||||
|
tile.ON = count
|
||||||
local rw = rx1 - rx0
|
local rw = rx1 - rx0
|
||||||
if rw == TILE then
|
if rw == TILE then
|
||||||
buffer.fill(buf, ry0 * TILE * 4, 0, (ry1 - ry0) * TILE * 4)
|
buffer.fill(buf, ry0 * TILE * 4, 0, (ry1 - ry0) * TILE * 4)
|
||||||
@@ -796,8 +1015,10 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
|
|||||||
local tox, toy = ox + x0, oy + y0
|
local tox, toy = ox + x0, oy + y0
|
||||||
for k = 1, count do
|
for k = 1, count do
|
||||||
local p = list[k]
|
local p = list[k]
|
||||||
local cx0, cy0 = max(pr_cx0[p], x0) - x0, max(pr_cy0[p], y0) - y0
|
-- clip to the dirty region (only those pixels are redrawn)
|
||||||
local cx1, cy1 = min(pr_cx1[p], x1) - x0, min(pr_cy1[p], y1) - y0
|
local cx0, cy0 = max(pr_cx0[p] - x0, rx0), max(pr_cy0[p] - y0, ry0)
|
||||||
|
local cx1, cy1 = min(pr_cx1[p] - x0, rx1), min(pr_cy1[p] - y0, ry1)
|
||||||
|
if cx0 >= cx1 or cy0 >= cy1 or pr_bx0[p] - x0 >= rx1 or pr_by0[p] - y0 >= ry1 or pr_bx1[p] - x0 <= rx0 or pr_by1[p] - y0 <= ry0 then continue end
|
||||||
local va, vc, vd, vbuf = pr_a[p], pr_c[p], pr_d[p], pr_vb[p]
|
local va, vc, vd, vbuf = pr_a[p], pr_c[p], pr_d[p], pr_vb[p]
|
||||||
if vd then
|
if vd then
|
||||||
local ax, ay, cx, cy = readf64(vbuf, va) - tox, readf64(vbuf, va + 8) - toy, readf64(vbuf, vc) - tox, readf64(vbuf, vc + 8) - toy
|
local ax, ay, cx, cy = readf64(vbuf, va) - tox, readf64(vbuf, va + 8) - toy, readf64(vbuf, vc) - tox, readf64(vbuf, vc + 8) - toy
|
||||||
@@ -816,31 +1037,42 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
|
|||||||
tile.Image:WritePixelsBuffer(Vector2.zero, TILE_SIZE, buf)
|
tile.Image:WritePixelsBuffer(Vector2.zero, TILE_SIZE, buf)
|
||||||
elseif rw > 0 and ry1 > ry0 then
|
elseif rw > 0 and ry1 > ry0 then
|
||||||
local rh = ry1 - ry0
|
local rh = ry1 - ry0
|
||||||
local sub = buffer.create(rw * rh * 4)
|
local sk = rw * 1024 + rh -- partial upload buffers are reused (WritePixelsBuffer needs the exact size)
|
||||||
|
local sub = sub_bufs[sk]
|
||||||
|
if not sub then
|
||||||
|
if n_sub_bufs >= 256 then table.clear(sub_bufs); n_sub_bufs = 0 end
|
||||||
|
sub = buffer.create(rw * rh * 4); sub_bufs[sk] = sub; n_sub_bufs += 1
|
||||||
|
end
|
||||||
for y = 0, rh - 1 do buffer.copy(sub, y * rw * 4, buf, ((ry0 + y) * TILE + rx0) * 4, rw * 4) end
|
for y = 0, rh - 1 do buffer.copy(sub, y * rw * 4, buf, ((ry0 + y) * TILE + rx0) * 4, rw * 4) end
|
||||||
tile.Image:WritePixelsBuffer(Vector2.new(rx0, ry0), Vector2.new(rw, rh), sub)
|
tile.Image:WritePixelsBuffer(Vector2.new(rx0, ry0), Vector2.new(rw, rh), sub)
|
||||||
end
|
end
|
||||||
PE()
|
PE()
|
||||||
end
|
end
|
||||||
if not tile.Visible then
|
if not tile.Visible and (tile.Deferred or 0) == 0 then -- (a deferred tile that was hidden holds old pixels: stays hidden until drawn)
|
||||||
tile.Visible = true
|
tile.Visible = true
|
||||||
tile.Label.Visible = true
|
tile.Label.Visible = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
PE()
|
PE()
|
||||||
|
return deferred
|
||||||
end
|
end
|
||||||
|
|
||||||
@native
|
@native
|
||||||
function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
||||||
local bd = ImGui_ImplRoblox_GetBackendData()
|
local bd = ImGui_ImplRoblox_GetBackendData()
|
||||||
PB("ImGui RenderDrawData")
|
PB("ImGui RenderDrawData")
|
||||||
|
bd.FrameT0 = os.clock()
|
||||||
|
bd.FrameTiles = 0
|
||||||
|
bd.RedrawStarted = false -- the first dirty tile of a frame is always redrawn (progress), the rest within the budget
|
||||||
|
bd.Stale = nil
|
||||||
|
|
||||||
PB("ImGui textures")
|
PB("ImGui textures")
|
||||||
if draw_data.Textures ~= nil then
|
if draw_data.Textures ~= nil then
|
||||||
for _, tex in draw_data.Textures:iter() do
|
for _, tex in draw_data.Textures:iter() do
|
||||||
if tex.Status ~= ImTextureStatus.OK then
|
if tex.Status ~= ImTextureStatus.OK then
|
||||||
|
-- No redraw needed for WantUpdates: it only writes texture portions which have never been used before
|
||||||
|
-- (new glyphs/rects), so no existing tile samples them. Created textures get a new (never reused) id.
|
||||||
ImGui_ImplRoblox_UpdateTexture(tex)
|
ImGui_ImplRoblox_UpdateTexture(tex)
|
||||||
bd.ForceRedraw = true -- texture contents changed: tiles sampling it must be redrawn
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -862,7 +1094,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
|
||||||
|
|
||||||
@@ -877,20 +1110,27 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
|
|
||||||
local n = 0
|
local n = 0
|
||||||
local z = 0
|
local z = 0
|
||||||
|
local any_deferred = false
|
||||||
for _, draw_list in draw_data.CmdLists:iter() do
|
for _, draw_list in draw_data.CmdLists:iter() do
|
||||||
-- Unchanged draw list (same vertex bytes, indices, commands, origin and scroll): its layers are already
|
-- Unchanged draw list (same vertices, indices, commands, origin and scroll): its layers are already correct,
|
||||||
-- correct, only restack them. Static windows then cost one byte compare instead of collect + hash.
|
-- only restack them. Moved by whole pixels (a dragged window): only move its layers. Either way no collect + hash.
|
||||||
PB("ImGui unchanged check")
|
PB("ImGui unchanged check")
|
||||||
local sig = DrawListSignature(draw_list, ox, oy)
|
|
||||||
local set = bd.Layers[draw_list]
|
local set = bd.Layers[draw_list]
|
||||||
if sig and set and not force and set.Sig == sig then
|
local same, sdx, sdy = 0, 0, 0
|
||||||
|
if not force then same, sdx, sdy = CompareDrawList(set, draw_list, ox, oy, sw, sh) end
|
||||||
|
if same ~= 0 then
|
||||||
for kind = 1, 3 do
|
for kind = 1, 3 do
|
||||||
local layer = set[kind]
|
local layer = set[kind]
|
||||||
if layer and layer.Visible then
|
if layer and layer.Visible then
|
||||||
|
if same == 2 then ShiftLayer(layer, sdx, sdy, bd.Scale) end
|
||||||
z += 1
|
z += 1
|
||||||
if layer.Z ~= z then layer.Z = z; layer.Root.ZIndex = z end
|
if layer.Z ~= z then layer.Z = z; layer.Root.ZIndex = z end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if same == 2 then
|
||||||
|
Snapshot(set, draw_list, ox, oy, sw, sh)
|
||||||
|
set.SEX0 += sdx; set.SEY0 += sdy; set.SEX1 += sdx; set.SEY1 += sdy
|
||||||
|
end
|
||||||
set.LastUsed = frame_no
|
set.LastUsed = frame_no
|
||||||
z = PlaceOverlays(bd, draw_list, z, ox, oy)
|
z = PlaceOverlays(bd, draw_list, z, ox, oy)
|
||||||
PE()
|
PE()
|
||||||
@@ -908,13 +1148,30 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
content_x1, content_y1 = min(floor(ccx1 - ox), lim_x1), min(floor(ccy1 - oy), lim_y1)
|
content_x1, content_y1 = min(floor(ccx1 - ox), lim_x1), min(floor(ccy1 - oy), lim_y1)
|
||||||
end
|
end
|
||||||
local seen_content = false
|
local seen_content = false
|
||||||
|
-- extents of everything clamped by the clip limits below (lim_free: nothing was), for the move fast path
|
||||||
|
local ex0, ey0, ex1, ey1 = math.huge, math.huge, -math.huge, -math.huge
|
||||||
|
local lim_free = true
|
||||||
|
if ccx0 then
|
||||||
|
local a, b, c, d = floor(ccx0 - ox), floor(ccy0 - oy), floor(ccx1 - ox), floor(ccy1 - oy)
|
||||||
|
if a < lim_x0 or b < lim_y0 or c > lim_x1 or d > lim_y1 then lim_free = false end
|
||||||
|
ex0, ey0, ex1, ey1 = min(ex0, a), min(ey0, b), max(ex1, c), max(ey1, d)
|
||||||
|
end
|
||||||
|
local ci = 0
|
||||||
for _, pcmd in draw_list.CmdBuffer:iter() do
|
for _, pcmd in draw_list.CmdBuffer:iter() do
|
||||||
|
ci += 1
|
||||||
|
cm_free[ci], cm_kind[ci] = false, 0
|
||||||
if pcmd.UserCallback ~= nil then
|
if pcmd.UserCallback ~= nil then
|
||||||
pcmd.UserCallback(draw_list, pcmd)
|
pcmd.UserCallback(draw_list, pcmd)
|
||||||
elseif pcmd.ElemCount > 0 then
|
elseif pcmd.ElemCount > 0 then
|
||||||
local clip = pcmd.ClipRect
|
local clip = pcmd.ClipRect
|
||||||
local cx0, cy0 = max(floor(clip.x - ox), lim_x0), max(floor(clip.y - oy), lim_y0)
|
local rcx0, rcy0, rcx1, rcy1 = floor(clip.x - ox), floor(clip.y - oy), floor(clip.z - ox), floor(clip.w - oy)
|
||||||
local cx1, cy1 = min(floor(clip.z - ox), lim_x1), min(floor(clip.w - oy), lim_y1)
|
if rcx0 < lim_x0 or rcy0 < lim_y0 or rcx1 > lim_x1 or rcy1 > lim_y1 then lim_free = false end
|
||||||
|
if rcx0 < ex0 then ex0 = rcx0 end
|
||||||
|
if rcy0 < ey0 then ey0 = rcy0 end
|
||||||
|
if rcx1 > ex1 then ex1 = rcx1 end
|
||||||
|
if rcy1 > ey1 then ey1 = rcy1 end
|
||||||
|
local cx0, cy0 = max(rcx0, lim_x0), max(rcy0, lim_y0)
|
||||||
|
local cx1, cy1 = min(rcx1, lim_x1), min(rcy1, lim_y1)
|
||||||
local kind = 1
|
local kind = 1
|
||||||
if ccx0 and clip.x >= ccx0 and clip.y >= ccy0 and clip.z <= ccx1 and clip.w <= ccy1 then
|
if ccx0 and clip.x >= ccx0 and clip.y >= ccy0 and clip.z <= ccx1 and clip.w <= ccy1 then
|
||||||
kind = 2; seen_content = true
|
kind = 2; seen_content = true
|
||||||
@@ -925,6 +1182,8 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
elseif seen_content then
|
elseif seen_content then
|
||||||
kind = 3
|
kind = 3
|
||||||
end
|
end
|
||||||
|
cm_kind[ci] = kind
|
||||||
|
local cfree, cex0, cey0, cex1, cey1 = cx1 > cx0 and cy1 > cy0, math.huge, math.huge, -math.huge, -math.huge
|
||||||
if cx1 > cx0 and cy1 > cy0 then
|
if cx1 > cx0 and cy1 > cy0 then
|
||||||
local tex_id = pcmd:GetTexID()
|
local tex_id = pcmd:GetTexID()
|
||||||
local t = bd.Textures[tex_id]
|
local t = bd.Textures[tex_id]
|
||||||
@@ -970,6 +1229,16 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
bx1, by1 = ceil(max(pax, pbx, pcx) - ox) + 1, ceil(max(pay, pby, pcy) - oy) + 1
|
bx1, by1 = ceil(max(pax, pbx, pcx) - ox) + 1, ceil(max(pay, pby, pcy) - oy) + 1
|
||||||
i = i + 3
|
i = i + 3
|
||||||
end
|
end
|
||||||
|
if bx0 < cx0 or by0 < cy0 or bx1 > cx1 or by1 > cy1 then cfree = false end
|
||||||
|
if bx0 < cex0 then cex0 = bx0 end
|
||||||
|
if by0 < cey0 then cey0 = by0 end
|
||||||
|
if bx1 > cex1 then cex1 = bx1 end
|
||||||
|
if by1 > cey1 then cey1 = by1 end
|
||||||
|
if bx0 < ex0 then ex0 = bx0 end
|
||||||
|
if by0 < ey0 then ey0 = by0 end
|
||||||
|
if bx1 > ex1 then ex1 = bx1 end
|
||||||
|
if by1 > ey1 then ey1 = by1 end
|
||||||
|
if bx0 < lim_x0 or by0 < lim_y0 or bx1 > lim_x1 or by1 > lim_y1 then lim_free = false end
|
||||||
if bx0 < cx0 then bx0 = cx0 end
|
if bx0 < cx0 then bx0 = cx0 end
|
||||||
if by0 < cy0 then by0 = cy0 end
|
if by0 < cy0 then by0 = cy0 end
|
||||||
if bx1 > cx1 then bx1 = cx1 end
|
if bx1 > cx1 then bx1 = cx1 end
|
||||||
@@ -983,12 +1252,16 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if cex0 > cex1 then cex0, cey0, cex1, cey1 = 0, 0, 0, 0 end -- (no primitives)
|
||||||
|
cm_free[ci] = cfree
|
||||||
|
cm_x0[ci], cm_y0[ci], cm_x1[ci], cm_y1[ci] = cex0, cey0, cex1, cey1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
PE()
|
PE()
|
||||||
|
|
||||||
-- Split into layer kinds and place each one
|
-- Split into layer kinds and place each one
|
||||||
set = bd.Layers[draw_list]
|
set = bd.Layers[draw_list]
|
||||||
|
local list_deferred = false
|
||||||
local nk1, nk2, nk3 = 0, 0, 0
|
local nk1, nk2, nk3 = 0, 0, 0
|
||||||
local o1x, o1y, o3x, o3y = math.huge, math.huge, math.huge, math.huge
|
local o1x, o1y, o3x, o3y = math.huge, math.huge, math.huge, math.huge
|
||||||
for p = first, n do
|
for p = first, n do
|
||||||
@@ -1007,19 +1280,28 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
end
|
end
|
||||||
if nk1 > 0 then
|
if nk1 > 0 then
|
||||||
z += 1
|
z += 1
|
||||||
DrawLayer(bd, GetLayer(bd, draw_list, 1), plist1, nk1, o1x, o1y, ox, oy, force, z)
|
if DrawLayer(bd, GetLayer(bd, draw_list, 1), plist1, nk1, o1x, o1y, ox, oy, force, z) then list_deferred = true end
|
||||||
elseif set and set[1] then HideLayer(set[1]) end
|
elseif set and set[1] then HideLayer(set[1]) end
|
||||||
if nk2 > 0 then
|
if nk2 > 0 then
|
||||||
z += 1
|
z += 1
|
||||||
DrawLayer(bd, GetLayer(bd, draw_list, 2), plist2, nk2, floor(draw_list._ScrollOriginX - ox), floor(draw_list._ScrollOriginY - oy), ox, oy, force, z,
|
if DrawLayer(bd, GetLayer(bd, draw_list, 2), plist2, nk2, floor(draw_list._ScrollOriginX - ox), floor(draw_list._ScrollOriginY - oy), ox, oy, force, z,
|
||||||
content_x0, content_y0, content_x1, content_y1)
|
content_x0, content_y0, content_x1, content_y1) then list_deferred = true end
|
||||||
elseif set and set[2] then HideLayer(set[2]) end
|
elseif set and set[2] then HideLayer(set[2]) end
|
||||||
if nk3 > 0 then
|
if nk3 > 0 then
|
||||||
z += 1
|
z += 1
|
||||||
DrawLayer(bd, GetLayer(bd, draw_list, 3), plist3, nk3, o3x, o3y, ox, oy, force, z)
|
if DrawLayer(bd, GetLayer(bd, draw_list, 3), plist3, nk3, o3x, o3y, ox, oy, force, z) then list_deferred = true end
|
||||||
elseif set and set[3] then HideLayer(set[3]) end
|
elseif set and set[3] then HideLayer(set[3]) end
|
||||||
set = bd.Layers[draw_list]
|
set = bd.Layers[draw_list]
|
||||||
if set then set.LastUsed = frame_no; set.Sig = sig end
|
-- (deferred tiles: not "unchanged" next frame even if the draw list is, so they get redrawn)
|
||||||
|
if set then
|
||||||
|
set.LastUsed = frame_no
|
||||||
|
if list_deferred then set.SnapOK = false else
|
||||||
|
Snapshot(set, draw_list, ox, oy, sw, sh)
|
||||||
|
set.SLimFree = lim_free
|
||||||
|
set.SEX0, set.SEY0, set.SEX1, set.SEY1 = ex0, ey0, ex1, ey1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if list_deferred then any_deferred = true end
|
||||||
z = PlaceOverlays(bd, draw_list, z, ox, oy)
|
z = PlaceOverlays(bd, draw_list, z, ox, oy)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1032,7 +1314,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
-- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips)
|
-- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips)
|
||||||
for draw_list, set in pairs(bd.Layers) do
|
for draw_list, set in pairs(bd.Layers) do
|
||||||
if set.LastUsed ~= frame_no then
|
if set.LastUsed ~= frame_no then
|
||||||
set.Sig = nil -- hidden now: must go through the full path when shown again
|
set.SnapOK = false -- hidden now: must go through the full path when shown again
|
||||||
for kind = 1, 3 do if set[kind] then HideLayer(set[kind]) end end
|
for kind = 1, 3 do if set[kind] then HideLayer(set[kind]) end end
|
||||||
if frame_no - set.LastUsed > LAYER_KEEP_FRAMES then
|
if frame_no - set.LastUsed > LAYER_KEEP_FRAMES then
|
||||||
DestroyLayerSet(set)
|
DestroyLayerSet(set)
|
||||||
@@ -1044,6 +1326,8 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
|
|||||||
-- drop references so old vertex tables can be collected
|
-- drop references so old vertex tables can be collected
|
||||||
for k = n + 1, bd.LastPrimCount do pr_t[k], pr_vb[k] = nil, nil end
|
for k = n + 1, bd.LastPrimCount do pr_t[k], pr_vb[k] = nil, nil end
|
||||||
bd.LastPrimCount = n
|
bd.LastPrimCount = n
|
||||||
|
-- Tiles left for later: if the app doesn't submit a new frame, Heartbeat finishes them from this draw data
|
||||||
|
if any_deferred then bd.Stale = draw_data; bd.StaleHB = bd.HB end
|
||||||
PE()
|
PE()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1069,9 +1353,11 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
|
|||||||
local bd = {
|
local bd = {
|
||||||
Time = os.clock(), Gui = gui, Connections = {},
|
Time = os.clock(), Gui = gui, Connections = {},
|
||||||
Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1,
|
Textures = {}, TextureCount = 0, MouseX = -1, MouseY = -1,
|
||||||
Layers = {}, Overlays = {}, OverlayHolders = {}, OverlayShown = {}, FrameNo = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 60, BusyRenderRate = 0, NextRender = 0, LastPrimCount = 0, ForceRedraw = true,
|
Layers = {}, Overlays = {}, OverlayHolders = {}, OverlayShown = {}, FrameNo = 0, Scale = render_scale or 1, TilesActive = false, RenderRate = 60, BusyRenderRate = 0, RedrawBudget = 0, HB = 0, NextRender = 0, LastPrimCount = 0, ForceRedraw = true,
|
||||||
}
|
}
|
||||||
io.BackendPlatformUserData = bd
|
io.BackendPlatformUserData = bd
|
||||||
|
io.BackendPlatformName = "imgui_impl_roblox"
|
||||||
|
io.BackendRendererName = "imgui_impl_roblox"
|
||||||
|
|
||||||
io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset)
|
io.BackendFlags = bit32.bor(io.BackendFlags, ImGuiBackendFlags.RendererHasTextures, ImGuiBackendFlags.RendererHasVtxOffset)
|
||||||
|
|
||||||
@@ -1129,6 +1415,19 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
|
|||||||
|
|
||||||
bd.PendingChars = {}
|
bd.PendingChars = {}
|
||||||
|
|
||||||
|
-- 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)
|
||||||
|
elseif bd.Stale and bd.HB > bd.StaleHB then -- deferred tiles of a frame not followed by a new one
|
||||||
|
ImGui_ImplRoblox_RenderDrawData(bd.Stale)
|
||||||
|
end
|
||||||
|
bd.HB += 1
|
||||||
|
end)
|
||||||
|
|
||||||
connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end)
|
connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end)
|
||||||
connect(UserInputService.WindowFocusReleased, function() ImGui.GetIO():AddFocusEvent(false) end)
|
connect(UserInputService.WindowFocusReleased, function() ImGui.GetIO():AddFocusEvent(false) end)
|
||||||
end
|
end
|
||||||
@@ -1178,6 +1477,8 @@ function ImGui_ImplRoblox_Shutdown()
|
|||||||
bd.Gui:Destroy()
|
bd.Gui:Destroy()
|
||||||
|
|
||||||
io.BackendPlatformUserData = nil
|
io.BackendPlatformUserData = nil
|
||||||
|
io.BackendPlatformName = nil
|
||||||
|
io.BackendRendererName = nil
|
||||||
io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor(
|
io.BackendFlags = bit32.band(io.BackendFlags, bit32.bnot(bit32.bor(
|
||||||
ImGuiBackendFlags.RendererHasTextures,
|
ImGuiBackendFlags.RendererHasTextures,
|
||||||
ImGuiBackendFlags.RendererHasVtxOffset
|
ImGuiBackendFlags.RendererHasVtxOffset
|
||||||
@@ -1188,6 +1489,8 @@ 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)
|
||||||
|
bd.Stale = nil
|
||||||
|
|
||||||
local screen = bd.Gui.AbsoluteSize
|
local screen = bd.Gui.AbsoluteSize
|
||||||
local w, h = floor(screen.X * bd.Scale), floor(screen.Y * bd.Scale)
|
local w, h = floor(screen.X * bd.Scale), floor(screen.Y * bd.Scale)
|
||||||
@@ -1294,7 +1597,15 @@ function ImGui_ImplRoblox_NewFrame()
|
|||||||
PE()
|
PE()
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Max tile redraws per second (default 60); 0 = every frame. imgui logic still runs every frame.
|
--- Opt-in: max milliseconds per frame spent redrawing changed tiles (default 0 = no limit, every change is drawn the
|
||||||
|
--- same frame). With a budget (e.g. 1.5), tiles over it keep
|
||||||
|
--- their previous pixels and are redrawn on the following frames (each one at most a few frames late), so a big change
|
||||||
|
--- (resizing a large window, opening one, scrolling a long list) is spread over frames instead of one long frame.
|
||||||
|
function ImGui_ImplRoblox_SetRedrawBudget(ms)
|
||||||
|
ImGui_ImplRoblox_GetBackendData().RedrawBudget = (ms or 0) / 1000
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Max tile redraws per second (default 60; 0 = every frame, limited by the redraw budget instead). imgui logic still runs every frame.
|
||||||
function ImGui_ImplRoblox_SetRenderRate(hz)
|
function ImGui_ImplRoblox_SetRenderRate(hz)
|
||||||
ImGui_ImplRoblox_GetBackendData().RenderRate = hz or 0
|
ImGui_ImplRoblox_GetBackendData().RenderRate = hz or 0
|
||||||
end
|
end
|
||||||
@@ -1324,6 +1635,7 @@ ImGui_ImplRoblox = {
|
|||||||
AddFile = ImGui_ImplRoblox_AddFile,
|
AddFile = ImGui_ImplRoblox_AddFile,
|
||||||
SetRenderScale = ImGui_ImplRoblox_SetRenderScale,
|
SetRenderScale = ImGui_ImplRoblox_SetRenderScale,
|
||||||
SetRenderRate = ImGui_ImplRoblox_SetRenderRate,
|
SetRenderRate = ImGui_ImplRoblox_SetRenderRate,
|
||||||
|
SetRedrawBudget = ImGui_ImplRoblox_SetRedrawBudget,
|
||||||
SetOverlay = ImGui_ImplRoblox_SetOverlay,
|
SetOverlay = ImGui_ImplRoblox_SetOverlay,
|
||||||
SetTextProxy = ImGui_ImplRoblox_SetTextProxy,
|
SetTextProxy = ImGui_ImplRoblox_SetTextProxy,
|
||||||
CreateTexture = ImGui_ImplRoblox_CreateTexture,
|
CreateTexture = ImGui_ImplRoblox_CreateTexture,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[[12, "imgui.lua"], [18510, "imgui_demo.lua"], [25795, "imgui_draw.lua"], [31033, "imgui_h.lua"], [33231, "imgui_impl_roblox.lua"], [34571, "imgui_impl_roblox_fonts.lua"], [40974, "imgui_internal.lua"], [44150, "imgui_tables.lua"], [48497, "imgui_widgets.lua"], [58252, "imstb_rectpack.lua"], [58698, "imstb_textedit.lua"], [59939, "imstb_truetype.lua"], [62407, "imstd_minstdio.lua"]]
|
[[46, "imgui.lua"], [22675, "imgui_demo.lua"], [34533, "imgui_draw.lua"], [42860, "imgui_h.lua"], [45857, "imgui_impl_roblox.lua"], [47509, "imgui_impl_roblox_fonts.lua"], [53912, "imgui_internal.lua"], [57884, "imgui_tables.lua"], [62976, "imgui_widgets.lua"], [75210, "imstb_rectpack.lua"], [75874, "imstb_textedit.lua"], [77416, "imstb_truetype.lua"], [80204, "imstd_minstdio.lua"]]
|
||||||
+32096
-14169
File diff suppressed because it is too large
Load Diff
+77
-3
@@ -6,6 +6,7 @@ export type ImVec4 = { x: number, y: number, z: number, w: number, [any]: any }
|
|||||||
export type ImGuiAPI = {
|
export type ImGuiAPI = {
|
||||||
AcceptDragDropPayload: (type: string, flags: number?) -> any,
|
AcceptDragDropPayload: (type: string, flags: number?) -> any,
|
||||||
ActivateItemByID: (...any) -> ...any,
|
ActivateItemByID: (...any) -> ...any,
|
||||||
|
AddContextHook: (...any) -> ...any,
|
||||||
AddDrawListToDrawDataEx: (...any) -> ...any,
|
AddDrawListToDrawDataEx: (...any) -> ...any,
|
||||||
AddSettingsHandler: (...any) -> ...any,
|
AddSettingsHandler: (...any) -> ...any,
|
||||||
AddUpdateViewport: (...any) -> ...any,
|
AddUpdateViewport: (...any) -> ...any,
|
||||||
@@ -13,6 +14,7 @@ export type ImGuiAPI = {
|
|||||||
ArrowButton: (str_id: string, dir: number) -> boolean,
|
ArrowButton: (str_id: string, dir: number) -> boolean,
|
||||||
ArrowButtonEx: (...any) -> ...any,
|
ArrowButtonEx: (...any) -> ...any,
|
||||||
Begin: (name: string, open: boolean?, flags: number?) -> (boolean, boolean?),
|
Begin: (name: string, open: boolean?, flags: number?) -> (boolean, boolean?),
|
||||||
|
BeginBoxSelect: (...any) -> ...any,
|
||||||
BeginChild: (...any) -> ...any,
|
BeginChild: (...any) -> ...any,
|
||||||
BeginChildEx: (...any) -> ...any,
|
BeginChildEx: (...any) -> ...any,
|
||||||
BeginColumns: (...any) -> ...any,
|
BeginColumns: (...any) -> ...any,
|
||||||
@@ -36,6 +38,7 @@ export type ImGuiAPI = {
|
|||||||
BeginMenu: (label: string, enabled: boolean?) -> boolean,
|
BeginMenu: (label: string, enabled: boolean?) -> boolean,
|
||||||
BeginMenuBar: () -> boolean,
|
BeginMenuBar: () -> boolean,
|
||||||
BeginMenuEx: (...any) -> ...any,
|
BeginMenuEx: (...any) -> ...any,
|
||||||
|
BeginMultiSelect: (flags: number, selection_size: number?, items_count: number?) -> any,
|
||||||
BeginPopup: (str_id: string, flags: number?) -> boolean,
|
BeginPopup: (str_id: string, flags: number?) -> boolean,
|
||||||
BeginPopupContextItem: (str_id: string?, popup_flags: number?) -> boolean,
|
BeginPopupContextItem: (str_id: string?, popup_flags: number?) -> boolean,
|
||||||
BeginPopupContextVoid: (str_id: string?, popup_flags: number?) -> boolean,
|
BeginPopupContextVoid: (str_id: string?, popup_flags: number?) -> boolean,
|
||||||
@@ -70,6 +73,7 @@ export type ImGuiAPI = {
|
|||||||
CalcTypematicRepeatAmount: (...any) -> ...any,
|
CalcTypematicRepeatAmount: (...any) -> ...any,
|
||||||
CalcWindowNextAutoFitSize: (...any) -> ...any,
|
CalcWindowNextAutoFitSize: (...any) -> ...any,
|
||||||
CalcWrapWidthForPos: (...any) -> ...any,
|
CalcWrapWidthForPos: (...any) -> ...any,
|
||||||
|
CallContextHooks: (...any) -> ...any,
|
||||||
Checkbox: (label: string, v: boolean?) -> (boolean, boolean?),
|
Checkbox: (label: string, v: boolean?) -> (boolean, boolean?),
|
||||||
CheckboxFlags: (...any) -> ...any,
|
CheckboxFlags: (...any) -> ...any,
|
||||||
CleanupIniSettings: (...any) -> ...any,
|
CleanupIniSettings: (...any) -> ...any,
|
||||||
@@ -109,26 +113,47 @@ export type ImGuiAPI = {
|
|||||||
DataTypeGetInfo: (...any) -> ...any,
|
DataTypeGetInfo: (...any) -> ...any,
|
||||||
DataTypeIsZero: (...any) -> ...any,
|
DataTypeIsZero: (...any) -> ...any,
|
||||||
DebugAllocHook: (...any) -> ...any,
|
DebugAllocHook: (...any) -> ...any,
|
||||||
|
DebugBreakButton: (...any) -> ...any,
|
||||||
|
DebugBreakButtonTooltip: (...any) -> ...any,
|
||||||
|
DebugBreakClearData: (...any) -> ...any,
|
||||||
|
DebugDrawCursorPos: (...any) -> ...any,
|
||||||
|
DebugDrawItemRect: (...any) -> ...any,
|
||||||
|
DebugDrawLineExtents: (...any) -> ...any,
|
||||||
|
DebugFlashStyleColor: (idx: number) -> (),
|
||||||
DebugHookIdInfo: (...any) -> ...any,
|
DebugHookIdInfo: (...any) -> ...any,
|
||||||
|
DebugLocateItem: (...any) -> ...any,
|
||||||
|
DebugLocateItemOnHover: (...any) -> ...any,
|
||||||
|
DebugLocateItemResolveWithLastItem: (...any) -> ...any,
|
||||||
DebugLog: (fmt: string, ...any) -> (),
|
DebugLog: (fmt: string, ...any) -> (),
|
||||||
|
DebugLogV: (fmt: string, ...any) -> (),
|
||||||
DebugNodeColumns: (...any) -> ...any,
|
DebugNodeColumns: (...any) -> ...any,
|
||||||
DebugNodeDockNode: (...any) -> ...any,
|
DebugNodeDockNode: (...any) -> ...any,
|
||||||
|
DebugNodeDrawCmdShowMeshAndBoundingBox: (...any) -> ...any,
|
||||||
DebugNodeDrawList: (...any) -> ...any,
|
DebugNodeDrawList: (...any) -> ...any,
|
||||||
DebugNodeFont: (...any) -> ...any,
|
DebugNodeFont: (...any) -> ...any,
|
||||||
DebugNodeFontGlyph: (...any) -> ...any,
|
DebugNodeFontGlyph: (...any) -> ...any,
|
||||||
DebugNodeFontGlyphsForSrcMask: (...any) -> ...any,
|
DebugNodeFontGlyphsForSrcMask: (...any) -> ...any,
|
||||||
|
DebugNodeInputTextState: (...any) -> ...any,
|
||||||
|
DebugNodeMultiSelectState: (...any) -> ...any,
|
||||||
|
DebugNodePlatformMonitor: (...any) -> ...any,
|
||||||
DebugNodeStorage: (...any) -> ...any,
|
DebugNodeStorage: (...any) -> ...any,
|
||||||
DebugNodeTabBar: (...any) -> ...any,
|
DebugNodeTabBar: (...any) -> ...any,
|
||||||
DebugNodeTable: (...any) -> ...any,
|
DebugNodeTable: (...any) -> ...any,
|
||||||
DebugNodeTableSettings: (...any) -> ...any,
|
DebugNodeTableSettings: (...any) -> ...any,
|
||||||
DebugNodeTexture: (...any) -> ...any,
|
DebugNodeTexture: (...any) -> ...any,
|
||||||
|
DebugNodeTypingSelectState: (...any) -> ...any,
|
||||||
DebugNodeViewport: (...any) -> ...any,
|
DebugNodeViewport: (...any) -> ...any,
|
||||||
DebugNodeWindow: (...any) -> ...any,
|
DebugNodeWindow: (...any) -> ...any,
|
||||||
DebugNodeWindowSettings: (...any) -> ...any,
|
DebugNodeWindowSettings: (...any) -> ...any,
|
||||||
DebugNodeWindowsList: (...any) -> ...any,
|
DebugNodeWindowsList: (...any) -> ...any,
|
||||||
DebugNodeWindowsListByBeginStackParent: (...any) -> ...any,
|
DebugNodeWindowsListByBeginStackParent: (...any) -> ...any,
|
||||||
|
DebugRenderKeyboardPreview: (...any) -> ...any,
|
||||||
DebugRenderViewportThumbnail: (...any) -> ...any,
|
DebugRenderViewportThumbnail: (...any) -> ...any,
|
||||||
|
DebugStartItemPicker: () -> (),
|
||||||
DebugTextEncoding: (str: string) -> (),
|
DebugTextEncoding: (str: string) -> (),
|
||||||
|
DebugTextUnformattedWithLocateItem: (...any) -> ...any,
|
||||||
|
DebugTextureIDToU64: (...any) -> ...any,
|
||||||
|
DemoMarker: (...any) -> ...any,
|
||||||
DestroyContext: (ctx: any) -> ...any,
|
DestroyContext: (ctx: any) -> ...any,
|
||||||
DestroyPlatformWindow: (...any) -> ...any,
|
DestroyPlatformWindow: (...any) -> ...any,
|
||||||
DestroyPlatformWindows: () -> (),
|
DestroyPlatformWindows: () -> (),
|
||||||
@@ -220,6 +245,7 @@ export type ImGuiAPI = {
|
|||||||
DragScalarN: (label: string, data_type: number, v: any, components: number, v_speed: number?, v_min: any, v_max: any, format: string?, flags: number?) -> ...any,
|
DragScalarN: (label: string, data_type: number, v: any, components: number, v_speed: number?, v_min: any, v_max: any, format: string?, flags: number?) -> ...any,
|
||||||
Dummy: (size: ImVec2) -> (),
|
Dummy: (size: ImVec2) -> (),
|
||||||
End: () -> (),
|
End: () -> (),
|
||||||
|
EndBoxSelect: (...any) -> ...any,
|
||||||
EndChild: () -> (),
|
EndChild: () -> (),
|
||||||
EndColumns: (...any) -> ...any,
|
EndColumns: (...any) -> ...any,
|
||||||
EndCombo: () -> (),
|
EndCombo: () -> (),
|
||||||
@@ -235,12 +261,15 @@ export type ImGuiAPI = {
|
|||||||
EndMainMenuBar: () -> (),
|
EndMainMenuBar: () -> (),
|
||||||
EndMenu: () -> (),
|
EndMenu: () -> (),
|
||||||
EndMenuBar: () -> (),
|
EndMenuBar: () -> (),
|
||||||
|
EndMultiSelect: () -> any,
|
||||||
EndPopup: () -> (),
|
EndPopup: () -> (),
|
||||||
EndTabBar: () -> (),
|
EndTabBar: () -> (),
|
||||||
EndTabItem: () -> (),
|
EndTabItem: () -> (),
|
||||||
EndTable: () -> (),
|
EndTable: () -> (),
|
||||||
EndTooltip: () -> (),
|
EndTooltip: () -> (),
|
||||||
ErrorCheckEndFrameFinalizeErrorTooltip: (...any) -> ...any,
|
ErrorCheckEndFrameFinalizeErrorTooltip: (...any) -> ...any,
|
||||||
|
ErrorCheckEndFrameSanityChecks: (...any) -> ...any,
|
||||||
|
ErrorCheckNewFrameSanityChecks: (...any) -> ...any,
|
||||||
ErrorCheckUsingSetCursorPosToExtendParentBoundaries: (...any) -> ...any,
|
ErrorCheckUsingSetCursorPosToExtendParentBoundaries: (...any) -> ...any,
|
||||||
ErrorLog: (...any) -> ...any,
|
ErrorLog: (...any) -> ...any,
|
||||||
ErrorRecoveryStoreState: (...any) -> ...any,
|
ErrorRecoveryStoreState: (...any) -> ...any,
|
||||||
@@ -259,6 +288,7 @@ export type ImGuiAPI = {
|
|||||||
FindRenderedTextEnd: (...any) -> ...any,
|
FindRenderedTextEnd: (...any) -> ...any,
|
||||||
FindSettingsHandler: (...any) -> ...any,
|
FindSettingsHandler: (...any) -> ...any,
|
||||||
FindViewportByID: (viewport_id: number) -> any,
|
FindViewportByID: (viewport_id: number) -> any,
|
||||||
|
FindViewportByPlatformHandle: (platform_handle: any) -> ...any,
|
||||||
FindWindowByID: (...any) -> ...any,
|
FindWindowByID: (...any) -> ...any,
|
||||||
FindWindowByName: (...any) -> ...any,
|
FindWindowByName: (...any) -> ...any,
|
||||||
FindWindowDisplayIndex: (...any) -> ...any,
|
FindWindowDisplayIndex: (...any) -> ...any,
|
||||||
@@ -273,7 +303,9 @@ export type ImGuiAPI = {
|
|||||||
GcAwakeTransientWindowBuffers: (...any) -> ...any,
|
GcAwakeTransientWindowBuffers: (...any) -> ...any,
|
||||||
GcCompactTransientMiscBuffers: (...any) -> ...any,
|
GcCompactTransientMiscBuffers: (...any) -> ...any,
|
||||||
GcCompactTransientWindowBuffers: (...any) -> ...any,
|
GcCompactTransientWindowBuffers: (...any) -> ...any,
|
||||||
|
GetActiveID: (...any) -> ...any,
|
||||||
GetBackgroundDrawList: (viewport: any) -> ...any,
|
GetBackgroundDrawList: (viewport: any) -> ...any,
|
||||||
|
GetBoxSelectState: (...any) -> ...any,
|
||||||
GetClipboardText: () -> any,
|
GetClipboardText: () -> any,
|
||||||
GetColorU32: (...any) -> ...any,
|
GetColorU32: (...any) -> ...any,
|
||||||
GetColumnIndex: () -> number,
|
GetColumnIndex: () -> number,
|
||||||
@@ -286,6 +318,9 @@ export type ImGuiAPI = {
|
|||||||
GetContentRegionAvail: () -> ImVec2,
|
GetContentRegionAvail: () -> ImVec2,
|
||||||
GetContentRegionMax: (...any) -> ...any,
|
GetContentRegionMax: (...any) -> ...any,
|
||||||
GetCurrentContext: () -> any,
|
GetCurrentContext: () -> any,
|
||||||
|
GetCurrentFocusScope: (...any) -> ...any,
|
||||||
|
GetCurrentTabBar: (...any) -> ...any,
|
||||||
|
GetCurrentTable: (...any) -> ...any,
|
||||||
GetCurrentWindow: (...any) -> ...any,
|
GetCurrentWindow: (...any) -> ...any,
|
||||||
GetCurrentWindowRead: (...any) -> ...any,
|
GetCurrentWindowRead: (...any) -> ...any,
|
||||||
GetCursorPos: () -> ImVec2,
|
GetCursorPos: () -> ImVec2,
|
||||||
@@ -297,7 +332,9 @@ export type ImGuiAPI = {
|
|||||||
GetDragDropPayload: () -> any,
|
GetDragDropPayload: () -> any,
|
||||||
GetDrawData: () -> any,
|
GetDrawData: () -> any,
|
||||||
GetDrawListSharedData: () -> any,
|
GetDrawListSharedData: () -> any,
|
||||||
|
GetFocusID: (...any) -> ...any,
|
||||||
GetFont: () -> any,
|
GetFont: () -> any,
|
||||||
|
GetFontBaked: () -> any,
|
||||||
GetFontSize: () -> number,
|
GetFontSize: () -> number,
|
||||||
GetFontTexUvWhitePixel: () -> ImVec2,
|
GetFontTexUvWhitePixel: () -> ImVec2,
|
||||||
GetForegroundDrawList: (viewport: any) -> ...any,
|
GetForegroundDrawList: (viewport: any) -> ...any,
|
||||||
@@ -315,6 +352,7 @@ export type ImGuiAPI = {
|
|||||||
GetItemRectMax: () -> ImVec2,
|
GetItemRectMax: () -> ImVec2,
|
||||||
GetItemRectMin: () -> ImVec2,
|
GetItemRectMin: () -> ImVec2,
|
||||||
GetItemRectSize: () -> ImVec2,
|
GetItemRectSize: () -> ImVec2,
|
||||||
|
GetItemStatusFlags: (...any) -> ...any,
|
||||||
GetKeyChordName: (...any) -> ...any,
|
GetKeyChordName: (...any) -> ...any,
|
||||||
GetKeyData: (...any) -> ...any,
|
GetKeyData: (...any) -> ...any,
|
||||||
GetKeyMagnitude2d: (...any) -> ...any,
|
GetKeyMagnitude2d: (...any) -> ...any,
|
||||||
@@ -331,7 +369,9 @@ export type ImGuiAPI = {
|
|||||||
GetMouseDragDelta: (button: number?, lock_threshold: number?) -> ImVec2,
|
GetMouseDragDelta: (button: number?, lock_threshold: number?) -> ImVec2,
|
||||||
GetMousePos: () -> ImVec2,
|
GetMousePos: () -> ImVec2,
|
||||||
GetMousePosOnOpeningCurrentPopup: () -> ImVec2,
|
GetMousePosOnOpeningCurrentPopup: () -> ImVec2,
|
||||||
|
GetMultiSelectState: (...any) -> ...any,
|
||||||
GetNavTweakPressedAmount: (...any) -> ...any,
|
GetNavTweakPressedAmount: (...any) -> ...any,
|
||||||
|
GetPixelDensity: (...any) -> ...any,
|
||||||
GetPlatformIO: () -> any,
|
GetPlatformIO: () -> any,
|
||||||
GetPopupAllowedExtentRect: (...any) -> ...any,
|
GetPopupAllowedExtentRect: (...any) -> ...any,
|
||||||
GetRoundedFontSize: (...any) -> ...any,
|
GetRoundedFontSize: (...any) -> ...any,
|
||||||
@@ -353,6 +393,7 @@ export type ImGuiAPI = {
|
|||||||
GetTopMostPopupModal: (...any) -> ...any,
|
GetTopMostPopupModal: (...any) -> ...any,
|
||||||
GetTreeNodeToLabelSpacing: () -> number,
|
GetTreeNodeToLabelSpacing: () -> number,
|
||||||
GetTypematicRepeatRate: (...any) -> ...any,
|
GetTypematicRepeatRate: (...any) -> ...any,
|
||||||
|
GetTypingSelectRequest: (...any) -> ...any,
|
||||||
GetVersion: () -> any,
|
GetVersion: () -> any,
|
||||||
GetViewportPlatformMonitor: (...any) -> ...any,
|
GetViewportPlatformMonitor: (...any) -> ...any,
|
||||||
GetWindowAlwaysWantOwnTabBar: (...any) -> ...any,
|
GetWindowAlwaysWantOwnTabBar: (...any) -> ...any,
|
||||||
@@ -360,10 +401,13 @@ export type ImGuiAPI = {
|
|||||||
GetWindowContentRegionMax: (...any) -> ...any,
|
GetWindowContentRegionMax: (...any) -> ...any,
|
||||||
GetWindowContentRegionMin: (...any) -> ...any,
|
GetWindowContentRegionMin: (...any) -> ...any,
|
||||||
GetWindowDockID: () -> number,
|
GetWindowDockID: () -> number,
|
||||||
|
GetWindowDockNode: (...any) -> ...any,
|
||||||
GetWindowDpiScale: () -> number,
|
GetWindowDpiScale: () -> number,
|
||||||
GetWindowDrawList: () -> any,
|
GetWindowDrawList: () -> any,
|
||||||
GetWindowHeight: () -> number,
|
GetWindowHeight: () -> number,
|
||||||
GetWindowPos: () -> ImVec2,
|
GetWindowPos: () -> ImVec2,
|
||||||
|
GetWindowResizeBorderID: (...any) -> ...any,
|
||||||
|
GetWindowResizeCornerID: (...any) -> ...any,
|
||||||
GetWindowScrollbarID: (...any) -> ...any,
|
GetWindowScrollbarID: (...any) -> ...any,
|
||||||
GetWindowScrollbarRect: (...any) -> ...any,
|
GetWindowScrollbarRect: (...any) -> ...any,
|
||||||
GetWindowSize: () -> ImVec2,
|
GetWindowSize: () -> ImVec2,
|
||||||
@@ -405,6 +449,7 @@ export type ImGuiAPI = {
|
|||||||
IsInNavFocusRoute: (...any) -> ...any,
|
IsInNavFocusRoute: (...any) -> ...any,
|
||||||
IsItemActivated: () -> boolean,
|
IsItemActivated: () -> boolean,
|
||||||
IsItemActive: () -> boolean,
|
IsItemActive: () -> boolean,
|
||||||
|
IsItemActiveAsInputText: (...any) -> ...any,
|
||||||
IsItemClicked: (mouse_button: number?) -> boolean,
|
IsItemClicked: (mouse_button: number?) -> boolean,
|
||||||
IsItemDeactivated: () -> boolean,
|
IsItemDeactivated: () -> boolean,
|
||||||
IsItemDeactivatedAfterEdit: () -> boolean,
|
IsItemDeactivatedAfterEdit: () -> boolean,
|
||||||
@@ -421,6 +466,7 @@ export type ImGuiAPI = {
|
|||||||
IsKeyReleased: (...any) -> ...any,
|
IsKeyReleased: (...any) -> ...any,
|
||||||
IsKeyboardKey: (...any) -> ...any,
|
IsKeyboardKey: (...any) -> ...any,
|
||||||
IsLRModKey: (...any) -> ...any,
|
IsLRModKey: (...any) -> ...any,
|
||||||
|
IsLegacyKey: (...any) -> ...any,
|
||||||
IsMouseClicked: (button: number, is_repeat: boolean?) -> boolean,
|
IsMouseClicked: (button: number, is_repeat: boolean?) -> boolean,
|
||||||
IsMouseClickedEx: (...any) -> ...any,
|
IsMouseClickedEx: (...any) -> ...any,
|
||||||
IsMouseDoubleClicked: (...any) -> ...any,
|
IsMouseDoubleClicked: (...any) -> ...any,
|
||||||
@@ -479,6 +525,10 @@ export type ImGuiAPI = {
|
|||||||
MenuItem: (...any) -> ...any,
|
MenuItem: (...any) -> ...any,
|
||||||
MenuItemEx: (...any) -> ...any,
|
MenuItemEx: (...any) -> ...any,
|
||||||
MouseButtonToKey: (...any) -> ...any,
|
MouseButtonToKey: (...any) -> ...any,
|
||||||
|
MultiSelectAddSetAll: (...any) -> ...any,
|
||||||
|
MultiSelectAddSetRange: (...any) -> ...any,
|
||||||
|
MultiSelectItemFooter: (...any) -> ...any,
|
||||||
|
MultiSelectItemHeader: (...any) -> ...any,
|
||||||
NavApplyItemToResult: (...any) -> ...any,
|
NavApplyItemToResult: (...any) -> ...any,
|
||||||
NavBiasScoringRect: (...any) -> ...any,
|
NavBiasScoringRect: (...any) -> ...any,
|
||||||
NavCalcPreferredRefPos: (...any) -> ...any,
|
NavCalcPreferredRefPos: (...any) -> ...any,
|
||||||
@@ -556,6 +606,8 @@ export type ImGuiAPI = {
|
|||||||
RadioButton: (...any) -> ...any,
|
RadioButton: (...any) -> ...any,
|
||||||
RadioButtonEx: (...any) -> ...any,
|
RadioButtonEx: (...any) -> ...any,
|
||||||
RegisterFontAtlas: (...any) -> ...any,
|
RegisterFontAtlas: (...any) -> ...any,
|
||||||
|
RegisterUserTexture: (...any) -> ...any,
|
||||||
|
RemoveContextHook: (...any) -> ...any,
|
||||||
RemoveSettingsHandler: (...any) -> ...any,
|
RemoveSettingsHandler: (...any) -> ...any,
|
||||||
Render: () -> (),
|
Render: () -> (),
|
||||||
RenderArrow: (...any) -> ...any,
|
RenderArrow: (...any) -> ...any,
|
||||||
@@ -591,6 +643,7 @@ export type ImGuiAPI = {
|
|||||||
ScaleRatioFromValueT: (...any) -> ...any,
|
ScaleRatioFromValueT: (...any) -> ...any,
|
||||||
ScaleValueFromRatioT: (...any) -> ...any,
|
ScaleValueFromRatioT: (...any) -> ...any,
|
||||||
ScaleWindowsInViewport: (...any) -> ...any,
|
ScaleWindowsInViewport: (...any) -> ...any,
|
||||||
|
ScrollToBringRectIntoView: (...any) -> ...any,
|
||||||
ScrollToItem: (...any) -> ...any,
|
ScrollToItem: (...any) -> ...any,
|
||||||
ScrollToRect: (...any) -> ...any,
|
ScrollToRect: (...any) -> ...any,
|
||||||
ScrollToRectEx: (...any) -> ...any,
|
ScrollToRectEx: (...any) -> ...any,
|
||||||
@@ -608,6 +661,7 @@ export type ImGuiAPI = {
|
|||||||
SetColorEditOptions: (...any) -> ...any,
|
SetColorEditOptions: (...any) -> ...any,
|
||||||
SetColumnOffset: (column_index: number, offset: number) -> (),
|
SetColumnOffset: (column_index: number, offset: number) -> (),
|
||||||
SetColumnWidth: (column_index: number, width: number) -> (),
|
SetColumnWidth: (column_index: number, width: number) -> (),
|
||||||
|
SetContextName: (...any) -> ...any,
|
||||||
SetCurrentContext: (ctx: any) -> ...any,
|
SetCurrentContext: (ctx: any) -> ...any,
|
||||||
SetCurrentFont: (...any) -> ...any,
|
SetCurrentFont: (...any) -> ...any,
|
||||||
SetCurrentViewport: (...any) -> ...any,
|
SetCurrentViewport: (...any) -> ...any,
|
||||||
@@ -621,6 +675,7 @@ export type ImGuiAPI = {
|
|||||||
SetItemDefaultFocus: () -> (),
|
SetItemDefaultFocus: () -> (),
|
||||||
SetItemKeyOwner: (...any) -> ...any,
|
SetItemKeyOwner: (...any) -> ...any,
|
||||||
SetItemTooltip: (fmt: string, ...any) -> (),
|
SetItemTooltip: (fmt: string, ...any) -> (),
|
||||||
|
SetItemTooltipV: (fmt: string, ...any) -> (),
|
||||||
SetKeyOwner: (...any) -> ...any,
|
SetKeyOwner: (...any) -> ...any,
|
||||||
SetKeyOwnersForKeyChord: (...any) -> ...any,
|
SetKeyOwnersForKeyChord: (...any) -> ...any,
|
||||||
SetKeyboardFocusHere: (offset: number?) -> (),
|
SetKeyboardFocusHere: (offset: number?) -> (),
|
||||||
@@ -639,6 +694,7 @@ export type ImGuiAPI = {
|
|||||||
SetNextItemColorMarker: (...any) -> ...any,
|
SetNextItemColorMarker: (...any) -> ...any,
|
||||||
SetNextItemOpen: (is_open: boolean, cond: number?) -> (),
|
SetNextItemOpen: (is_open: boolean, cond: number?) -> (),
|
||||||
SetNextItemRefVal: (...any) -> ...any,
|
SetNextItemRefVal: (...any) -> ...any,
|
||||||
|
SetNextItemSelectionUserData: (selection_user_data: any) -> (),
|
||||||
SetNextItemShortcut: (key_chord: any, flags: number?) -> (),
|
SetNextItemShortcut: (key_chord: any, flags: number?) -> (),
|
||||||
SetNextItemStorageID: (storage_id: number) -> (),
|
SetNextItemStorageID: (storage_id: number) -> (),
|
||||||
SetNextItemWidth: (item_width: number) -> (),
|
SetNextItemWidth: (item_width: number) -> (),
|
||||||
@@ -657,6 +713,7 @@ export type ImGuiAPI = {
|
|||||||
SetNextWindowSize: (size: ImVec2, cond: number?) -> (),
|
SetNextWindowSize: (size: ImVec2, cond: number?) -> (),
|
||||||
SetNextWindowSizeConstraints: (size_min: ImVec2, size_max: ImVec2, custom_callback: any, custom_callback_user_data: any) -> ...any,
|
SetNextWindowSizeConstraints: (size_min: ImVec2, size_max: ImVec2, custom_callback: any, custom_callback_user_data: any) -> ...any,
|
||||||
SetNextWindowViewport: (id: number) -> (),
|
SetNextWindowViewport: (id: number) -> (),
|
||||||
|
SetPixelDensity: (...any) -> ...any,
|
||||||
SetScrollFromPosX: (...any) -> ...any,
|
SetScrollFromPosX: (...any) -> ...any,
|
||||||
SetScrollFromPosY: (...any) -> ...any,
|
SetScrollFromPosY: (...any) -> ...any,
|
||||||
SetScrollHereX: (center_x_ratio: number?) -> (),
|
SetScrollHereX: (center_x_ratio: number?) -> (),
|
||||||
@@ -667,6 +724,7 @@ export type ImGuiAPI = {
|
|||||||
SetStateStorage: (tree: any) -> ...any,
|
SetStateStorage: (tree: any) -> ...any,
|
||||||
SetTabItemClosed: (label: string) -> (),
|
SetTabItemClosed: (label: string) -> (),
|
||||||
SetTooltip: (fmt: string, ...any) -> (),
|
SetTooltip: (fmt: string, ...any) -> (),
|
||||||
|
SetTooltipV: (fmt: string, ...any) -> (),
|
||||||
SetWindowClipRectBeforeSetChannel: (...any) -> ...any,
|
SetWindowClipRectBeforeSetChannel: (...any) -> ...any,
|
||||||
SetWindowCollapsed: (...any) -> ...any,
|
SetWindowCollapsed: (...any) -> ...any,
|
||||||
SetWindowDock: (...any) -> ...any,
|
SetWindowDock: (...any) -> ...any,
|
||||||
@@ -674,6 +732,7 @@ export type ImGuiAPI = {
|
|||||||
SetWindowFontScale: (...any) -> ...any,
|
SetWindowFontScale: (...any) -> ...any,
|
||||||
SetWindowHiddenAndSkipItemsForCurrentFrame: (...any) -> ...any,
|
SetWindowHiddenAndSkipItemsForCurrentFrame: (...any) -> ...any,
|
||||||
SetWindowHitTestHole: (...any) -> ...any,
|
SetWindowHitTestHole: (...any) -> ...any,
|
||||||
|
SetWindowParentWindowForFocusRoute: (...any) -> ...any,
|
||||||
SetWindowPos: (...any) -> ...any,
|
SetWindowPos: (...any) -> ...any,
|
||||||
SetWindowSize: (...any) -> ...any,
|
SetWindowSize: (...any) -> ...any,
|
||||||
SetWindowViewport: (...any) -> ...any,
|
SetWindowViewport: (...any) -> ...any,
|
||||||
@@ -822,6 +881,7 @@ export type ImGuiAPI = {
|
|||||||
TempInputScalar: (...any) -> ...any,
|
TempInputScalar: (...any) -> ...any,
|
||||||
TempInputText: (...any) -> ...any,
|
TempInputText: (...any) -> ...any,
|
||||||
TestKeyOwner: (...any) -> ...any,
|
TestKeyOwner: (...any) -> ...any,
|
||||||
|
TestShortcutRouting: (...any) -> ...any,
|
||||||
Text: (fmt: string, ...any) -> (),
|
Text: (fmt: string, ...any) -> (),
|
||||||
TextAligned: (...any) -> ...any,
|
TextAligned: (...any) -> ...any,
|
||||||
TextBufferToString: (buf: {number}) -> string,
|
TextBufferToString: (buf: {number}) -> string,
|
||||||
@@ -848,11 +908,17 @@ export type ImGuiAPI = {
|
|||||||
TreePop: () -> (),
|
TreePop: () -> (),
|
||||||
TreePush: (...any) -> ...any,
|
TreePush: (...any) -> ...any,
|
||||||
TreePushOverrideID: (...any) -> ...any,
|
TreePushOverrideID: (...any) -> ...any,
|
||||||
|
TypingSelectFindBestLeadingMatch: (...any) -> ...any,
|
||||||
|
TypingSelectFindMatch: (...any) -> ...any,
|
||||||
|
TypingSelectFindNextSingleCharMatch: (...any) -> ...any,
|
||||||
Unindent: (indent_w: number?) -> (),
|
Unindent: (indent_w: number?) -> (),
|
||||||
UnregisterFontAtlas: (...any) -> ...any,
|
UnregisterFontAtlas: (...any) -> ...any,
|
||||||
|
UnregisterUserTexture: (...any) -> ...any,
|
||||||
UpdateAliasKey: (...any) -> ...any,
|
UpdateAliasKey: (...any) -> ...any,
|
||||||
UpdateCurrentFontSize: (...any) -> ...any,
|
UpdateCurrentFontSize: (...any) -> ...any,
|
||||||
|
UpdateDebugToolFlashStyleColor: (...any) -> ...any,
|
||||||
UpdateDebugToolItemPathQuery: (...any) -> ...any,
|
UpdateDebugToolItemPathQuery: (...any) -> ...any,
|
||||||
|
UpdateDebugToolItemPicker: (...any) -> ...any,
|
||||||
UpdateFontsEndFrame: (...any) -> ...any,
|
UpdateFontsEndFrame: (...any) -> ...any,
|
||||||
UpdateFontsNewFrame: (...any) -> ...any,
|
UpdateFontsNewFrame: (...any) -> ...any,
|
||||||
UpdateHoveredWindowAndCaptureFlags: (...any) -> ...any,
|
UpdateHoveredWindowAndCaptureFlags: (...any) -> ...any,
|
||||||
@@ -880,20 +946,22 @@ export type ImGuiAPI = {
|
|||||||
VSliderScalar: (label: string, size: ImVec2, data_type: number, data: any, min: any, max: any, format: string?, flags: number?) -> ...any,
|
VSliderScalar: (label: string, size: ImVec2, data_type: number, data: any, min: any, max: any, format: string?, flags: number?) -> ...any,
|
||||||
Value: (...any) -> ...any,
|
Value: (...any) -> ...any,
|
||||||
WindowPosAbsToRel: (...any) -> ...any,
|
WindowPosAbsToRel: (...any) -> ...any,
|
||||||
|
WindowPosRelToAbs: (...any) -> ...any,
|
||||||
WindowRectAbsToRel: (...any) -> ...any,
|
WindowRectAbsToRel: (...any) -> ...any,
|
||||||
WindowRectRelToAbs: (...any) -> ...any,
|
WindowRectRelToAbs: (...any) -> ...any,
|
||||||
WindowSelectViewport: (...any) -> ...any,
|
WindowSelectViewport: (...any) -> ...any,
|
||||||
WindowSettingsAddSettingsHandler: (...any) -> ...any,
|
WindowSettingsAddSettingsHandler: (...any) -> ...any,
|
||||||
WindowSyncOwnedViewport: (...any) -> ...any,
|
WindowSyncOwnedViewport: (...any) -> ...any,
|
||||||
_ByteArrayToString: (...any) -> ...any,
|
_ByteArrayToString: (...any) -> ...any,
|
||||||
|
_CalcItemSizeXY: (...any) -> ...any,
|
||||||
_GetTypeFunc: (...any) -> ...any,
|
_GetTypeFunc: (...any) -> ...any,
|
||||||
_SetCurrentContext_Internal: (...any) -> ...any,
|
_SetCurrentContext_Internal: (...any) -> ...any,
|
||||||
_SetCurrentContext_Tables: (...any) -> ...any,
|
_SetCurrentContext_Tables: (...any) -> ...any,
|
||||||
_SetCurrentContext_Widgets: (...any) -> ...any,
|
_SetCurrentContext_Widgets: (...any) -> ...any,
|
||||||
[string]: any,
|
[string]: any,
|
||||||
}
|
}
|
||||||
export type ImDrawFlags = { None: number, RoundCornersTopLeft: number, RoundCornersTopRight: number, RoundCornersBottomLeft: number, RoundCornersBottomRight: number, RoundCornersNone: number, Closed: number, RoundCornersTop: number, RoundCornersBottom: number, RoundCornersLeft: number, RoundCornersRight: number, RoundCornersAll: number, RoundCornersDefault_: number, RoundCornersMask_: number, InvalidMask_: number, [string]: number }
|
export type ImDrawFlags = { None: number, RoundTopLeft: number, RoundTopRight: number, RoundBottomLeft: number, RoundBottomRight: number, RoundNone: number, Closed: number, JoinMiter: number, CapSquare: number, AAFill: number, AALines: number, AALineEnds: number, StrokeInside: number, StrokeCenter: number, StrokeCenterBiased: number, StrokeOutside: number, StrokeLegacy: number, TextNoPixelSnap: number, UseTexForRoundCorners: number, UseVtxOffset: number, AllowTexForRoundCorners_: number, RoundAll: number, RoundTop: number, RoundBottom: number, RoundLeft: number, RoundRight: number, RoundMask_: number, AllowInPushScope_: number, AllowInFrameScope_: number, StrokeMask_: number, InvalidMask_: number, RoundCornersTopLeft: number, RoundCornersTopRight: number, RoundCornersBottomLeft: number, RoundCornersBottomRight: number, RoundCornersNone: number, RoundCornersAll: number, RoundCornersTop: number, RoundCornersBottom: number, RoundCornersLeft: number, RoundCornersRight: number, [string]: number }
|
||||||
export type ImDrawListFlags = { None: number, AntiAliasedLines: number, AntiAliasedLinesUseTex: number, AntiAliasedFill: number, AllowVtxOffset: number, TextNoPixelSnap: number, [string]: number }
|
export type ImDrawListFlags = { None: number, AntiAliasedFill: number, AntiAliasedLines: number, AntiAliasedLinesUseTex: number, AllowVtxOffset: number, TextNoPixelSnap: number, [string]: number }
|
||||||
export type ImFontFlags = { None: number, NoLoadError: number, NoLoadGlyphs: number, LockBakedSizes: number, ImplicitRefSize: number, [string]: number }
|
export type ImFontFlags = { None: number, NoLoadError: number, NoLoadGlyphs: number, LockBakedSizes: number, ImplicitRefSize: number, [string]: number }
|
||||||
export type ImGuiBackendFlags = { None: number, HasGamepad: number, HasMouseCursors: number, HasSetMousePos: number, RendererHasVtxOffset: number, RendererHasTextures: number, RendererHasViewports: number, PlatformHasViewports: number, HasMouseHoveredViewport: number, HasParentViewport: number, [string]: number }
|
export type ImGuiBackendFlags = { None: number, HasGamepad: number, HasMouseCursors: number, HasSetMousePos: number, RendererHasVtxOffset: number, RendererHasTextures: number, RendererHasViewports: number, PlatformHasViewports: number, HasMouseHoveredViewport: number, HasParentViewport: number, [string]: number }
|
||||||
export type ImGuiButtonFlags = { None: number, MouseButtonLeft: number, MouseButtonRight: number, MouseButtonMiddle: number, EnableNav: number, PressedOnClick: number, PressedOnClickRelease: number, PressedOnClickReleaseAnywhere: number, PressedOnRelease: number, PressedOnDoubleClick: number, PressedOnDragDropHold: number, FlattenChildren: number, AllowOverlap: number, AlignTextBaseLine: number, NoKeyModsAllowed: number, NoHoldingActiveId: number, NoNavFocus: number, NoHoveredOnFocus: number, NoSetKeyOwner: number, NoTestKeyOwner: number, NoFocus: number, MouseButtonMask_: number, PressedOnMask_: number, PressedOnDefault_: number, NoKeyModifiers: number, [string]: number }
|
export type ImGuiButtonFlags = { None: number, MouseButtonLeft: number, MouseButtonRight: number, MouseButtonMiddle: number, EnableNav: number, PressedOnClick: number, PressedOnClickRelease: number, PressedOnClickReleaseAnywhere: number, PressedOnRelease: number, PressedOnDoubleClick: number, PressedOnDragDropHold: number, FlattenChildren: number, AllowOverlap: number, AlignTextBaseLine: number, NoKeyModsAllowed: number, NoHoldingActiveId: number, NoNavFocus: number, NoHoveredOnFocus: number, NoSetKeyOwner: number, NoTestKeyOwner: number, NoFocus: number, MouseButtonMask_: number, PressedOnMask_: number, PressedOnDefault_: number, NoKeyModifiers: number, [string]: number }
|
||||||
@@ -911,12 +979,15 @@ export type ImGuiInputTextFlags = { None: number, CharsDecimal: number, CharsHex
|
|||||||
export type ImGuiItemFlags = { None: number, NoTabStop: number, NoNav: number, NoNavDefaultFocus: number, ButtonRepeat: number, AutoClosePopups: number, AllowDuplicateId: number, Disabled: number, LiveEditOnInputText: number, LiveEditOnInputScalar: number, LiveEditOnInput: number, ReadOnly: number, MixedValue: number, NoWindowHoverableCheck: number, AllowOverlap: number, NoNavDisableMouseHover: number, NoMarkEdited: number, NoFocus: number, Inputable: number, HasSelectionUserData: number, IsMultiSelect: number, Default_: number, [string]: number }
|
export type ImGuiItemFlags = { None: number, NoTabStop: number, NoNav: number, NoNavDefaultFocus: number, ButtonRepeat: number, AutoClosePopups: number, AllowDuplicateId: number, Disabled: number, LiveEditOnInputText: number, LiveEditOnInputScalar: number, LiveEditOnInput: number, ReadOnly: number, MixedValue: number, NoWindowHoverableCheck: number, AllowOverlap: number, NoNavDisableMouseHover: number, NoMarkEdited: number, NoFocus: number, Inputable: number, HasSelectionUserData: number, IsMultiSelect: number, Default_: number, [string]: number }
|
||||||
export type ImGuiItemStatusFlags = { None: number, HoveredRect: number, HasDisplayRect: number, Edited: number, ToggledSelection: number, ToggledOpen: number, HasDeactivated: number, Deactivated: number, HoveredWindow: number, Visible: number, HasClipRect: number, HasShortcut: number, EditedInternal: number, [string]: number }
|
export type ImGuiItemStatusFlags = { None: number, HoveredRect: number, HasDisplayRect: number, Edited: number, ToggledSelection: number, ToggledOpen: number, HasDeactivated: number, Deactivated: number, HoveredWindow: number, Visible: number, HasClipRect: number, HasShortcut: number, EditedInternal: number, [string]: number }
|
||||||
export type ImGuiKey = { None: number, NamedKey_BEGIN: number, Tab: number, LeftArrow: number, RightArrow: number, UpArrow: number, DownArrow: number, PageUp: number, PageDown: number, Home: number, End: number, Insert: number, Delete: number, Backspace: number, Space: number, Enter: number, Escape: number, LeftCtrl: number, RightCtrl: number, Menu: number, K0: number, A: number, K: number, U: number, F1: number, F7: number, F13: number, F19: number, Apostrophe: number, CapsLock: number, Keypad0: number, KeypadDecimal: number, AppBack: number, AppForward: number, Oem102: number, GamepadStart: number, GamepadFaceLeft: number, GamepadDpadLeft: number, GamepadL1: number, GamepadL2: number, GamepadL3: number, GamepadLStickLeft: number, GamepadRStickLeft: number, MouseLeft: number, ReservedForModCtrl: number, NamedKey_END: number, NamedKey_COUNT: number, NavKeyboardTweakSlow: number, NavKeyboardTweakFast: number, NavGamepadTweakSlow: number, NavGamepadTweakFast: number, NavGamepadMenu: number, NavGamepadContextMenu: number, [string]: number }
|
export type ImGuiKey = { None: number, NamedKey_BEGIN: number, Tab: number, LeftArrow: number, RightArrow: number, UpArrow: number, DownArrow: number, PageUp: number, PageDown: number, Home: number, End: number, Insert: number, Delete: number, Backspace: number, Space: number, Enter: number, Escape: number, LeftCtrl: number, RightCtrl: number, Menu: number, K0: number, A: number, K: number, U: number, F1: number, F7: number, F13: number, F19: number, Apostrophe: number, CapsLock: number, Keypad0: number, KeypadDecimal: number, AppBack: number, AppForward: number, Oem102: number, GamepadStart: number, GamepadFaceLeft: number, GamepadDpadLeft: number, GamepadL1: number, GamepadL2: number, GamepadL3: number, GamepadLStickLeft: number, GamepadRStickLeft: number, MouseLeft: number, ReservedForModCtrl: number, NamedKey_END: number, NamedKey_COUNT: number, NavKeyboardTweakSlow: number, NavKeyboardTweakFast: number, NavGamepadTweakSlow: number, NavGamepadTweakFast: number, NavGamepadMenu: number, NavGamepadContextMenu: number, [string]: number }
|
||||||
|
export type ImGuiListClipperFlags = { None: number, NoSetTableRowCounters: number, [string]: number }
|
||||||
export type ImGuiMouseSource = { Mouse: number, TouchScreen: number, Pen: number, COUNT: number, [string]: number }
|
export type ImGuiMouseSource = { Mouse: number, TouchScreen: number, Pen: number, COUNT: number, [string]: number }
|
||||||
|
export type ImGuiMultiSelectFlags = { None: number, SingleSelect: number, NoSelectAll: number, NoRangeSelect: number, NoAutoSelect: number, NoAutoClear: number, NoAutoClearOnReselect: number, BoxSelect1d: number, BoxSelect2d: number, BoxSelectNoScroll: number, ClearOnEscape: number, ClearOnClickVoid: number, ScopeWindow: number, ScopeRect: number, SelectOnAuto: number, SelectOnClickAlways: number, SelectOnClickRelease: number, NavWrapX: number, NoSelectOnRightClick: number, CheckboxMode_: number, SelectOnMask_: number, SelectOnClick: number, [string]: number }
|
||||||
export type ImGuiNextItemDataFlags = { None: number, HasWidth: number, HasOpen: number, HasShortcut: number, HasRefVal: number, HasStorageID: number, HasColorMarker: number, [string]: number }
|
export type ImGuiNextItemDataFlags = { None: number, HasWidth: number, HasOpen: number, HasShortcut: number, HasRefVal: number, HasStorageID: number, HasColorMarker: number, [string]: number }
|
||||||
export type ImGuiPopupFlags = { None: number, MouseButtonLeft: number, MouseButtonRight: number, MouseButtonMiddle: number, NoReopen: number, NoOpenOverExistingPopup: number, NoOpenOverItems: number, AnyPopupId: number, AnyPopupLevel: number, AnyPopup: number, MouseButtonShift_: number, MouseButtonMask_: number, InvalidMask_: number, [string]: number }
|
export type ImGuiPopupFlags = { None: number, MouseButtonLeft: number, MouseButtonRight: number, MouseButtonMiddle: number, NoReopen: number, NoOpenOverExistingPopup: number, NoOpenOverItems: number, AnyPopupId: number, AnyPopupLevel: number, AnyPopup: number, MouseButtonShift_: number, MouseButtonMask_: number, InvalidMask_: number, [string]: number }
|
||||||
export type ImGuiSelectableFlags = { None: number, NoAutoClosePopups: number, SpanAllColumns: number, AllowDoubleClick: number, Disabled: number, AllowOverlap: number, Highlight: number, SelectOnNav: number, NoHoldingActiveID: number, SelectOnClick: number, SelectOnRelease: number, SpanAvailWidth: number, SetNavIdOnHover: number, NoPadWithHalfSpacing: number, NoSetKeyOwner: number, [string]: number }
|
export type ImGuiSelectableFlags = { None: number, NoAutoClosePopups: number, SpanAllColumns: number, AllowDoubleClick: number, Disabled: number, AllowOverlap: number, Highlight: number, SelectOnNav: number, NoHoldingActiveID: number, SelectOnClick: number, SelectOnRelease: number, SpanAvailWidth: number, SetNavIdOnHover: number, NoPadWithHalfSpacing: number, NoSetKeyOwner: number, [string]: number }
|
||||||
|
export type ImGuiSelectionRequestType = { None: number, SetAll: number, SetRange: number, [string]: number }
|
||||||
export type ImGuiSliderFlags = { None: number, Logarithmic: number, NoRoundToFormat: number, NoInput: number, WrapAround: number, ClampOnInput: number, ClampZeroRange: number, NoSpeedTweaks: number, ColorMarkers: number, InvalidMask_: number, AlwaysClamp: number, Vertical: number, ReadOnly: number, [string]: number }
|
export type ImGuiSliderFlags = { None: number, Logarithmic: number, NoRoundToFormat: number, NoInput: number, WrapAround: number, ClampOnInput: number, ClampZeroRange: number, NoSpeedTweaks: number, ColorMarkers: number, InvalidMask_: number, AlwaysClamp: number, Vertical: number, ReadOnly: number, [string]: number }
|
||||||
export type ImGuiStyleVar = { Alpha: number, DisabledAlpha: number, WindowPadding: number, WindowRounding: number, WindowBorderSize: number, WindowMinSize: number, WindowTitleAlign: number, ChildRounding: number, ChildBorderSize: number, PopupRounding: number, PopupBorderSize: number, FramePadding: number, FrameRounding: number, FrameBorderSize: number, ItemSpacing: number, ItemInnerSpacing: number, IndentSpacing: number, CellPadding: number, ScrollbarSize: number, ScrollbarRounding: number, ScrollbarPadding: number, GrabMinSize: number, GrabRounding: number, ImageRounding: number, ImageBorderSize: number, TabRounding: number, TabBorderSize: number, TabMinWidthBase: number, TabMinWidthShrink: number, TabBarBorderSize: number, TabBarOverlineSize: number, TableAngledHeadersAngle: number, TableAngledHeadersTextAlign: number, TreeLinesSize: number, TreeLinesRounding: number, MenuItemRounding: number, SelectableRounding: number, DragDropTargetRounding: number, ButtonTextAlign: number, SelectableTextAlign: number, SeparatorTextBorderSize: number, SeparatorTextAlign: number, SeparatorTextPadding: number, COUNT: number, [string]: number }
|
export type ImGuiStyleVar = { Alpha: number, DisabledAlpha: number, WindowPadding: number, WindowRounding: number, WindowBorderSize: number, WindowMinSize: number, WindowTitleAlign: number, ChildRounding: number, ChildBorderSize: number, PopupRounding: number, PopupBorderSize: number, FramePadding: number, FrameRounding: number, FrameBorderSize: number, ItemSpacing: number, ItemInnerSpacing: number, IndentSpacing: number, CellPadding: number, ScrollbarSize: number, ScrollbarRounding: number, ScrollbarPadding: number, GrabMinSize: number, GrabRounding: number, ImageRounding: number, ImageBorderSize: number, TabRounding: number, TabBorderSize: number, TabMinWidthBase: number, TabMinWidthShrink: number, TabBarBorderSize: number, TabBarOverlineSize: number, TableAngledHeadersAngle: number, TableAngledHeadersTextAlign: number, TreeLinesSize: number, TreeLinesRounding: number, MenuItemRounding: number, SelectableRounding: number, DragDropTargetRounding: number, ButtonTextAlign: number, SelectableTextAlign: number, SeparatorSize: number, SeparatorTextBorderSize: number, SeparatorTextAlign: number, SeparatorTextPadding: number, DockingSeparatorSize: number, COUNT: number, [string]: number }
|
||||||
export type ImGuiTreeNodeFlags = { None: number, Selected: number, Framed: number, AllowOverlap: number, NoTreePushOnOpen: number, NoAutoOpenOnLog: number, DefaultOpen: number, OpenOnDoubleClick: number, OpenOnArrow: number, Leaf: number, Bullet: number, FramePadding: number, SpanAvailWidth: number, SpanFullWidth: number, SpanLabelWidth: number, SpanAllColumns: number, LabelSpanAllColumns: number, NavLeftJumpsToParent: number, DrawLinesNone: number, DrawLinesFull: number, DrawLinesToNodes: number, NoNavFocus: number, ClipLabelForTrailingButton: number, UpsideDownArrow: number, CollapsingHeader: number, OpenOnMask_: number, DrawLinesMask_: number, [string]: number }
|
export type ImGuiTreeNodeFlags = { None: number, Selected: number, Framed: number, AllowOverlap: number, NoTreePushOnOpen: number, NoAutoOpenOnLog: number, DefaultOpen: number, OpenOnDoubleClick: number, OpenOnArrow: number, Leaf: number, Bullet: number, FramePadding: number, SpanAvailWidth: number, SpanFullWidth: number, SpanLabelWidth: number, SpanAllColumns: number, LabelSpanAllColumns: number, NavLeftJumpsToParent: number, DrawLinesNone: number, DrawLinesFull: number, DrawLinesToNodes: number, NoNavFocus: number, ClipLabelForTrailingButton: number, UpsideDownArrow: number, CollapsingHeader: number, OpenOnMask_: number, DrawLinesMask_: number, [string]: number }
|
||||||
export type ImGuiViewportFlags = { None: number, IsPlatformWindow: number, IsPlatformMonitor: number, OwnedByApp: number, NoDecoration: number, NoTaskBarIcon: number, NoFocusOnAppearing: number, NoFocusOnClick: number, NoInputs: number, NoRendererClear: number, NoAutoMerge: number, TopMost: number, CanHostOtherWindows: number, IsMinimized: number, IsFocused: number, [string]: number }
|
export type ImGuiViewportFlags = { None: number, IsPlatformWindow: number, IsPlatformMonitor: number, OwnedByApp: number, NoDecoration: number, NoTaskBarIcon: number, NoFocusOnAppearing: number, NoFocusOnClick: number, NoInputs: number, NoRendererClear: number, NoAutoMerge: number, TopMost: number, CanHostOtherWindows: number, IsMinimized: number, IsFocused: number, [string]: number }
|
||||||
export type ImGuiWindowFlags = { None: number, NoTitleBar: number, NoResize: number, NoMove: number, NoScrollbar: number, NoScrollWithMouse: number, NoCollapse: number, AlwaysAutoResize: number, NoBackground: number, NoSavedSettings: number, NoMouseInputs: number, MenuBar: number, HorizontalScrollbar: number, NoFocusOnAppearing: number, NoBringToFrontOnFocus: number, AlwaysVerticalScrollbar: number, AlwaysHorizontalScrollbar: number, NoNavInputs: number, NoNavFocus: number, UnsavedDocument: number, NoDocking: number, DockNodeHost: number, ChildWindow: number, Tooltip: number, Popup: number, Modal: number, ChildMenu: number, NoNav: number, NoDecoration: number, NoInputs: number, [string]: number }
|
export type ImGuiWindowFlags = { None: number, NoTitleBar: number, NoResize: number, NoMove: number, NoScrollbar: number, NoScrollWithMouse: number, NoCollapse: number, AlwaysAutoResize: number, NoBackground: number, NoSavedSettings: number, NoMouseInputs: number, MenuBar: number, HorizontalScrollbar: number, NoFocusOnAppearing: number, NoBringToFrontOnFocus: number, AlwaysVerticalScrollbar: number, AlwaysHorizontalScrollbar: number, NoNavInputs: number, NoNavFocus: number, UnsavedDocument: number, NoDocking: number, DockNodeHost: number, ChildWindow: number, Tooltip: number, Popup: number, Modal: number, ChildMenu: number, NoNav: number, NoDecoration: number, NoInputs: number, [string]: number }
|
||||||
@@ -943,10 +1014,13 @@ export type ImGuiModule = {
|
|||||||
ImGuiItemFlags: ImGuiItemFlags,
|
ImGuiItemFlags: ImGuiItemFlags,
|
||||||
ImGuiItemStatusFlags: ImGuiItemStatusFlags,
|
ImGuiItemStatusFlags: ImGuiItemStatusFlags,
|
||||||
ImGuiKey: ImGuiKey,
|
ImGuiKey: ImGuiKey,
|
||||||
|
ImGuiListClipperFlags: ImGuiListClipperFlags,
|
||||||
ImGuiMouseSource: ImGuiMouseSource,
|
ImGuiMouseSource: ImGuiMouseSource,
|
||||||
|
ImGuiMultiSelectFlags: ImGuiMultiSelectFlags,
|
||||||
ImGuiNextItemDataFlags: ImGuiNextItemDataFlags,
|
ImGuiNextItemDataFlags: ImGuiNextItemDataFlags,
|
||||||
ImGuiPopupFlags: ImGuiPopupFlags,
|
ImGuiPopupFlags: ImGuiPopupFlags,
|
||||||
ImGuiSelectableFlags: ImGuiSelectableFlags,
|
ImGuiSelectableFlags: ImGuiSelectableFlags,
|
||||||
|
ImGuiSelectionRequestType: ImGuiSelectionRequestType,
|
||||||
ImGuiSliderFlags: ImGuiSliderFlags,
|
ImGuiSliderFlags: ImGuiSliderFlags,
|
||||||
ImGuiStyleVar: ImGuiStyleVar,
|
ImGuiStyleVar: ImGuiStyleVar,
|
||||||
ImGuiTreeNodeFlags: ImGuiTreeNodeFlags,
|
ImGuiTreeNodeFlags: ImGuiTreeNodeFlags,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[[149, "imgui.lua"], [18647, "imgui_demo.lua"], [25932, "imgui_draw.lua"], [31170, "imgui_h.lua"], [33368, "imgui_impl_love.lua"], [33908, "imgui_internal.lua"], [37084, "imgui_tables.lua"], [41431, "imgui_widgets.lua"], [51186, "imstb_rectpack.lua"], [51632, "imstb_textedit.lua"], [52873, "imstb_truetype.lua"], [55341, "imstd_minstdio.lua"]]
|
[[183, "imgui.lua"], [22812, "imgui_demo.lua"], [34670, "imgui_draw.lua"], [42997, "imgui_h.lua"], [45994, "imgui_impl_love.lua"], [46538, "imgui_internal.lua"], [50510, "imgui_tables.lua"], [55602, "imgui_widgets.lua"], [67836, "imstb_rectpack.lua"], [68500, "imstb_textedit.lua"], [70042, "imstb_truetype.lua"], [72830, "imstd_minstdio.lua"]]
|
||||||
+31712
-14094
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -65,5 +65,5 @@ there, but keep this port's existing data structures and conventions. Read the s
|
|||||||
|
|
||||||
- **ImGuiAxis is 1-based in this port** (`None=0, X=1, Y=2`; upstream `None=-1, X=0, Y=1`). Use `v[axis]` to index an ImVec2 and `3 - axis` for the other axis. `ImGuiDir` keeps upstream values (`None=-1, Left=0, Right=1, Up=2, Down=3`).
|
- **ImGuiAxis is 1-based in this port** (`None=0, X=1, Y=2`; upstream `None=-1, X=0, Y=1`). Use `v[axis]` to index an ImVec2 and `3 - axis` for the other axis. `ImGuiDir` keeps upstream values (`None=-1, Left=0, Right=1, Up=2, Down=3`).
|
||||||
- Never write `cond and nil or x` in Lua: it always evaluates `x`. Use an `if`.
|
- Never write `cond and nil or x` in Lua: it always evaluates `x`. Use an `if`.
|
||||||
- Port signature quirk: `AddRect(min, max, col, rounding, thickness, flags)` (thickness BEFORE flags; upstream has flags first). Check the port's signature before porting any draw call that takes both.\n
|
- Draw calls taking both thickness and flags use upstream's 1.92.8+ order: thickness BEFORE flags, e.g. `AddRect(min, max, col, rounding, thickness, flags)`, `PathStroke(col, thickness, flags)`, `AddPolyline(points, count, col, thickness, flags)`.
|
||||||
- C++ default arguments (e.g. `bool x = true`) must be reproduced with `if x == nil then x = true end`; a missing Lua argument is nil/falsy.
|
- C++ default arguments (e.g. `bool x = true`) must be reproduced with `if x == nil then x = true end`; a missing Lua argument is nil/falsy.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+19
-38
@@ -1,52 +1,33 @@
|
|||||||
Dear ImGui for Roblox - not yet ported / known gaps
|
Dear ImGui for Roblox - not yet ported / known gaps
|
||||||
====================================================
|
====================================================
|
||||||
Upstream reference: tools/test/upstream/ (ocornut/imgui docking branch, 1.93 WIP).
|
Upstream reference: tools/test/upstream/ (ocornut/imgui docking branch, 1.93 WIP).
|
||||||
Re-check with: python3 tools/test/missing.py [imgui|widgets|tables|draw] and python3 tools/test/undefined.py --no-demo
|
Re-check with: python3 tools/compare_upstream.py (writes docs/PORT_STATUS.txt: [ ] = not ported, [=] = replaced by a
|
||||||
(missing.py also lists names the port defines differently, e.g. the *V printf variants and DragFloat2..4 / DragInt2..4,
|
Lua equivalent, "missing:" = enum values / struct fields) and python3 tools/test/undefined.py --no-demo
|
||||||
which exist as ImGui.DragFloat2 = DragFloatN(2) etc. Those are NOT gaps.)
|
|
||||||
|
|
||||||
|
|
||||||
FEATURES NOT PORTED
|
NOT PORTED / PARTIAL
|
||||||
-------------------
|
--------------------
|
||||||
- Multi-select / box-select: BeginMultiSelect, EndMultiSelect, MultiSelectItemHeader/Footer, MultiSelectAddSetAll,
|
- ImFont::AddRemapCodepointToGlyphIndex / ImGui_ImplStbTrueType_FontSrcGetGlyphIndexFromCodepoint (the port's font
|
||||||
MultiSelectAddSetRange, SetNextItemSelectionUserData, BeginBoxSelect/EndBoxSelect, DebugNodeMultiSelectState.
|
loader still checks glyphs by codepoint; 1.93 split this into codepoint -> glyph index -> glyph).
|
||||||
(Selectable/TreeNode call sites are stubbed with TODOs; the demo "Selection State & Multi-Select" section is not ported.)
|
- ImGui_GetDefaultCompressedFontDataProggyClean/ProggyForever: AddFontFromMemoryCompressedTTF works, but the default
|
||||||
- Typing-select (type to jump in lists): GetTypingSelectRequest, TypingSelectFind*, DebugNodeTypingSelectState.
|
fonts are loaded from fonts/ instead of embedded compressed data.
|
||||||
- Examples > Assets Browser (needs multi-select + box-select).
|
- ImFontAtlasDebugLogTextureRequests, ImHashSkipUncontributingPrefix.
|
||||||
- Metrics > Tools > Item Picker (UpdateDebugToolItemPicker).
|
- DebugLogSkippedErrors / DebugDrawIdConflicts* (error-recovery and ID-conflict debug tools).
|
||||||
- DebugFlashStyleColor / UpdateDebugToolFlashStyleColor (Style Editor "?" flash button).
|
|
||||||
- DebugNodeInputTextState (Metrics > Internal state > InputText).
|
LUA LIMITATIONS
|
||||||
- BringWindowToDisplayBehind: stub, does nothing.
|
---------------
|
||||||
- ColorEdit4 / ColorPicker4: drag & drop target (dropping a color onto a color widget).
|
- ImGuiDataType.S64/U64: values are Luau doubles, exact only within +-2^53 (LLONG_MAX/ULLONG_MAX round up).
|
||||||
- ImFontAtlas:AddFontFromMemoryCompressedTTF: stub (binary_to_compressed_c fonts can't be loaded).
|
- ImDrawList::AddCallback(userdata_size > 0): the "copy" is a shallow copy of a table userdata (no raw bytes).
|
||||||
- ImGuiSizeCallbackData: SetNextWindowSizeConstraints custom callbacks only partially supported
|
|
||||||
(Examples > Constrained Resize).
|
|
||||||
- LogSetNextTextDecoration (Selectable "[ ]" decoration in log/clipboard output).
|
|
||||||
- stb_truetype: cmap high-byte mapping (some CJK fonts assert).
|
|
||||||
- Context hooks removal (RemoveContextHook), SetContextName.
|
|
||||||
- RegisterUserTexture / UnregisterUserTexture.
|
|
||||||
- ErrorCheckNewFrameSanityChecks / ErrorCheckEndFrameSanityChecks (fewer usage-error asserts than upstream).
|
|
||||||
- GetAllocatorFunctions (n/a in Luau).
|
|
||||||
|
|
||||||
WON'T DO (platform)
|
WON'T DO (platform)
|
||||||
-------------------
|
-------------------
|
||||||
- Multi-viewports / platform windows: Roblox has one window. Viewport API exists for the main viewport only.
|
- Multi-viewports / platform windows: Roblox has one window. The viewport API covers the main viewport only.
|
||||||
- Gamepad navigation via a real gamepad backend (keyboard navigation works).
|
- Gamepad navigation via a real gamepad backend (keyboard navigation works).
|
||||||
- IME / non-ASCII keyboard text input (backend sends printable ASCII only).
|
- IME / non-ASCII keyboard text input (backend sends printable ASCII only).
|
||||||
|
|
||||||
NOT VERIFIED LINE BY LINE AGAINST UPSTREAM
|
|
||||||
------------------------------------------
|
|
||||||
- Demo: Layout & Scrolling, Popups & Modal windows, Inputs & Focus.
|
|
||||||
|
|
||||||
KNOWN ISSUES
|
KNOWN ISSUES
|
||||||
------------
|
------------
|
||||||
- Tables > "Columns flags": disabling every column asserts in TableUpdateLayout (upstream asserts too).
|
- Tables > "Columns flags": disabling every column asserts in TableUpdateLayout (upstream asserts too).
|
||||||
- Style Editor > Colors uses a ListClipper (port-only change, upstream submits every row).
|
- stb_truetype: cmap format 2 (high-byte mapping) asserts (same "@TODO" as upstream stb_truetype).
|
||||||
|
- ImTriangulator differs from upstream (imgui_draw.lua, [Luau]): CCW input is reversed first and only strictly
|
||||||
PERFORMANCE IDEAS (not done)
|
counter-clockwise corners are reflex, so CCW and duplicate-point polygons triangulate differently from upstream.
|
||||||
----------------------------
|
|
||||||
- Cheaper visible ColorEdit4 rows; cheaper Begin() for child windows.
|
|
||||||
|
|
||||||
PLANNED EXTENSIONS
|
|
||||||
------------------
|
|
||||||
- ImGuizmo, ImNodes, ImGuiColorTextEdit, ImPlot, ImPlot3D (as separate extensions/, built with a --with flag).
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ setfenv(1, setmetatable({}, {
|
|||||||
|
|
||||||
local MainScale = 1.0
|
local MainScale = 1.0
|
||||||
local RenderScale = 1.0
|
local RenderScale = 1.0
|
||||||
local RenderRate = 60
|
local RenderRate = 60 -- max tile redraws per second (0 = every frame, limited by the redraw budget instead)
|
||||||
local ThumbnailSize = 420
|
local ThumbnailSize = 420
|
||||||
local FontSize = 13.0
|
local FontSize = 13.0
|
||||||
local FontNames = { "Cousine-Regular", "DroidSans", "Karla-Regular", "ProggyTiny", "Roboto-Medium" }
|
local FontNames = { "Cousine-Regular", "DroidSans", "Karla-Regular", "ProggyTiny", "Roboto-Medium" }
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
-- One UI that runs unchanged on Roblox and LÖVE: only the Dear ImGui API is used, and the code is plain Lua 5.1
|
|
||||||
-- (no Luau-only syntax), so both Luau and LuaJIT run it. The platform launchers are roblox.client.luau and main.lua.
|
|
||||||
-- local App = require(app)(G) -- G = the table returned by the ImGui bundle
|
|
||||||
-- every frame, between ImGui.NewFrame() and ImGui.Render(): App.Frame()
|
|
||||||
return function(G)
|
|
||||||
local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4
|
|
||||||
local App = {
|
|
||||||
Platform = "?", -- set by the launcher ("Roblox" / "LÖVE")
|
|
||||||
ClearColor = ImVec4(0.45, 0.55, 0.60, 1.00), -- launchers may use it as the background colour
|
|
||||||
}
|
|
||||||
local show_demo, show_table = false, true
|
|
||||||
local counter, speed = 0, 1.0
|
|
||||||
local name = ImGui.NewTextBuffer("Player", 64)
|
|
||||||
local samples, t = {}, 0.0
|
|
||||||
local items = { { "Sword", 1, 120 }, { "Potion", 5, 15 }, { "Arrow", 64, 1 }, { "Shield", 1, 80 } }
|
|
||||||
|
|
||||||
function App.Frame()
|
|
||||||
local io = ImGui.GetIO()
|
|
||||||
t = t + io.DeltaTime * speed
|
|
||||||
for i = 1, 90 do samples[i] = math.sin(i / 90 * math.pi * 4 + t * 3) end
|
|
||||||
|
|
||||||
ImGui.SetNextWindowPos(ImVec2(20, 20), G.ImGuiCond.FirstUseEver)
|
|
||||||
ImGui.SetNextWindowSize(ImVec2(420, 360), G.ImGuiCond.FirstUseEver)
|
|
||||||
ImGui.Begin("Shared Example")
|
|
||||||
ImGui.Text("Running on %s (%.1f FPS)", App.Platform, io.Framerate)
|
|
||||||
ImGui.Separator()
|
|
||||||
ImGui.InputText("name", name, 64)
|
|
||||||
if ImGui.Button("Click me") then counter = counter + 1 end
|
|
||||||
ImGui.SameLine(); ImGui.Text("%s clicked %d times", ImGui.TextBufferToString(name), counter)
|
|
||||||
speed = ImGui.SliderFloat("speed", speed, 0.0, 4.0)
|
|
||||||
ImGui.ColorEdit3("background", App.ClearColor)
|
|
||||||
ImGui.PlotLines("wave", samples, nil, #samples, 0, nil, -1.0, 1.0, ImVec2(0, 60))
|
|
||||||
local _
|
|
||||||
_, show_table = ImGui.Checkbox("Inventory table", show_table)
|
|
||||||
ImGui.SameLine(); _, show_demo = ImGui.Checkbox("Demo window", show_demo)
|
|
||||||
if show_table and ImGui.BeginTable("inv", 3, bit32.bor(G.ImGuiTableFlags.Borders, G.ImGuiTableFlags.RowBg, G.ImGuiTableFlags.Sortable)) then
|
|
||||||
ImGui.TableSetupColumn("Item"); ImGui.TableSetupColumn("Count"); ImGui.TableSetupColumn("Value")
|
|
||||||
ImGui.TableHeadersRow()
|
|
||||||
for _, it in ipairs(items) do
|
|
||||||
ImGui.TableNextRow()
|
|
||||||
ImGui.TableNextColumn(); ImGui.Text(it[1])
|
|
||||||
ImGui.TableNextColumn(); ImGui.Text("%d", it[2])
|
|
||||||
ImGui.TableNextColumn(); ImGui.Text("%d", it[3])
|
|
||||||
end
|
|
||||||
ImGui.EndTable()
|
|
||||||
end
|
|
||||||
ImGui.End()
|
|
||||||
|
|
||||||
if show_demo and ImGui.ShowDemoWindow then show_demo = ImGui.ShowDemoWindow(show_demo) end
|
|
||||||
end
|
|
||||||
|
|
||||||
return App
|
|
||||||
end
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
function love.conf(t)
|
|
||||||
t.window.title = "Dear ImGui - shared example (LÖVE)"
|
|
||||||
t.window.width, t.window.height = 1280, 800
|
|
||||||
t.window.resizable = true
|
|
||||||
end
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "imgui-shared-example",
|
|
||||||
"tree": {
|
|
||||||
"$className": "DataModel",
|
|
||||||
"ReplicatedStorage": {
|
|
||||||
"ImGui": { "$path": "../../build/ImGuiTyped.luau", "Impl": { "$path": "../../build/ImGui.luau" } },
|
|
||||||
"SharedApp": { "$path": "app.lua" }
|
|
||||||
},
|
|
||||||
"StarterPlayer": {
|
|
||||||
"StarterPlayerScripts": {
|
|
||||||
"SharedExample": { "$path": "roblox.client.luau" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
-- LÖVE launcher for app.lua: python3 tools/bundle.py --target love then love examples/example_shared
|
|
||||||
local ROOT = love.filesystem.getSource() .. "/../../" -- repo root (build/, fonts/)
|
|
||||||
local function load_bundle()
|
|
||||||
local f = assert(io.open(ROOT .. "build/imgui_love.lua", "rb"), "run: python3 tools/bundle.py --target love")
|
|
||||||
local src = f:read("*a"); f:close()
|
|
||||||
return assert(loadstring(src, "@imgui_love.lua"))()
|
|
||||||
end
|
|
||||||
|
|
||||||
local G, ImGui, App
|
|
||||||
|
|
||||||
function love.load()
|
|
||||||
G = load_bundle()
|
|
||||||
ImGui = G.ImGui
|
|
||||||
ImGui.CreateContext()
|
|
||||||
local io = ImGui.GetIO()
|
|
||||||
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.NavEnableKeyboard, G.ImGuiConfigFlags.DockingEnable)
|
|
||||||
io.IniFilename = nil
|
|
||||||
G.ImGui_ImplLove.Init({ root = ROOT })
|
|
||||||
App = love.filesystem.load("app.lua")()(G)
|
|
||||||
App.Platform = "LÖVE"
|
|
||||||
end
|
|
||||||
|
|
||||||
function love.update(dt)
|
|
||||||
G.ImGui_ImplLove.NewFrame()
|
|
||||||
ImGui.NewFrame()
|
|
||||||
App.Frame()
|
|
||||||
ImGui.Render()
|
|
||||||
end
|
|
||||||
|
|
||||||
function love.draw()
|
|
||||||
local c = App.ClearColor
|
|
||||||
love.graphics.clear(c.x, c.y, c.z, 1)
|
|
||||||
G.ImGui_ImplLove.RenderDrawData(ImGui.GetDrawData())
|
|
||||||
end
|
|
||||||
|
|
||||||
function love.mousemoved(x, y) G.ImGui_ImplLove.MouseMoved(x, y) end
|
|
||||||
function love.mousepressed(x, y, b) G.ImGui_ImplLove.MousePressed(x, y, b) end
|
|
||||||
function love.mousereleased(x, y, b) G.ImGui_ImplLove.MouseReleased(x, y, b) end
|
|
||||||
function love.wheelmoved(x, y) G.ImGui_ImplLove.WheelMoved(x, y) end
|
|
||||||
function love.keypressed(k) G.ImGui_ImplLove.KeyPressed(k) end
|
|
||||||
function love.keyreleased(k) G.ImGui_ImplLove.KeyReleased(k) end
|
|
||||||
function love.textinput(t) G.ImGui_ImplLove.TextInput(t) end
|
|
||||||
function love.focus(f) G.ImGui_ImplLove.Focus(f) end
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
-- Roblox launcher for app.lua (sync with examples/example_shared/default.project.json).
|
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
||||||
local RunService = game:GetService("RunService")
|
|
||||||
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
|
|
||||||
local ImGui = G.ImGui
|
|
||||||
|
|
||||||
ImGui.CreateContext()
|
|
||||||
local io = ImGui.GetIO()
|
|
||||||
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.NavEnableKeyboard, G.ImGuiConfigFlags.DockingEnable)
|
|
||||||
G.ImGui_ImplRoblox.Init()
|
|
||||||
|
|
||||||
local App = require(ReplicatedStorage:WaitForChild("SharedApp"))(G)
|
|
||||||
App.Platform = "Roblox"
|
|
||||||
|
|
||||||
RunService.Stepped:Connect(function()
|
|
||||||
G.ImGui_ImplRoblox.NewFrame()
|
|
||||||
ImGui.NewFrame()
|
|
||||||
App.Frame()
|
|
||||||
ImGui.Render()
|
|
||||||
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
|
||||||
end)
|
|
||||||
+11036
-6463
File diff suppressed because it is too large
Load Diff
+3941
-852
File diff suppressed because it is too large
Load Diff
+1536
-737
File diff suppressed because it is too large
Load Diff
+1462
-666
File diff suppressed because it is too large
Load Diff
+965
-220
File diff suppressed because it is too large
Load Diff
+3070
-591
File diff suppressed because it is too large
Load Diff
+229
-11
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
+31
-14
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
-- Supports:
|
-- Supports:
|
||||||
-- - %x, %X, %nx, %nX (where n is a number)
|
-- - %x, %X, %nx, %nX (where n is a number)
|
||||||
-- - %d, %nd (where n is a number)
|
-- - %d, %nd, %u, %nu (where n is a number)
|
||||||
|
-- - length modifiers h, hh, l, ll, I64 on integers: ll/I64 = 64-bit range (doubles: exact only within +-2^53)
|
||||||
-- - %f, %lf (very limited)
|
-- - %f, %lf (very limited)
|
||||||
--
|
--
|
||||||
-- Returns items_matched instead of EOF on error
|
-- Returns items_matched instead of EOF on error
|
||||||
@@ -17,6 +18,9 @@ local CHAR_0, CHAR_9 = _byte'0', _byte'9'
|
|||||||
local CHAR_a, CHAR_A = _byte'a', _byte'A'
|
local CHAR_a, CHAR_A = _byte'a', _byte'A'
|
||||||
local CHAR_d = _byte'd'
|
local CHAR_d = _byte'd'
|
||||||
local CHAR_l = _byte'l'
|
local CHAR_l = _byte'l'
|
||||||
|
local CHAR_h = _byte'h'
|
||||||
|
local CHAR_u = _byte'u'
|
||||||
|
local CHAR_I = _byte'I'
|
||||||
local CHAR_f, CHAR_F = _byte'f', _byte'F'
|
local CHAR_f, CHAR_F = _byte'f', _byte'F'
|
||||||
local CHAR_x, CHAR_X = _byte'x', _byte'X'
|
local CHAR_x, CHAR_X = _byte'x', _byte'X'
|
||||||
|
|
||||||
@@ -37,10 +41,15 @@ local function hex_digit_to_int(c)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- we force this behavior by default
|
-- we force this behavior by default
|
||||||
local function clamp_if_overflow(spec, val)
|
local function clamp_if_overflow(spec, val, is_64)
|
||||||
if spec == CHAR_d or spec == CHAR_x or spec == CHAR_X then
|
if spec == CHAR_d or spec == CHAR_x or spec == CHAR_X then
|
||||||
if val > INT_MAX then return INT_MAX end
|
local max = is_64 and 9223372036854775807 or INT_MAX
|
||||||
if val < INT_MIN then return INT_MIN end
|
local min = is_64 and -9223372036854775808 or INT_MIN
|
||||||
|
if val > max then return max end
|
||||||
|
if val < min then return min end
|
||||||
|
elseif spec == CHAR_u then
|
||||||
|
local max = is_64 and 18446744073709551615 or UINT_MAX
|
||||||
|
if val > max then return max end
|
||||||
end
|
end
|
||||||
|
|
||||||
return val
|
return val
|
||||||
@@ -104,22 +113,27 @@ local function sscanf(buffer, buffer_begin, format, result)
|
|||||||
width = math.huge
|
width = math.huge
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- length modifiers
|
||||||
|
local n_l = 0
|
||||||
|
while _byte(format, f, f) == CHAR_l or _byte(format, f, f) == CHAR_h do
|
||||||
|
if _byte(format, f, f) == CHAR_l then n_l = n_l + 1 end
|
||||||
|
f = f + 1
|
||||||
|
end
|
||||||
|
if _byte(format, f, f) == CHAR_I and string.sub(format, f, f + 2) == "I64" then
|
||||||
|
n_l = 2
|
||||||
|
f = f + 3
|
||||||
|
end
|
||||||
|
local is_64 = n_l >= 2
|
||||||
|
|
||||||
local spec = _byte(format, f, f)
|
local spec = _byte(format, f, f)
|
||||||
local base
|
local base
|
||||||
local is_float = false
|
local is_float = false
|
||||||
if spec == CHAR_x or spec == CHAR_X then
|
if spec == CHAR_x or spec == CHAR_X then
|
||||||
base = 16
|
base = 16
|
||||||
elseif spec == CHAR_d then
|
elseif spec == CHAR_d or spec == CHAR_u then
|
||||||
base = 10
|
base = 10
|
||||||
elseif spec == CHAR_f or spec == CHAR_F then
|
elseif spec == CHAR_f or spec == CHAR_F then
|
||||||
is_float = true
|
is_float = true -- TODO: distinguish %lf from %f?
|
||||||
elseif spec == CHAR_l then
|
|
||||||
f = f + 1
|
|
||||||
if _byte(format, f, f) == CHAR_f then
|
|
||||||
is_float = true -- TODO: distinguish from float?
|
|
||||||
else
|
|
||||||
return items_matched
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
return items_matched
|
return items_matched
|
||||||
end
|
end
|
||||||
@@ -197,11 +211,14 @@ local function sscanf(buffer, buffer_begin, format, result)
|
|||||||
val = val * base + sign * hex_digit_to_int(c)
|
val = val * base + sign * hex_digit_to_int(c)
|
||||||
|
|
||||||
local old_val = val
|
local old_val = val
|
||||||
val = clamp_if_overflow(spec, val)
|
val = clamp_if_overflow(spec, val, is_64)
|
||||||
if old_val ~= val then
|
if old_val ~= val then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if spec == CHAR_u and val < 0 then
|
||||||
|
val = val + (is_64 and 18446744073709551616 or 4294967296) -- unsigned wrap-around like strtoul()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
result[assigned + 1] = val
|
result[assigned + 1] = val
|
||||||
|
|||||||
+222
-14
@@ -7,15 +7,34 @@
|
|||||||
# Each source file becomes a function in __FILES; IM_INCLUDE(name) runs it once and caches the result.
|
# Each source file becomes a function in __FILES; IM_INCLUDE(name) runs it once and caches the result.
|
||||||
# The module returns a table with every global the library defines (ImGui, ImVec2, ImGuiConfigFlags, ImGui_ImplRoblox, ...).
|
# The module returns a table with every global the library defines (ImGui, ImVec2, ImGuiConfigFlags, ImGui_ImplRoblox, ...).
|
||||||
# Roblox target also writes ImGuiTyped.luau (types + require(script.Impl), see tools/gen_types.py).
|
# Roblox target also writes ImGuiTyped.luau (types + require(script.Impl), see tools/gen_types.py).
|
||||||
import os, re
|
#
|
||||||
|
# Build-time optimizations (token rewrites with minify.py's lexer: strings/comments are never touched, every source line
|
||||||
|
# stays on its line so linemap.json still maps errors). IMGUI_BUNDLE_NOOPT=1 turns all off, IMGUI_BUNDLE_NO_<X>=1 one:
|
||||||
|
# A hot library globals (most referenced names assigned at a file's top level) become bundle-level locals declared
|
||||||
|
# before __FILES: every file's assignment/reference then hits an upvalue instead of GETGLOBAL. Fewer names for
|
||||||
|
# LuaJIT (60 upvalues per function). Library globals can't be patched from outside (use the returned table).
|
||||||
|
# B enum folding: the bundle is loaded once headless (Luau CLI) to dump enum-like tables (all values numbers, never
|
||||||
|
# written inside a function) and numeric constants assigned once at top level; reads `ImGuiCol.Text` / `FLT_MAX`
|
||||||
|
# inside function bodies become `0 --[[ImGuiCol.Text]]`. Load-time (top-level) code is left alone.
|
||||||
|
# C `--@hoist` functions in the sources (pure numeric helpers: ImMin, ImMax, ImClamp, ...) are moved to the bundle top
|
||||||
|
# as `local function`s (their source lines become blank) so Luau -O2 can inline them everywhere.
|
||||||
|
# E statement-form `IM_ASSERT(...)` becomes `assert(...)` (fast builtin); expression uses are left alone.
|
||||||
|
import os, re, sys, json, shutil, subprocess, tempfile
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.dirname(HERE)
|
||||||
|
sys.path.insert(0, HERE)
|
||||||
|
from minify import TOKEN
|
||||||
|
|
||||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
import sys
|
|
||||||
NO_DEMO = "--no-demo" in sys.argv
|
NO_DEMO = "--no-demo" in sys.argv
|
||||||
LOVE = False
|
LOVE = False
|
||||||
if "--target" in sys.argv:
|
if "--target" in sys.argv:
|
||||||
i = sys.argv.index("--target"); LOVE = sys.argv[i + 1] == "love"; del sys.argv[i:i + 2]
|
i = sys.argv.index("--target"); LOVE = sys.argv[i + 1] == "love"; del sys.argv[i:i + 2]
|
||||||
sys.argv = [a for a in sys.argv if a != "--no-demo"]
|
sys.argv = [a for a in sys.argv if a != "--no-demo"]
|
||||||
|
OPT = {x: os.environ.get("IMGUI_BUNDLE_NOOPT") != "1" and os.environ.get("IMGUI_BUNDLE_NO_" + x) != "1" for x in "ABCE"}
|
||||||
|
# A limits: Luau 200 locals in the main chunk (+ helpers, __FILES, ...), 200 upvalues per function; LuaJIT 60 upvalues
|
||||||
|
# per function (margin left for nested functions that also capture file locals)
|
||||||
|
MAX_LOCALS, MAX_UPVALUES = 160, (45 if LOVE else 150)
|
||||||
BACKEND = "imgui_impl_love.lua" if LOVE else "imgui_impl_roblox.lua"
|
BACKEND = "imgui_impl_love.lua" if LOVE else "imgui_impl_roblox.lua"
|
||||||
SKIP = {"imgui_impl_roblox.lua", "imgui_impl_roblox_fonts.lua"} if LOVE else {"imgui_impl_love.lua"}
|
SKIP = {"imgui_impl_roblox.lua", "imgui_impl_roblox_fonts.lua"} if LOVE else {"imgui_impl_love.lua"}
|
||||||
FILES = {}
|
FILES = {}
|
||||||
@@ -26,6 +45,7 @@ for d in (ROOT, os.path.join(ROOT, "backends"), os.path.join(ROOT, "fonts")):
|
|||||||
if LOVE: src = re.sub(r"^@native$", "", src, flags=re.M) # keep line numbers
|
if LOVE: src = re.sub(r"^@native$", "", src, flags=re.M) # keep line numbers
|
||||||
FILES[f] = src
|
FILES[f] = src
|
||||||
|
|
||||||
|
ORIG = dict(FILES) # for gen_types
|
||||||
ENTRY = ["imgui.lua", BACKEND] + ([] if NO_DEMO else ["imgui_demo.lua"]) # run order
|
ENTRY = ["imgui.lua", BACKEND] + ([] if NO_DEMO else ["imgui_demo.lua"]) # run order
|
||||||
|
|
||||||
names = set()
|
names = set()
|
||||||
@@ -33,16 +53,207 @@ for src in FILES.values():
|
|||||||
names.update(re.findall(r"^function ([A-Za-z_]\w*)\s*\(", src, re.M))
|
names.update(re.findall(r"^function ([A-Za-z_]\w*)\s*\(", src, re.M))
|
||||||
names.update(re.findall(r"^([A-Za-z_]\w*)\s*=[^=]", src, re.M))
|
names.update(re.findall(r"^([A-Za-z_]\w*)\s*=[^=]", src, re.M))
|
||||||
|
|
||||||
|
# ---- lexer helpers ----
|
||||||
|
KW = set("and break do else elseif end false for function goto if in local nil not or repeat return then true until while".split())
|
||||||
|
|
||||||
|
def lex(src):
|
||||||
|
"""[(kind, text, start)] without whitespace/comments; kinds: name num str op."""
|
||||||
|
toks, pos = [], 0
|
||||||
|
while pos < len(src):
|
||||||
|
m = TOKEN.match(src, pos)
|
||||||
|
k = m.lastgroup
|
||||||
|
if k in ("ceq", "seq"): k = "lcom" if m.group(0).startswith("--") else "lstr"
|
||||||
|
if k not in ("ws", "lcom", "com"): toks.append(("str" if k == "lstr" else k, m.group(0), pos))
|
||||||
|
pos = m.end()
|
||||||
|
return toks
|
||||||
|
|
||||||
|
def fdepths(toks):
|
||||||
|
"""function nesting depth of every token (0 = runs at load time)"""
|
||||||
|
st, fd, out = [], 0, []
|
||||||
|
for k, t, _ in toks:
|
||||||
|
out.append(fd)
|
||||||
|
if k == "name":
|
||||||
|
if t == "function": st.append(1); fd += 1
|
||||||
|
elif t in ("if", "do", "repeat"): st.append(0)
|
||||||
|
elif t in ("end", "until"): fd -= st.pop()
|
||||||
|
assert not st, "unbalanced blocks"
|
||||||
|
return out
|
||||||
|
|
||||||
|
def declared(toks):
|
||||||
|
"""names declared as locals / parameters / loop variables anywhere"""
|
||||||
|
out, n = set(), len(toks)
|
||||||
|
for i, (k, t, _) in enumerate(toks):
|
||||||
|
j = None
|
||||||
|
if t == "local": j = i + 2 if toks[i + 1][1] == "function" else i + 1
|
||||||
|
elif t == "for": j = i + 1
|
||||||
|
elif t == "function":
|
||||||
|
j = i + 1
|
||||||
|
while toks[j][1] != "(": j += 1
|
||||||
|
j += 1
|
||||||
|
while j is not None and j < n and toks[j][0] == "name" and toks[j][1] not in KW:
|
||||||
|
out.add(toks[j][1])
|
||||||
|
j += 1
|
||||||
|
if j < n and toks[j][1] == ",": j += 1
|
||||||
|
else: break
|
||||||
|
return out
|
||||||
|
|
||||||
|
def apply(src, edits):
|
||||||
|
for s, e, r in sorted(edits, reverse=True): src = src[:s] + r + src[e:]
|
||||||
|
return src
|
||||||
|
|
||||||
|
def isref(toks, i):
|
||||||
|
"""toks[i] is a variable read/write (not a field name)"""
|
||||||
|
return toks[i][0] == "name" and (i == 0 or toks[i - 1][1] not in (".", ":"))
|
||||||
|
|
||||||
|
def nexttext(toks, i): return toks[i + 1][1] if i + 1 < len(toks) else ""
|
||||||
|
|
||||||
|
# ---- C: --@hoist helpers ----
|
||||||
|
hoisted, hoisted_names = [], []
|
||||||
|
if OPT["C"]:
|
||||||
|
for f, src in FILES.items():
|
||||||
|
while "--@hoist\n" in src:
|
||||||
|
s = src.index("--@hoist\n") + len("--@hoist\n")
|
||||||
|
toks = lex(src[s:])
|
||||||
|
depth, i = 0, 0
|
||||||
|
while True: # first `function` ... matching end
|
||||||
|
t = toks[i][1]
|
||||||
|
if t == "function" or t in ("if", "do", "repeat"): depth += 1
|
||||||
|
elif t in ("end", "until"):
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0: break
|
||||||
|
i += 1
|
||||||
|
e = s + toks[i][2] + 3
|
||||||
|
text = src[s:e]
|
||||||
|
m = re.search(r"^(local )?function ([A-Za-z_]\w*)", text, re.M)
|
||||||
|
hoisted_names.append(m.group(2))
|
||||||
|
hoisted.append(text[:m.start()] + "local function " + text[m.start() + len(m.group(0)) - len(m.group(2)):])
|
||||||
|
src = src[:s - len("--@hoist\n")] + "\n" + re.sub(r"[^\n]", "", text) + src[e:]
|
||||||
|
FILES[f] = src
|
||||||
|
HOIST_DEPS = "local type, mathMin, mathMax, mathFloor, mathCeil, bitOr, bitLShift = type, math.min, math.max, math.floor, math.ceil, bit32.bor, bit32.lshift"
|
||||||
|
|
||||||
|
# ---- E: statement IM_ASSERT -> assert ----
|
||||||
|
STMT_PREV_KW = {"end", "true", "false", "nil", "break", "then", "do", "else", "repeat"}
|
||||||
|
def rewrite_assert(src):
|
||||||
|
toks, edits = lex(src), []
|
||||||
|
for i, (k, t, p) in enumerate(toks):
|
||||||
|
if t == "IM_ASSERT" and isref(toks, i) and nexttext(toks, i) == "(":
|
||||||
|
pk, pt = (toks[i - 1][0], toks[i - 1][1]) if i else ("op", ";")
|
||||||
|
if (pk == "name" and (pt not in KW or pt in STMT_PREV_KW)) or pk in ("num", "str") or pt in (")", "]", "}", ";", "..."):
|
||||||
|
edits.append((p, p + len(t), "assert"))
|
||||||
|
return apply(src, edits)
|
||||||
|
|
||||||
|
if OPT["E"]:
|
||||||
|
FILES = {f: rewrite_assert(s) for f, s in FILES.items()}
|
||||||
|
hoisted = [rewrite_assert(s) for s in hoisted]
|
||||||
|
|
||||||
|
# ---- B: enum / constant folding ----
|
||||||
|
LUAU = shutil.which("luau") or "/nix/store/2m2ja613nndihc8qm10f2rhq6znp1nh4-luau-0.734/bin/luau"
|
||||||
|
|
||||||
|
def header():
|
||||||
|
return ["local __FILES, __LOADED = {}, {}",
|
||||||
|
"function IM_INCLUDE(name)",
|
||||||
|
" local r = __LOADED[name]",
|
||||||
|
" if r == nil then r = __FILES[name](); if r == nil then r = true end; __LOADED[name] = r end",
|
||||||
|
" return r",
|
||||||
|
"end", ""]
|
||||||
|
|
||||||
|
def numtext(s):
|
||||||
|
v = float(s)
|
||||||
|
if v != v or v in (float("inf"), float("-inf")) or (v == 0 and s.startswith("-")): return None
|
||||||
|
r = str(int(v)) if v.is_integer() and abs(v) < 2 ** 53 else repr(v)
|
||||||
|
return "(%s)" % r if v < 0 else r
|
||||||
|
|
||||||
|
def dump_values():
|
||||||
|
"""{name: number} and {name: {key: number}} of the loaded library (imgui.lua and what it includes)"""
|
||||||
|
src = ["--!optimize 1"] + ([HOIST_DEPS] + hoisted if hoisted else []) + header()
|
||||||
|
for name, s in sorted(FILES.items()): src += ["__FILES[%r] = function()" % name, s, "end"]
|
||||||
|
src += ['IM_INCLUDE("imgui.lua")',
|
||||||
|
'local function D(n, v) if type(v) == "number" then print("C", n, string.format("%.17g", v)) elseif type(v) == "table" then',
|
||||||
|
' for k, x in pairs(v) do print("T", n, type(k) == "string" and k or "?", type(x) == "number" and string.format("%.17g", x) or "?") end end end']
|
||||||
|
src += ["D(%r, %s)" % (n, n) for n in sorted(names)]
|
||||||
|
with tempfile.NamedTemporaryFile("w", suffix=".luau", delete=False) as fp: fp.write("\n".join(src))
|
||||||
|
try:
|
||||||
|
p = subprocess.run([LUAU, fp.name], capture_output=True, text=True, timeout=60)
|
||||||
|
finally:
|
||||||
|
os.unlink(fp.name)
|
||||||
|
if p.returncode: raise RuntimeError("enum dump failed:\n" + p.stdout[-2000:] + p.stderr[-2000:])
|
||||||
|
consts, tables = {}, {}
|
||||||
|
for line in p.stdout.splitlines():
|
||||||
|
a = line.split("\t")
|
||||||
|
if a[0] == "C": consts[a[1]] = a[2]
|
||||||
|
elif a[0] == "T": tables.setdefault(a[1], {})[a[2]] = a[3]
|
||||||
|
tables = {n: t for n, t in tables.items() if "?" not in t and "?" not in t.values()}
|
||||||
|
return consts, tables
|
||||||
|
|
||||||
|
def fold(files):
|
||||||
|
consts, tables = dump_values()
|
||||||
|
lexed = {f: lex(s) for f, s in files.items()}
|
||||||
|
depth = {f: fdepths(t) for f, t in lexed.items()}
|
||||||
|
# tables written inside a function, constants assigned more than once or not at top level: not foldable
|
||||||
|
assigns = {}
|
||||||
|
for f, toks in lexed.items():
|
||||||
|
for i, (k, t, _) in enumerate(toks):
|
||||||
|
if not isref(toks, i): continue
|
||||||
|
if t in tables and depth[f][i] > 0:
|
||||||
|
j = i + 1
|
||||||
|
if nexttext(toks, i) == ".": j = i + 3
|
||||||
|
elif nexttext(toks, i) == "[":
|
||||||
|
b = 0
|
||||||
|
while True:
|
||||||
|
b += {"[": 1, "]": -1}.get(toks[j][1], 0); j += 1
|
||||||
|
if b == 0: break
|
||||||
|
if j < len(toks) and toks[j][1] == "=": tables.pop(t, None)
|
||||||
|
if t in consts and nexttext(toks, i) == "=":
|
||||||
|
assigns[t] = assigns.get(t, 0) + (1 if depth[f][i] == 0 else 2)
|
||||||
|
consts = {n: v for n, v in consts.items() if assigns.get(n) == 1 and numtext(v)}
|
||||||
|
out = {}
|
||||||
|
for f, toks in lexed.items():
|
||||||
|
shadow = declared(toks)
|
||||||
|
edits = []
|
||||||
|
for i, (k, t, p) in enumerate(toks):
|
||||||
|
if depth[f][i] == 0 or not isref(toks, i) or t in shadow: continue
|
||||||
|
if t in tables and nexttext(toks, i) == "." and i + 2 < len(toks):
|
||||||
|
key = toks[i + 2][1]
|
||||||
|
if key in tables[t] and numtext(tables[t][key]) and nexttext(toks, i + 2) not in ("=", ".", ":", "[", "(", "{") and toks[i + 3][0] != "str":
|
||||||
|
edits.append((p, toks[i + 2][2] + len(key), "%s --[[%s.%s]]" % (numtext(tables[t][key]), t, key)))
|
||||||
|
elif t in consts and nexttext(toks, i) not in ("=", ".", ":", "[", "(", "{") and toks[i + 1][0] != "str":
|
||||||
|
edits.append((p, p + len(t), "%s --[[%s]]" % (numtext(consts[t]), t)))
|
||||||
|
out[f] = apply(files[f], edits)
|
||||||
|
return out
|
||||||
|
|
||||||
|
if OPT["B"]:
|
||||||
|
try:
|
||||||
|
allf = dict(FILES); allf.update({"@hoist%d" % i: "local function __h()\n" + s + "\nend" for i, s in enumerate(hoisted)})
|
||||||
|
allf = fold(allf)
|
||||||
|
hoisted = [allf.pop("@hoist%d" % i)[len("local function __h()\n"):-len("\nend")] for i in range(len(hoisted))]
|
||||||
|
FILES = allf
|
||||||
|
except (OSError, RuntimeError) as e:
|
||||||
|
if os.environ.get("IMGUI_BUNDLE_STRICT"): raise # CI: a release must not silently lose the folding
|
||||||
|
print("bundle.py: enum folding skipped (%s)" % str(e).strip()[:300], file=sys.stderr)
|
||||||
|
|
||||||
|
# ---- A: hot globals -> bundle locals ----
|
||||||
|
hot = []
|
||||||
|
if OPT["A"]:
|
||||||
|
count, used = {}, []
|
||||||
|
for src in FILES.values():
|
||||||
|
toks = lex(src)
|
||||||
|
used.append({t for i, (k, t, _) in enumerate(toks) if isref(toks, i)})
|
||||||
|
for i, (k, t, _) in enumerate(toks):
|
||||||
|
if t in names and isref(toks, i): count[t] = count.get(t, 0) + 1
|
||||||
|
# greedy by reference count; each file function's upvalues (hoisted names it uses) stay <= MAX_UPVALUES
|
||||||
|
taken = [len(u & set(hoisted_names + re.findall(r"\w+", HOIST_DEPS.split("=")[0]))) for u in used]
|
||||||
|
for n in sorted((n for n in count if n not in hoisted_names), key=lambda n: (-count[n], n)):
|
||||||
|
if len(hot) == MAX_LOCALS: break
|
||||||
|
if all(taken[i] < MAX_UPVALUES for i, u in enumerate(used) if n in u):
|
||||||
|
hot.append(n)
|
||||||
|
taken = [c + (n in u) for c, u in zip(taken, used)]
|
||||||
|
|
||||||
out = ["--!optimize 2", "-- GENERATED by tools/bundle.py from *.lua, backends/, fonts/ - do not edit", ""]
|
out = ["--!optimize 2", "-- GENERATED by tools/bundle.py from *.lua, backends/, fonts/ - do not edit", ""]
|
||||||
if LOVE:
|
if LOVE:
|
||||||
out = ["-- GENERATED by tools/bundle.py --target love - do not edit", re.search(r"--@compat-begin\n(.*?)--@compat-end", open(os.path.join(ROOT, "backends", "imgui_impl_love.lua")).read(), re.S).group(1), ""]
|
out = ["-- GENERATED by tools/bundle.py --target love - do not edit", re.search(r"--@compat-begin\n(.*?)--@compat-end", open(os.path.join(ROOT, "backends", "imgui_impl_love.lua")).read(), re.S).group(1), ""]
|
||||||
out += [
|
if hot: out.append("local " + ", ".join(hot))
|
||||||
"local __FILES, __LOADED = {}, {}",
|
if hoisted: out += [HOIST_DEPS] + hoisted
|
||||||
"function IM_INCLUDE(name)",
|
out += header()
|
||||||
" local r = __LOADED[name]",
|
|
||||||
" if r == nil then r = __FILES[name](); if r == nil then r = true end; __LOADED[name] = r end",
|
|
||||||
" return r",
|
|
||||||
"end", ""]
|
|
||||||
linemap = [] # (first bundle line, file) for error -> source mapping (tools/test/run.py)
|
linemap = [] # (first bundle line, file) for error -> source mapping (tools/test/run.py)
|
||||||
for name, src in sorted(FILES.items()):
|
for name, src in sorted(FILES.items()):
|
||||||
out.append("__FILES[%r] = function()" % name)
|
out.append("__FILES[%r] = function()" % name)
|
||||||
@@ -58,15 +269,12 @@ for n in sorted(names):
|
|||||||
out.append(" %s = %s," % (n, n))
|
out.append(" %s = %s," % (n, n))
|
||||||
out.append("}")
|
out.append("}")
|
||||||
|
|
||||||
import sys
|
|
||||||
OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(ROOT, "build")
|
OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(ROOT, "build")
|
||||||
os.makedirs(OUT, exist_ok=True)
|
os.makedirs(OUT, exist_ok=True)
|
||||||
NAME = "imgui_love.lua" if LOVE else "ImGui.luau"
|
NAME = "imgui_love.lua" if LOVE else "ImGui.luau"
|
||||||
open(os.path.join(OUT, NAME), "w").write("\n".join(out) + "\n")
|
open(os.path.join(OUT, NAME), "w").write("\n".join(out) + "\n")
|
||||||
import json
|
|
||||||
json.dump(linemap, open(os.path.join(OUT, NAME.rsplit(".", 1)[0] + ".linemap.json"), "w"))
|
json.dump(linemap, open(os.path.join(OUT, NAME.rsplit(".", 1)[0] + ".linemap.json"), "w"))
|
||||||
if not LOVE:
|
if not LOVE:
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
from gen_types import wrapper
|
from gen_types import wrapper
|
||||||
open(os.path.join(OUT, "ImGuiTyped.luau"), "w").write(wrapper(FILES))
|
open(os.path.join(OUT, "ImGuiTyped.luau"), "w").write(wrapper(ORIG))
|
||||||
print("wrote build/%s (%d files, %d exports)" % (NAME, len(FILES), len(names)))
|
print("wrote build/%s (%d files, %d exports)" % (NAME, len(FILES), len(names)))
|
||||||
|
|||||||
@@ -0,0 +1,645 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Compares the port against upstream Dear ImGui (tools/test/upstream/*) and writes docs/PORT_STATUS.txt.
|
||||||
|
# python3 tools/compare_upstream.py # print summary, write docs/PORT_STATUS.txt
|
||||||
|
# Ported %: upstream functions (by qualified name, overloads merged) that have a port definition; enum values; struct fields;
|
||||||
|
# public/internal API declarations in imgui.h / imgui_internal.h (namespace ImGui).
|
||||||
|
# Similarity %: for each ported function, both bodies are normalized to a common token stream (comments/types/punctuation
|
||||||
|
# dropped, C++ operators and names mapped to Lua: -> :: . ! && || _Flag) and compared with difflib ratio.
|
||||||
|
# File figures are weighted by upstream body size. Matching is by name, so it's an estimate, not a proof.
|
||||||
|
# Excluded from the %: C++-only (operators, destructors), #if 0, IMGUI_DISABLE_OBSOLETE_FUNCTIONS, OS-specific (#ifdef _WIN32...),
|
||||||
|
# test engine. They're listed separately in the report.
|
||||||
|
import os, re, sys
|
||||||
|
from difflib import SequenceMatcher
|
||||||
|
from fnmatch import fnmatchcase
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
from minify import TOKEN as LUA_TOKEN
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
UP = os.path.join(ROOT, "tools", "test", "upstream")
|
||||||
|
CPP_FILES = ["imgui.cpp", "imgui_widgets.cpp", "imgui_draw.cpp", "imgui_tables.cpp", "imgui_demo.cpp", "imgui.h", "imgui_internal.h"]
|
||||||
|
PORT_FILES = sorted(f for f in os.listdir(ROOT) if f.endswith(".lua"))
|
||||||
|
LUA_CLASS_ALIAS = {"IM_RECT": "ImRect", "IM_VECTOR": "ImVector", "IM_POOL": "ImPool", "IMGUI_VIEWPORT_P": "ImGuiViewportP",
|
||||||
|
"ImGuiTextBufferMT": "ImGuiTextBuffer", "IMGUI_TEXT_INDEX": "ImGuiTextIndex", "IM_PAYLOAD": "ImGuiPayload",
|
||||||
|
"DN": "ImGuiDockNode", "IM_VTXBUF": "ImVector"}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
# Intentional Lua replacements: [=] rows, not counted as missing. (glob over the upstream key "Class::name" / "Struct::field",
|
||||||
|
# reason, regex that must match the port sources - if the port's mechanism disappears, the entry falls back to [ ] / missing).
|
||||||
|
# Only applies when no same-named port definition / field exists.
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
REPLACED = [
|
||||||
|
# functions
|
||||||
|
("ImGuiStorage::*", "Lua table keyed by ID (e.g. window.StateStorage = {})", r"StateStorage\s*=\s*\{\}"),
|
||||||
|
("ImGuiStoragePair::*", "Lua table keyed by ID", r"StateStorage\s*=\s*\{\}"),
|
||||||
|
("PairComparerByID", "table.sort over Lua table keys", r"table\.sort\("),
|
||||||
|
("ImStr*", "Lua string library (string.find/sub/lower, immutable strings)", r"string\.find\("),
|
||||||
|
("ImMemdup", "Lua strings/tables are values, no copy needed", r"string\.find\("),
|
||||||
|
("ImFormatString*", "string.format", r"ImFormatString\s*=\s*string\.format"),
|
||||||
|
("ImText*Utf8*", "strings stay UTF-8 bytes: ImStd.ImTextCharFromUtf8 decoder + utf8 library", r"function ImStd\.ImTextCharFromUtf8"),
|
||||||
|
("MallocWrapper", "GC; IM_ALLOC = ImGui.MemAlloc", r"IM_ALLOC\s*=\s*ImGui\.MemAlloc"),
|
||||||
|
("FreeWrapper", "GC; IM_ALLOC = ImGui.MemAlloc", r"IM_ALLOC\s*=\s*ImGui\.MemAlloc"),
|
||||||
|
("ImGui::GetAllocatorFunctions", "GC, no allocator to query", r"IM_ALLOC\s*=\s*ImGui\.MemAlloc"),
|
||||||
|
("ImFileWrite", "no filesystem on Roblox: SaveIniSettingsToDisk is a no-op, use SaveIniSettingsToMemory", r"Roblox: no disk"),
|
||||||
|
("ImSwap", "Lua multiple assignment a, b = b, a", r"\b([\w.]+), ([\w.]+) = \2, \1\b"),
|
||||||
|
("ImGui::DebugCheckVersionAndDataLayout", "C++ ABI / sizeof check", None),
|
||||||
|
("TableSettingsCalcChunkSize", "sizeof; table settings are a plain ImVector", r"SettingsTables\s*=\s*ImVector\(\)"),
|
||||||
|
("ImVector::size", "v.Size field", r"function IM_VECTOR:"),
|
||||||
|
("ImVector::capacity", "v.Capacity field", r"function IM_VECTOR:"),
|
||||||
|
("ImVector::front", "v.Data[1]", r"function IM_VECTOR:"),
|
||||||
|
("ImVector::begin", "v.Data / v:iter()", r"function IM_VECTOR:iter\("),
|
||||||
|
("ImVector::end", "v.Data / v:iter()", r"function IM_VECTOR:iter\("),
|
||||||
|
("ImVector::size_in_bytes", "sizeof", None),
|
||||||
|
("ImVector::max_size", "sizeof", None),
|
||||||
|
("ImVector::clear_destruct", "GC: clear_delete / clear", r"function IM_VECTOR:clear_delete\("),
|
||||||
|
("ImVector::find", "find_index (no element pointers)", r"function IM_VECTOR:find_index\("),
|
||||||
|
("ImSpan::*", "plain Lua tables (e.g. table.Columns = {})", r"Columns = \{\}, ColumnsSize = 0"),
|
||||||
|
("ImSpanAllocator::*", "plain Lua tables, no arena", r"Columns = \{\}, ColumnsSize = 0"),
|
||||||
|
("ImStableVector::*", "ImVector (Lua references are stable)", r"BakedPool\s*=\s*ImVector\(\)"),
|
||||||
|
("ImChunkStream::*", "plain ImVector of settings tables", r"SettingsTables\s*=\s*ImVector\(\)"),
|
||||||
|
("ImPool::GetAliveCount", "pool.AliveCount field", r"\.AliveCount\)"),
|
||||||
|
("ImPool::GetBufSize", "pool.BufSize field / GetMapSize()", r"function IM_POOL:GetMapSize\(\) return self\.BufSize"),
|
||||||
|
("ImPool::TryGetMapData", "GetByIndex (removed slots hold false)", r"function IM_POOL:GetByIndex\("),
|
||||||
|
("ImGuiTextBuffer::begin", "chunk-list string builder: c_str()", r"function ImGuiTextBufferMT:c_str\("),
|
||||||
|
("ImGuiTextBuffer::end", "chunk-list string builder: c_str()", r"function ImGuiTextBufferMT:c_str\("),
|
||||||
|
("ImGuiTextBuffer::resize", "chunk-list string builder, no byte buffer", r"function ImGuiTextBufferMT:c_str\("),
|
||||||
|
("ImGuiTextBuffer::reserve", "chunk-list string builder, no byte buffer", r"function ImGuiTextBufferMT:c_str\("),
|
||||||
|
("ImGuiTextBuffer::appendfv", "appendf(fmt, ...) (Lua varargs)", r"function ImGuiTextBufferMT:appendf\("),
|
||||||
|
("ImGuiTextIndex::append", "debug log keeps a Lua line list (g.DebugLogLines)", r"g\.DebugLogLines\s*="),
|
||||||
|
# struct fields (ImSpan*/ImStableVector/ImGuiStoragePair fields: covered by the globs above)
|
||||||
|
("ImGuiTextBuffer::EmptyString", "c_str() of an empty chunk list", r"function ImGuiTextBufferMT:c_str\("),
|
||||||
|
("ImGuiWindow::NameBufLen", "window.Name is an immutable Lua string", None),
|
||||||
|
("ImGuiTextFilterItem::Begin", "item.Text substring", r"Text = string\.sub\(buf, b, e - 1\)"),
|
||||||
|
("ImGuiTextFilterItem::Len", "item.Text substring", r"Text = string\.sub\(buf, b, e - 1\)"),
|
||||||
|
("ImDrawVert::uv", "vertices packed into VtxBuffer.Buf (VTX_STRIDE bytes per vertex)", r"VTX_STRIDE\s*=\s*\d+"),
|
||||||
|
("ImDrawVert::col", "vertices packed into VtxBuffer.Buf (VTX_STRIDE bytes per vertex)", r"VTX_STRIDE\s*=\s*\d+"),
|
||||||
|
("ImGuiContext::DebugLogIndex", "Lua line list g.DebugLogLines", r"g\.DebugLogLines\s*="),
|
||||||
|
("ImGuiContext::TempKeychordName", "GetKeyChordName returns a Lua string", r"\.\. \(\(key ~= ImGuiKey\.None"),
|
||||||
|
("ImGuiContext::DragDropPayloadBuf*", "payload.Data holds the Lua value", r"payload\.Data = data"),
|
||||||
|
("ImGuiTabBar::TabsNames", "names stored on the tab (tab.Name)", r"Tab names are stored on the tab itself"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
# C++ side
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
CPP_TOKEN = re.compile(r"""
|
||||||
|
(?P<nl>\n)
|
||||||
|
| (?P<ws>[ \t\r\f\v]+|\\\n)
|
||||||
|
| (?P<com>//[^\n]*|/\*.*?\*/)
|
||||||
|
| (?P<pp>\#(?:[^\n\\]|\\.|\\\n)*)
|
||||||
|
| (?P<str>(?:u8|u|U|L)?"(?:\\.|[^"\\\n])*"|'(?:\\.|[^'\\\n])*')
|
||||||
|
| (?P<num>0[xX][0-9a-fA-F']+[uUlL]*|(?:\d[\d']*\.?\d*|\.\d+)(?:[eE][+-]?\d+)?[fFuUlL]*)
|
||||||
|
| (?P<name>[A-Za-z_]\w*)
|
||||||
|
| (?P<op>->\*?|::|<<=|>>=|<<|>>|&&|\|\||==|!=|<=|>=|\+\+|--|[-+*/%&|^]=|\.\.\.|.)
|
||||||
|
""", re.S | re.X)
|
||||||
|
|
||||||
|
EXCLUDE_PP = [("if0", re.compile(r"^\s*#\s*if\s+0\b")),
|
||||||
|
("obsolete", re.compile(r"#\s*ifndef\s+IMGUI_DISABLE_OBSOLETE_FUNCTIONS|#\s*if\s+!defined\(IMGUI_DISABLE_OBSOLETE")),
|
||||||
|
("os-specific", re.compile(r"_WIN32|__APPLE__|__linux__|_MSC_VER|__EMSCRIPTEN__|IMGUI_DISABLE_WIN32|__ANDROID__|_XBOX|__MINGW")),
|
||||||
|
("test-engine", re.compile(r"IMGUI_ENABLE_TEST_ENGINE"))]
|
||||||
|
|
||||||
|
def cpp_tokens(src):
|
||||||
|
"""[(kind, text, line, excl)] without whitespace/comments; excl = exclusion tag from enclosing #if, or None."""
|
||||||
|
out, line, stack, sections = [], 1, [], []
|
||||||
|
for m in CPP_TOKEN.finditer(src):
|
||||||
|
kind, text = m.lastgroup, m.group(0)
|
||||||
|
if kind == "nl": line += 1; continue
|
||||||
|
if kind == "com":
|
||||||
|
s = re.match(r"//\s*\[SECTION\]\s*(.+)", text)
|
||||||
|
if s: sections.append((line, s.group(1).strip()))
|
||||||
|
line += text.count("\n"); continue
|
||||||
|
if kind == "ws": line += text.count("\n"); continue
|
||||||
|
if kind == "pp":
|
||||||
|
d = re.match(r"#\s*(\w+)", text); d = d.group(1) if d else ""
|
||||||
|
if d in ("if", "ifdef", "ifndef"):
|
||||||
|
stack.append(next((t for t, r in EXCLUDE_PP if r.search(text)), None))
|
||||||
|
elif d in ("else", "elif") and stack:
|
||||||
|
stack[-1] = None if stack[-1] in ("if0", "obsolete") else stack[-1] # #else of `#if 0` / obsolete is the live path
|
||||||
|
elif d == "endif" and stack:
|
||||||
|
stack.pop()
|
||||||
|
line += text.count("\n"); continue
|
||||||
|
out.append((kind, text, line, next((x for x in reversed(stack) if x), None)))
|
||||||
|
return out, sections
|
||||||
|
|
||||||
|
CPP_MACROS = {"IM_MSVC_RUNTIME_CHECKS_OFF", "IM_MSVC_RUNTIME_CHECKS_RESTORE"} # argument-less macro lines, no trailing ';'
|
||||||
|
CTRL = {"if", "for", "while", "switch", "catch", "sizeof", "return", "alignof", "decltype", "IM_ASSERT", "defined"}
|
||||||
|
|
||||||
|
def match_back(toks, i, close, open_):
|
||||||
|
depth = 0
|
||||||
|
while i >= 0:
|
||||||
|
t = toks[i][1]
|
||||||
|
if t == close: depth += 1
|
||||||
|
elif t == open_:
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0: return i
|
||||||
|
i -= 1
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def match_fwd(toks, i):
|
||||||
|
depth = 0
|
||||||
|
pairs = {"{": "}", "(": ")", "[": "]"}
|
||||||
|
open_ = toks[i][1]; close = pairs[open_]
|
||||||
|
while i < len(toks):
|
||||||
|
t = toks[i][1]
|
||||||
|
if t == open_: depth += 1
|
||||||
|
elif t == close:
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0: return i
|
||||||
|
i += 1
|
||||||
|
return len(toks) - 1
|
||||||
|
|
||||||
|
def func_head(toks, i):
|
||||||
|
"""toks[i] is '{'. If it opens a function body, return (class, name, name_tok_index, cpp_only) else None."""
|
||||||
|
j = i - 1
|
||||||
|
while j >= 0 and toks[j][1] in ("const", "noexcept", "override", "final", "IM_FMTARGS", "IM_FMTLIST"):
|
||||||
|
j -= 1
|
||||||
|
if j >= 0 and toks[j][1] == ")" and toks[match_back(toks, j, ")", "(") - 1][1] in ("IM_FMTARGS", "IM_FMTLIST"):
|
||||||
|
j = match_back(toks, j, ")", "(") - 2
|
||||||
|
# constructor initializer list: Foo::Foo(...) : a(1), b{2} {
|
||||||
|
k = j
|
||||||
|
while k >= 0 and toks[k][1] in (")", "}"):
|
||||||
|
o = match_back(toks, k, toks[k][1], "(" if toks[k][1] == ")" else "{")
|
||||||
|
if o <= 1: break
|
||||||
|
if toks[o - 2][1] in (",",) or (toks[o - 2][1] == ":" ):
|
||||||
|
if toks[o - 2][1] == ":": j = o - 3; break
|
||||||
|
k = o - 3; continue
|
||||||
|
break
|
||||||
|
if j < 0 or toks[j][1] != ")": return None
|
||||||
|
o = match_back(toks, j, ")", "(")
|
||||||
|
if o <= 0: return None
|
||||||
|
n = o - 1
|
||||||
|
name = toks[n][1]
|
||||||
|
cpp_only = False
|
||||||
|
if toks[n - 1][1] == "operator" or (n >= 1 and toks[n][0] == "op" and toks[n - 1][1] == "operator"):
|
||||||
|
return (None, "operator" + name, n, True)
|
||||||
|
if toks[n][0] == "op" and n >= 2 and toks[n - 1][1] == "operator": return (None, "operator", n, True)
|
||||||
|
if toks[n][0] != "name" or name in CTRL: return None
|
||||||
|
if toks[n - 1][1] == "~": cpp_only = True; name = "~" + name
|
||||||
|
cls = None
|
||||||
|
if toks[n - 1][1] == "::" or (cpp_only and toks[n - 2][1] == "::"):
|
||||||
|
q = n - 2 if not cpp_only else n - 3
|
||||||
|
cls = toks[q][1]
|
||||||
|
if toks[n - 1][1] == "operator": return (cls, "operator", n, True)
|
||||||
|
return (cls, name, n, cpp_only)
|
||||||
|
|
||||||
|
class UpFunc:
|
||||||
|
def __init__(s, file, cls, name, line, body, section, excl):
|
||||||
|
s.file, s.cls, s.name, s.line, s.body, s.section, s.excl = file, cls, name, line, body, section, excl
|
||||||
|
@property
|
||||||
|
def key(s): return "%s::%s" % (s.cls, s.name) if s.cls else s.name
|
||||||
|
|
||||||
|
def parse_cpp(fname):
|
||||||
|
src = open(os.path.join(UP, fname), encoding="utf-8", errors="replace").read()
|
||||||
|
toks, sections = cpp_tokens(src)
|
||||||
|
funcs, enums, structs, decls = [], {}, {}, {} # decls: namespace ImGui declarations (name -> line)
|
||||||
|
|
||||||
|
def section_at(line):
|
||||||
|
cur = ""
|
||||||
|
for l, s in sections:
|
||||||
|
if l <= line and l > 120: cur = s # skip the index at the top of each file
|
||||||
|
return cur
|
||||||
|
|
||||||
|
def scan(a, b, cls, ns):
|
||||||
|
i = a
|
||||||
|
stmt_start = a
|
||||||
|
while i < b:
|
||||||
|
k, t, line, excl = toks[i]
|
||||||
|
if t == "{":
|
||||||
|
head = func_head(toks, i)
|
||||||
|
e = match_fwd(toks, i)
|
||||||
|
pre = [x[1] for x in toks[stmt_start:i]]
|
||||||
|
if head:
|
||||||
|
hc, hn, ni, cpp_only = head
|
||||||
|
c = hc or cls
|
||||||
|
if cls and hn == cls: c = cls # inline ctor in struct
|
||||||
|
ex = "c++-only" if cpp_only or hn.startswith("~") else toks[ni][3]
|
||||||
|
if c and hn == c and fname.endswith(".h") and e - i < 4: ex = ex # trivial ctor kept
|
||||||
|
funcs.append(UpFunc(fname, c, hn, toks[ni][2], toks[i + 1:e], section_at(toks[ni][2]), ex))
|
||||||
|
elif "namespace" in pre:
|
||||||
|
q = pre.index("namespace")
|
||||||
|
scan(i + 1, e, cls, (pre[q + 1] if len(pre) > q + 1 else ""))
|
||||||
|
elif "enum" in pre:
|
||||||
|
nm = next((x for x in pre[pre.index("enum") + 1:] if re.match(r"[A-Za-z_]\w*$", x) and x not in ("class", "int")), None)
|
||||||
|
vals, j, depth = [], i + 1, 0
|
||||||
|
expect = True
|
||||||
|
while j < e:
|
||||||
|
tt = toks[j][1]
|
||||||
|
if tt in "([{": depth += 1
|
||||||
|
elif tt in ")]}": depth -= 1
|
||||||
|
elif depth == 0 and tt == ",": expect = True
|
||||||
|
elif expect and depth == 0 and toks[j][0] == "name":
|
||||||
|
vals.append((tt, toks[j][2], toks[j][3])); expect = False
|
||||||
|
j += 1
|
||||||
|
if nm: enums[nm] = (vals, line, section_at(line))
|
||||||
|
elif any(x in ("struct", "class", "union") for x in pre) and "=" not in pre:
|
||||||
|
kw = max(q for q, x in enumerate(pre) if x in ("struct", "class", "union")) # skips macro lines / template<...> before it
|
||||||
|
names = [x for x in pre[kw:] if re.match(r"[A-Za-z_]\w*$", x) and x not in ("struct", "class", "union", "IMGUI_API", "typedef", "final", "public", "private", "alignas")]
|
||||||
|
nm = names[0] if names else cls
|
||||||
|
if nm and "union" not in pre: structs.setdefault(nm, ([], line, section_at(line)))
|
||||||
|
scan(i + 1, e, nm, ns)
|
||||||
|
if nm and pre[:1] != ["union"] and toks[e + 1][1] != ";" and toks[e + 1][0] == "name" and cls:
|
||||||
|
pass
|
||||||
|
i = e + 1
|
||||||
|
if i < b and toks[i][1] == ";": i += 1
|
||||||
|
stmt_start = i
|
||||||
|
continue
|
||||||
|
if t == ";":
|
||||||
|
st = toks[stmt_start:i]
|
||||||
|
txt = [x[1] for x in st]
|
||||||
|
if txt and txt[0] in ("public", "private", "protected") and len(txt) > 1 and txt[1] == ":": st, txt = st[2:], txt[2:]
|
||||||
|
while txt and txt[0] in CPP_MACROS: st, txt = st[1:], txt[1:]
|
||||||
|
if txt and ns == "ImGui" and cls is None and "(" in txt and txt[0] not in ("typedef", "struct", "enum", "using"):
|
||||||
|
p = txt.index("(")
|
||||||
|
if p > 0 and st[p - 1][0] == "name" and txt[p - 1] not in CTRL:
|
||||||
|
decls.setdefault(txt[p - 1], (st[p - 1][2], st[p - 1][3], section_at(st[p - 1][2])))
|
||||||
|
elif txt and cls and txt[0] not in ("typedef", "friend", "static", "using", "enum", "struct", "class", "template"):
|
||||||
|
eq = txt.index("=") if "=" in txt else len(txt)
|
||||||
|
pa = txt.index("(") if "(" in txt else len(txt)
|
||||||
|
if pa >= eq: # not a method declaration
|
||||||
|
depth = 0
|
||||||
|
for j, x in enumerate(txt[:eq] + [";"]):
|
||||||
|
if x in ("<", "(", "["): depth += 1
|
||||||
|
elif x in (">", ")", "]"): depth -= 1
|
||||||
|
elif depth == 0 and x in (",", ";", ":") or (depth == 0 and x == "[" ):
|
||||||
|
if j > 0 and re.match(r"[A-Za-z_]\w*$", txt[j - 1]) and txt[j - 1] not in ("const", "unsigned", "int", "float"):
|
||||||
|
structs.setdefault(cls, ([], 0, ""))[0].append((txt[j - 1], st[j - 1][2], st[j - 1][3]))
|
||||||
|
if x == "[" and depth == 1 and j > 0 and re.match(r"[A-Za-z_]\w*$", txt[j - 1]):
|
||||||
|
structs.setdefault(cls, ([], 0, ""))[0].append((txt[j - 1], st[j - 1][2], st[j - 1][3]))
|
||||||
|
if eq < len(txt) and re.match(r"[A-Za-z_]\w*$", txt[eq - 1]):
|
||||||
|
structs.setdefault(cls, ([], 0, ""))[0].append((txt[eq - 1], st[eq - 1][2], st[eq - 1][3]))
|
||||||
|
stmt_start = i + 1
|
||||||
|
i += 1
|
||||||
|
scan(0, len(toks), None, "")
|
||||||
|
for k in structs: # dedupe fields
|
||||||
|
seen, out = set(), []
|
||||||
|
for f in structs[k][0]:
|
||||||
|
if f[0] not in seen: seen.add(f[0]); out.append(f)
|
||||||
|
structs[k] = (out,) + structs[k][1:]
|
||||||
|
return funcs, enums, structs, decls
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
# Lua side
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
def lua_tokens(src):
|
||||||
|
out, line, pos = [], 1, 0
|
||||||
|
while pos < len(src):
|
||||||
|
m = LUA_TOKEN.match(src, pos)
|
||||||
|
kind, text = m.lastgroup, m.group(0)
|
||||||
|
if kind in ("ceq", "seq"): kind = "lcom" if text.startswith("--") else "lstr"
|
||||||
|
if kind not in ("ws", "com", "lcom"):
|
||||||
|
out.append(("str" if kind in ("str", "lstr") else kind, text, line))
|
||||||
|
line += text.count("\n"); pos = m.end()
|
||||||
|
return out
|
||||||
|
|
||||||
|
OPENERS = {"function", "if", "do", "repeat"}
|
||||||
|
CLOSERS = {"end", "until"}
|
||||||
|
|
||||||
|
class PortFunc:
|
||||||
|
alias = None
|
||||||
|
def __init__(s, file, cls, name, line, body):
|
||||||
|
s.file, s.cls, s.name, s.line, s.body = file, cls, name, line, body
|
||||||
|
|
||||||
|
def parse_lua():
|
||||||
|
defs, enum_keys, globals_, fields = [], set(), set(), set()
|
||||||
|
for f in PORT_FILES:
|
||||||
|
toks = lua_tokens(open(os.path.join(ROOT, f), encoding="utf-8").read())
|
||||||
|
n = len(toks)
|
||||||
|
for i, (k, t, line) in enumerate(toks):
|
||||||
|
chain, start = None, None
|
||||||
|
if t == "function" and i + 1 < n and toks[i + 1][0] == "name":
|
||||||
|
j = i + 1; chain = [toks[j][1]]
|
||||||
|
while j + 2 < n and toks[j + 1][1] in (".", ":") and toks[j + 2][0] == "name":
|
||||||
|
chain.append(toks[j + 2][1]); j += 2
|
||||||
|
if j + 1 < n and toks[j + 1][1] == "(": start = j + 1
|
||||||
|
else: chain = None
|
||||||
|
elif k == "name" and i + 3 < n and toks[i + 1][1] == "=" and toks[i + 2][1] == "function" and toks[i + 3][1] == "(":
|
||||||
|
j = i; chain = [t]
|
||||||
|
while j >= 2 and toks[j - 1][1] == "." and toks[j - 2][0] == "name":
|
||||||
|
chain.insert(0, toks[j - 2][1]); j -= 2
|
||||||
|
start = i + 3; i0 = i + 2
|
||||||
|
if chain is None: continue
|
||||||
|
# body: from start's ')' to matching end
|
||||||
|
j = start
|
||||||
|
while toks[j][1] != ")": j += 1
|
||||||
|
body_start, depth, j = j + 1, 1, j + 1
|
||||||
|
while j < n and depth > 0:
|
||||||
|
tt = toks[j][1]
|
||||||
|
if tt in OPENERS and toks[j][0] == "name": depth += 1
|
||||||
|
elif tt in CLOSERS and toks[j][0] == "name": depth -= 1
|
||||||
|
j += 1
|
||||||
|
name = chain[-1]
|
||||||
|
cls = LUA_CLASS_ALIAS.get(chain[-2], chain[-2]) if len(chain) > 1 else None
|
||||||
|
if cls and cls.endswith("MT") and len(cls) > 2: cls = cls[:-2] # method tables: function ExampleAppLogMT:AddLog()
|
||||||
|
if cls is None and re.match(r"[A-Z]\w*_[A-Z]\w*$", name) and "__" not in name: # Class_Method globals (C++ static members)
|
||||||
|
c, _, m = name.partition("_")
|
||||||
|
defs.append(PortFunc(f, c, m, line, toks[body_start:j - 1]))
|
||||||
|
pf = PortFunc(f, cls, name, line, toks[body_start:j - 1])
|
||||||
|
bt = [x[1] for x in pf.body]
|
||||||
|
if len(bt) >= 5 and bt[0] == "return" and bt[-3:] == ["(", "...", ")"] and all(x[0] == "name" or x[1] in (".", ":") for x in pf.body[1:-3]):
|
||||||
|
pf.alias = " ".join(bt[1:-3]) + "(...)"; pf.body = pf.body[1:-3] # forwarder: function(...) return ImGui.X(...) end
|
||||||
|
defs.append(pf)
|
||||||
|
# aliases / factories: ImGui.DragFloat2 = DragFloatN(2) ImFormatString = string.format ImGui.LogTextV = ImGui.LogText
|
||||||
|
for i, (k, t, line) in enumerate(toks):
|
||||||
|
if k != "name" or (i and toks[i - 1][2] == line) or t == "local": continue
|
||||||
|
j, chain = i, [t]
|
||||||
|
while j + 2 < n and toks[j + 1][1] == "." and toks[j + 2][0] == "name":
|
||||||
|
chain.append(toks[j + 2][1]); j += 2
|
||||||
|
if j + 2 >= n or toks[j + 1][1] != "=" or toks[j + 2][0] != "name" or toks[j + 2][1] in ("function", "nil", "true", "false", "not"): continue
|
||||||
|
if len(chain) == 1 and not re.match(r"(Im|STB|IMSTB|stb)", t): continue
|
||||||
|
if len(chain) > 1 and chain[0] not in ("ImGui", "ImStd", "ImStb", "MT") and chain[-2] not in LUA_CLASS_ALIAS: continue
|
||||||
|
rhs = [x for x in toks[j + 2:] if x[2] == toks[j + 2][2]]
|
||||||
|
cls = LUA_CLASS_ALIAS.get(chain[-2], chain[-2]) if len(chain) > 1 else None
|
||||||
|
pf = PortFunc(f, cls, chain[-1], line, rhs); pf.alias = " ".join(x[1] for x in rhs[:6])
|
||||||
|
defs.append(pf)
|
||||||
|
# enum tables / globals / fields
|
||||||
|
depth_stack = []
|
||||||
|
for i, (k, t, line) in enumerate(toks):
|
||||||
|
if t == "{":
|
||||||
|
owner = None
|
||||||
|
if i >= 2 and toks[i - 1][1] == "=" and toks[i - 2][0] == "name": owner = toks[i - 2][1]
|
||||||
|
depth_stack.append(owner)
|
||||||
|
elif t == "}":
|
||||||
|
if depth_stack: depth_stack.pop()
|
||||||
|
elif k == "name" and i + 1 < n and toks[i + 1][1] == "=":
|
||||||
|
prev = toks[i - 1][1] if i else ""
|
||||||
|
if prev in ("{", ",", ";") or (prev == "." ) or (prev not in ("local", "for") and toks[i - 1][0] != "name" and prev not in ("]", ")")):
|
||||||
|
fields.add(t)
|
||||||
|
if prev in ("{", ",") and depth_stack and depth_stack[-1]: enum_keys.add((depth_stack[-1], t))
|
||||||
|
if prev == "." and i >= 2: enum_keys.add((toks[i - 2][1], t))
|
||||||
|
if prev not in (".", "local", ",") and not depth_stack: globals_.add(t)
|
||||||
|
elif k == "str" and i >= 1 and toks[i - 1][1] == "[" and i + 2 < n and toks[i + 1][1] == "]" and toks[i + 2][1] == "=" and depth_stack and depth_stack[-1]:
|
||||||
|
enum_keys.add((depth_stack[-1], toks[i][1].strip("\"'")))
|
||||||
|
return defs, enum_keys, globals_, fields
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
# Normalization + similarity
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
CPP_DROP = {"const", "static", "float", "int", "unsigned", "signed", "bool", "char", "void", "auto", "double", "size_t", "short", "long",
|
||||||
|
"ImU8", "ImU16", "ImU32", "ImU64", "ImS8", "ImS16", "ImS32", "ImS64", "ImWchar", "ImGuiID", "struct", "inline", "IMGUI_API",
|
||||||
|
"constexpr", "template", "typename", "IM_UNUSED", "this", ";", "{", "}", "(", ")", "&", "*", "ImGuiContext", "ImGuiWindow", "ImGuiStyle",
|
||||||
|
"ImGuiIO", "ImDrawList", "ImFont", "ImRect", "ImVec2", "ImVec4", "case", "break", "default", ":", "[", "]"}
|
||||||
|
LUA_DROP = {"local", "then", "do", "end", "self", "(", ")", ";", "{", "}", "&", "*", "[", "]", ":", "bit32", "math", "string", "table"}
|
||||||
|
LUA_MAP = {"~=": "!=", "not": "!", "and": "&&", "or": "||", "nil": "NULL", "elseif": "else if", "band": "&", "bor": "|", "bxor": "^",
|
||||||
|
"bnot": "~", "lshift": "<<", "rshift": ">>", "btest": "&", "ImMax": "ImMax", "ImMin": "ImMin"}
|
||||||
|
CPP_MAP = {"->": ".", "::": ".", "nullptr": "NULL", "++": "+=", "--": "-=", "IM_COUNTOF": "#", "IM_ARRAYSIZE": "#", "true": "true", "false": "false"}
|
||||||
|
|
||||||
|
def norm_num(t):
|
||||||
|
try:
|
||||||
|
if t.lower().startswith("0x"): return str(int(t.rstrip("uUlL").replace("'", ""), 16))
|
||||||
|
return repr(float(t.rstrip("fFuUlL").replace("'", "")))
|
||||||
|
except ValueError: return t
|
||||||
|
|
||||||
|
def split_enum(t):
|
||||||
|
m = re.match(r"(Im[A-Za-z]+)_(\w+)$", t)
|
||||||
|
return [m.group(1), ".", m.group(2)] if m and not t.endswith("_") else [t]
|
||||||
|
|
||||||
|
def norm_cpp(body):
|
||||||
|
out = []
|
||||||
|
for k, t, _, _ in body:
|
||||||
|
if k == "num": out.append(norm_num(t)); continue
|
||||||
|
if k == "str": out.append("S"); continue
|
||||||
|
t = CPP_MAP.get(t, t)
|
||||||
|
if t in CPP_DROP: continue
|
||||||
|
out.extend(split_enum(t) if k == "name" else [t])
|
||||||
|
o2 = []
|
||||||
|
for i, t in enumerate(out): # drop the implicit ImGui:: qualifier (C++ calls inside the namespace omit it)
|
||||||
|
if t == "ImGui" and i + 1 < len(out) and out[i + 1] == ".": continue
|
||||||
|
if t == "." and o2 and o2[-1] in (".",): continue
|
||||||
|
o2.append(t)
|
||||||
|
return [t for i, t in enumerate(o2) if not (t == "." and (i == 0 or o2[i - 1] in ("(", ",", "=", "return")))]
|
||||||
|
|
||||||
|
def norm_lua(body):
|
||||||
|
out = []
|
||||||
|
for k, t, _ in body:
|
||||||
|
if k == "num": out.append(norm_num(t)); continue
|
||||||
|
if k == "str": out.append("S"); continue
|
||||||
|
if t in LUA_DROP: continue
|
||||||
|
t = LUA_MAP.get(t, t)
|
||||||
|
out.extend(t.split(" ") if " " in t else [t])
|
||||||
|
o2 = []
|
||||||
|
for i, t in enumerate(out):
|
||||||
|
if t == "ImGui" and i + 1 < len(out) and out[i + 1] == ".": continue
|
||||||
|
if t == "." and (not o2 or o2[-1] in ("&", "|", "^", "~", "<<", ">>", "=", ",", "return", "if", "while", "!")): continue
|
||||||
|
o2.append(t)
|
||||||
|
return o2
|
||||||
|
|
||||||
|
def similarity(a, b):
|
||||||
|
if not a and not b: return 1.0
|
||||||
|
return SequenceMatcher(None, a, b, autojunk=False).ratio()
|
||||||
|
|
||||||
|
def is_stub(port_norm, up_norm):
|
||||||
|
real = [t for t in port_norm if t not in ("return", "NULL", "false", "true", "0.0", "S", "IM_ASSERT", "!", ",", "=", "0")]
|
||||||
|
return len(up_norm) > 12 and (len(real) < 3 or not any(re.match(r"[A-Za-z_]\w*$", t) for t in real))
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
def main():
|
||||||
|
defs, enum_keys, lua_globals, lua_fields = parse_lua()
|
||||||
|
by_name = {}
|
||||||
|
for d in sorted(defs, key=lambda d: d.alias is not None): by_name.setdefault(d.name, []).append(d)
|
||||||
|
port_classes = {d.cls for d in defs if d.cls}
|
||||||
|
port_class_files = {}
|
||||||
|
for d in defs:
|
||||||
|
if d.cls: port_class_files.setdefault(d.cls, set()).add(d.file)
|
||||||
|
|
||||||
|
def find(u):
|
||||||
|
m = re.match(r"I?M?STB_TEXTEDIT_(\w+?)(_IMPL)?$", u.name)
|
||||||
|
if m: # STB_TEXTEDIT_INSERTCHARS / IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL -> ImStb.TEXTEDIT_*
|
||||||
|
c = [d for d in by_name.get("TEXTEDIT_" + m.group(1), []) if d.cls == "ImStb"]
|
||||||
|
return c[0] if c else None
|
||||||
|
cands = by_name.get(u.name, [])
|
||||||
|
if u.cls and u.cls not in ("ImGui",):
|
||||||
|
exact = [d for d in cands if d.cls == u.cls]
|
||||||
|
if exact: return exact[0]
|
||||||
|
if u.name == u.cls: # constructor: function ImGuiStyle() / MT.X.new
|
||||||
|
c = [d for d in cands if d.cls is None] or [d for d in by_name.get("new", []) if d.cls == u.cls]
|
||||||
|
return c[0] if c else None
|
||||||
|
if u.cls in port_classes: # only a classless helper in the class's own file (local function _grow_capacity)
|
||||||
|
loose = [d for d in cands if d.cls is None and d.file in port_class_files[u.cls]] if len(cands) == 1 else []
|
||||||
|
return loose[0] if loose else None
|
||||||
|
loose = [d for d in cands if d.cls in (None, "ImGui")] if len(cands) == 1 else []
|
||||||
|
return loose[0] if loose else None
|
||||||
|
pref = [d for d in cands if d.cls == "ImGui"] + [d for d in cands if d.cls is None] + [d for d in cands if d.cls in ("ImStd", "ImStb")]
|
||||||
|
return pref[0] if pref else None
|
||||||
|
|
||||||
|
up_names = set()
|
||||||
|
for f in CPP_FILES:
|
||||||
|
for u in parse_cpp(f)[0]: up_names.add(u.name)
|
||||||
|
def expand(p, depth=0, seen=()):
|
||||||
|
"""Port body with calls to port-only helpers in the same file inlined (the port splits some big functions)."""
|
||||||
|
out, body = [], p.body
|
||||||
|
for i, x in enumerate(body):
|
||||||
|
out.append(x)
|
||||||
|
if depth < 3 and x[0] == "name" and i + 1 < len(body) and body[i + 1][1] == "(" and x[1] not in up_names and (i == 0 or body[i - 1][1] not in (".", ":")):
|
||||||
|
h = [d for d in by_name.get(x[1], []) if d.file == p.file and not d.alias and d is not p and d.name not in seen]
|
||||||
|
if h: out.extend(expand(h[0], depth + 1, seen + (p.name,)))
|
||||||
|
return out
|
||||||
|
|
||||||
|
report, summary = [], []
|
||||||
|
all_up = {}
|
||||||
|
enums_all, structs_all, decls_by_file = {}, {}, {}
|
||||||
|
for f in CPP_FILES:
|
||||||
|
funcs, enums, structs, decls = parse_cpp(f)
|
||||||
|
merged = {}
|
||||||
|
for u in funcs: # merge overloads (keep the largest body for similarity)
|
||||||
|
if u.key not in merged or len(u.body) > len(merged[u.key].body):
|
||||||
|
if u.key in merged and merged[u.key].excl is None: u.excl = None
|
||||||
|
merged[u.key] = u
|
||||||
|
elif u.excl is None: merged[u.key].excl = None
|
||||||
|
all_up[f] = list(merged.values())
|
||||||
|
for k, v in enums.items(): enums_all.setdefault(k, v)
|
||||||
|
for k, v in structs.items():
|
||||||
|
if k in structs_all: structs_all[k][0].extend(x for x in v[0] if x[0] not in {y[0] for y in structs_all[k][0]})
|
||||||
|
else: structs_all[k] = v
|
||||||
|
decls_by_file[f] = decls
|
||||||
|
|
||||||
|
port_src = "\n".join(open(os.path.join(ROOT, f), encoding="utf-8").read() for f in PORT_FILES)
|
||||||
|
ev_ok = {ev: ev is None or re.search(ev, port_src) is not None for _, _, ev in REPLACED}
|
||||||
|
def replaced(key):
|
||||||
|
return next((why for pat, why, ev in REPLACED if ev_ok[ev] and fnmatchcase(key, pat)), None)
|
||||||
|
|
||||||
|
tot = {"n": 0, "have": 0, "stub": 0, "size": 0, "size_have": 0, "simw": 0.0, "size_sim": 0, "repl": 0}
|
||||||
|
for f in CPP_FILES:
|
||||||
|
rows = []
|
||||||
|
st = {"n": 0, "have": 0, "stub": 0, "size": 0, "size_have": 0, "simw": 0.0, "size_sim": 0, "repl": 0}
|
||||||
|
for u in sorted(all_up[f], key=lambda u: u.line):
|
||||||
|
un = norm_cpp(u.body)
|
||||||
|
if u.excl:
|
||||||
|
rows.append((u.section, "[-]", "", u, None, u.excl)); continue
|
||||||
|
p = find(u)
|
||||||
|
why = replaced(u.key) if p is None else None
|
||||||
|
if why:
|
||||||
|
st["repl"] += 1; rows.append((u.section, "[=]", "", u, None, why)); continue
|
||||||
|
st["n"] += 1; st["size"] += len(un)
|
||||||
|
if p is None:
|
||||||
|
rows.append((u.section, "[ ]", "", u, None, "")); continue
|
||||||
|
body = p.body
|
||||||
|
if p.alias: # ImGui.X = ImGui.Y / Factory(n): compare against the target's body when it is a plain reference
|
||||||
|
tgt = [x[1] for x in p.body if x[0] == "name"]
|
||||||
|
t = by_name.get(tgt[-1]) if tgt and all(x[1] == "." or x[0] == "name" for x in p.body) else None
|
||||||
|
t = [d for d in (t or []) if not d.alias]
|
||||||
|
body = t[0].body if t else None
|
||||||
|
if body is None: # alias of a Lua builtin / factory: ported, but no body to compare
|
||||||
|
st["have"] += 1; st["size_have"] += len(un)
|
||||||
|
rows.append((u.section, "[x]", " n/a", u, p, "= " + p.alias)); continue
|
||||||
|
pn = norm_lua(expand(p) if not p.alias else body)
|
||||||
|
sim = similarity(un, pn)
|
||||||
|
stub = is_stub(pn, un) and not p.alias
|
||||||
|
st["have"] += 1; st["size_have"] += len(un); st["simw"] += sim * len(un); st["size_sim"] += len(un)
|
||||||
|
if stub: st["stub"] += 1
|
||||||
|
rows.append((u.section, "[~]" if stub else "[x]", "%3.0f%%" % (100 * sim), u, p, "stub" if stub else ("= " + p.alias if p.alias else "")))
|
||||||
|
for k in tot: tot[k] += st[k]
|
||||||
|
summary.append((f, st))
|
||||||
|
report.append((f, rows))
|
||||||
|
|
||||||
|
def pct(a, b): return "%5.1f%%" % (100.0 * a / b) if b else " - "
|
||||||
|
|
||||||
|
# enums / fields / API declarations
|
||||||
|
enum_rows, enum_n, enum_have = [], 0, 0
|
||||||
|
for nm, (vals, line, sec) in sorted(enums_all.items(), key=lambda x: x[0]):
|
||||||
|
base = re.sub(r"(Private)?_$", "", nm)
|
||||||
|
missing, have, counted = [], 0, 0
|
||||||
|
for v, vline, excl in vals:
|
||||||
|
if excl or v.endswith("_COUNT") and False: continue
|
||||||
|
pre, _, key = v.partition("_")
|
||||||
|
ok = v in lua_globals or (pre, key) in enum_keys or (base, key) in enum_keys or (nm, v) in enum_keys or (base, v) in enum_keys
|
||||||
|
if not ok and not key: ok = v in lua_globals
|
||||||
|
enum_n += 1; counted += 1
|
||||||
|
if ok: have += 1; enum_have += 1
|
||||||
|
else: missing.append(v)
|
||||||
|
enum_rows.append((nm, have, counted, missing))
|
||||||
|
|
||||||
|
field_rows, field_n, field_have, field_repl = [], 0, 0, 0
|
||||||
|
for nm, (fl, line, sec) in sorted(structs_all.items()):
|
||||||
|
fl = [x for x in fl if not x[2]]
|
||||||
|
repl = [x[0] for x in fl if x[0] not in lua_fields and replaced("%s::%s" % (nm, x[0]))]
|
||||||
|
fl = [x for x in fl if x[0] not in repl]
|
||||||
|
if not fl and not repl: continue
|
||||||
|
missing = [x[0] for x in fl if x[0] not in lua_fields]
|
||||||
|
field_n += len(fl); field_have += len(fl) - len(missing); field_repl += len(repl)
|
||||||
|
field_rows.append((nm, len(fl) - len(missing), len(fl), missing, repl))
|
||||||
|
|
||||||
|
api_rows, api_repl = [], 0
|
||||||
|
port_names = set(by_name)
|
||||||
|
for f, label in (("imgui.h", "Public API (imgui.h, namespace ImGui)"), ("imgui_internal.h", "Internal API (imgui_internal.h, namespace ImGui)")):
|
||||||
|
d = {k: v for k, v in decls_by_file[f].items() if not v[1]}
|
||||||
|
r = [k for k in d if k not in port_names and replaced("ImGui::" + k)]
|
||||||
|
api_repl += len(r)
|
||||||
|
for k in r: del d[k]
|
||||||
|
have = [k for k in d if k in port_names]
|
||||||
|
api_rows.append((label, len(have), len(d), sorted((k for k in d if k not in port_names), key=lambda k: d[k][0]), d))
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ summary (stdout + file)
|
||||||
|
head = []
|
||||||
|
head.append("%-22s %9s %9s %9s %11s %9s %9s" % ("upstream file", "funcs", "ported", "by size", "similarity", "stubs", "replaced"))
|
||||||
|
for f, s in summary:
|
||||||
|
head.append("%-22s %9d %9s %9s %11s %9d %9d" % (f, s["n"], pct(s["have"], s["n"]), pct(s["size_have"], s["size"]),
|
||||||
|
pct(s["simw"], s["size_sim"]), s["stub"], s["repl"]))
|
||||||
|
head.append("%-22s %9d %9s %9s %11s %9d %9d" % ("TOTAL", tot["n"], pct(tot["have"], tot["n"]), pct(tot["size_have"], tot["size"]),
|
||||||
|
pct(tot["simw"], tot["size_sim"]), tot["stub"], tot["repl"]))
|
||||||
|
head.append("")
|
||||||
|
head.append("upstream code reproduced (similarity x ported size / all upstream size): %s" % pct(tot["simw"] / max(1, tot["size_sim"]) * tot["size_have"], tot["size"]))
|
||||||
|
for label, h, n, _, _ in api_rows: head.append("%-52s %4d / %4d %s" % (label, h, n, pct(h, n)))
|
||||||
|
head.append("%-52s %4d / %4d %s" % ("Enum values (imgui.h + imgui_internal.h)", enum_have, enum_n, pct(enum_have, enum_n)))
|
||||||
|
head.append("%-52s %4d / %4d %s" % ("Struct fields (imgui.h + imgui_internal.h)", field_have, field_n, pct(field_have, field_n)))
|
||||||
|
head.append("%d intentionally replaced by Lua equivalents [=] (%d functions, %d API declarations, %d struct fields), not counted above"
|
||||||
|
% (tot["repl"] + api_repl + field_repl, tot["repl"], api_repl, field_repl))
|
||||||
|
print("\n".join(head))
|
||||||
|
|
||||||
|
L = []
|
||||||
|
L.append("PORT STATUS: imgui-luau vs upstream Dear ImGui (docking, %s)" % re.search(r'IMGUI_VERSION\s+"([^"]+)"', open(os.path.join(UP, "imgui.h")).read()).group(1))
|
||||||
|
L.append("Generated by tools/compare_upstream.py - do not edit. Upstream sources: tools/test/upstream/.")
|
||||||
|
L.append("")
|
||||||
|
L.append("Columns: funcs = upstream function definitions (overloads merged), excluding [-] rows")
|
||||||
|
L.append(" ported = share that has a port definition with the same name")
|
||||||
|
L.append(" by size = same, weighted by upstream body size (tokens)")
|
||||||
|
L.append(" similarity = normalized token similarity of the ported bodies (difflib ratio), weighted by size")
|
||||||
|
L.append(" stubs = ported but (nearly) empty in the port")
|
||||||
|
L.append(" replaced = [=] rows: intentionally replaced by a Lua mechanism (REPLACED table), excluded from funcs")
|
||||||
|
L.append("Name-based matching: a renamed port function shows as [ ], a same-named one counts even if it differs. Treat as estimates.")
|
||||||
|
L.append("Port-only helpers called from a ported function (same file) are inlined before comparing, since the port splits some big functions.")
|
||||||
|
L.append("Known low scores that are not missing code: data-driven port code (e.g. DemoWindowTables' section table), the older 1.92")
|
||||||
|
L.append("polyline stroker (AddPolyline), Lua-native replacements (ImGuiStorage, ImStr*, ImFormatString -> string.format).")
|
||||||
|
L.append("")
|
||||||
|
L += head
|
||||||
|
L.append("")
|
||||||
|
L.append("Legend: [x] ported (sim %) [~] stub [ ] not ported [=] replaced by Lua equivalent (reason) [-] excluded (c++-only / obsolete / os-specific / #if 0 / test-engine)")
|
||||||
|
for f, rows in report:
|
||||||
|
L.append("")
|
||||||
|
L.append("=" * 100)
|
||||||
|
L.append("%s" % f)
|
||||||
|
L.append("=" * 100)
|
||||||
|
cur = None
|
||||||
|
for sec, mark, sim, u, p, note in rows:
|
||||||
|
if sec != cur:
|
||||||
|
cur = sec; L.append(""); L.append(" -- %s" % (sec or "(top)"))
|
||||||
|
where = "%s:%d" % (p.file, p.line) if p else ""
|
||||||
|
L.append(" %s %5s %-58s %-32s %s" % (mark, sim, u.key, where, note))
|
||||||
|
L.append("")
|
||||||
|
L.append("=" * 100)
|
||||||
|
L.append("API declarations not ported (by upstream line; [=] replacements excluded)")
|
||||||
|
L.append("=" * 100)
|
||||||
|
for label, h, n, missing, d in api_rows:
|
||||||
|
L.append(""); L.append(" %s: %d / %d" % (label, h, n))
|
||||||
|
cur = None
|
||||||
|
for k in missing:
|
||||||
|
if d[k][2] != cur: cur = d[k][2]; L.append(" -- %s" % (cur or "(top)"))
|
||||||
|
L.append(" [ ] ImGui::%s" % k)
|
||||||
|
L.append("")
|
||||||
|
L.append("=" * 100)
|
||||||
|
L.append("Enums (values ported / total; missing listed)")
|
||||||
|
L.append("=" * 100)
|
||||||
|
for nm, h, n, missing in enum_rows:
|
||||||
|
if not n: continue
|
||||||
|
L.append(" %-40s %4d / %4d%s" % (nm, h, n, (" missing: " + ", ".join(missing)) if missing else ""))
|
||||||
|
L.append("")
|
||||||
|
L.append("=" * 100)
|
||||||
|
L.append("Struct fields (name found in the port / total; missing listed; [=] replaced by Lua equivalent, excluded from total). Field names are matched globally, so common names may over-count.")
|
||||||
|
L.append("=" * 100)
|
||||||
|
for nm, h, n, missing, repl in field_rows:
|
||||||
|
L.append(" %-40s %4d / %4d%s%s" % (nm, h, n, (" missing: " + ", ".join(missing)) if missing else "",
|
||||||
|
(" [=] " + ", ".join("%s (%s)" % (x, replaced("%s::%s" % (nm, x))) for x in repl)) if repl else ""))
|
||||||
|
out = os.path.join(ROOT, "docs", "PORT_STATUS.txt")
|
||||||
|
open(out, "w").write("\n".join(L) + "\n")
|
||||||
|
print("\nwrote docs/PORT_STATUS.txt")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+107
@@ -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)))
|
||||||
Executable
+52
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# All local checks: CI's "Check" step (release.yml) + every scripted test + whole demo forced open + LÖVE.
|
||||||
|
# Usage: tools/test/check_all.sh (prints PASS/FAIL per check, exit 1 if any failed)
|
||||||
|
# HASHES=1 tools/test/check_all.sh (also prints the draw-data checksums, see hashes.sh)
|
||||||
|
cd "$(dirname "$0")/../.."
|
||||||
|
export PATH="$PATH:/nix/store/2m2ja613nndihc8qm10f2rhq6znp1nh4-luau-0.734/bin:/nix/store/4qxq4arv32ly13kl3g0m5q80gm3a35xr-luajit-2.1.1741730670/bin"
|
||||||
|
T=$(mktemp -d /tmp/imgui_check.XXXX)
|
||||||
|
R="python3 tools/test/run.py --quiet --roblox --timeout 600"
|
||||||
|
|
||||||
|
# name | expected output | command
|
||||||
|
CHECKS=(
|
||||||
|
"readme|README OK|$R --main tools/test/scripts/t_readme.luau --frames 30"
|
||||||
|
"readme-min|README OK|MINIFY=1 $R --main tools/test/scripts/t_readme.luau --frames 30"
|
||||||
|
"cached|CACHED OK|$R --main tools/test/scripts/t_cached_window.luau --frames 130"
|
||||||
|
"fixes|FIXES OK|$R --main tools/test/scripts/t_fixes.luau --frames 55"
|
||||||
|
"debugtools|DEBUGTOOLS OK|$R --main tools/test/scripts/t_debugtools.luau --frames 65"
|
||||||
|
"helpers|HELPERS OK|$R --main tools/test/scripts/t_helpers.luau --frames 45"
|
||||||
|
"multiselect|MULTISELECT OK|$R --main tools/test/scripts/t_multiselect.luau --frames 380"
|
||||||
|
"nodemo|NODEMO OK|NO_DEMO=1 $R --main tools/test/scripts/t_nodemo.luau --frames 25"
|
||||||
|
"widgets|OK|$R --main tools/test/scripts/t_widgets.luau --frames 80"
|
||||||
|
"tables|OK|$R --main tools/test/scripts/t_tables.luau --frames 150"
|
||||||
|
"tabs|scrollbtn.true|$R --main tools/test/scripts/t_tabs.luau --frames 60"
|
||||||
|
"modal|round trip ok|$R --main tools/test/scripts/t_modal.luau --frames 40"
|
||||||
|
"dock|DOCK OK|$R --main tools/test/scripts/t_dock.luau --frames 60"
|
||||||
|
"dock_close|DOCK CLOSE OK|$R --main tools/test/scripts/t_dock_close.luau --frames 60"
|
||||||
|
"dock_drag|OK|$R --main tools/test/scripts/t_dock_drag.luau --frames 80"
|
||||||
|
"dock_ini|ROUNDTRIP OK|$R --main tools/test/scripts/t_dock_ini.luau --frames 60"
|
||||||
|
"dock_move|OK|$R --main tools/test/scripts/t_dock_move.luau --frames 80"
|
||||||
|
"passthru|PASSTHRU OK|$R --main tools/test/scripts/t_passthru.luau --frames 40"
|
||||||
|
"fonts|FONTS OK|$R --main tools/test/scripts/t_fonts.luau --frames 8"
|
||||||
|
"textproxy|PROXY OK|$R --main tools/test/scripts/t_textproxy.luau --frames 30"
|
||||||
|
"style_editor|SE OK|$R --main tools/test/scripts/t_style_editor.luau --frames 20"
|
||||||
|
"style_leak|STYLE OK|$R --main tools/test/scripts/t_style_leak.luau --frames 60 --open"
|
||||||
|
"apps|APPS OK|$R --main tools/test/scripts/t_apps.luau --frames 120 --open"
|
||||||
|
"libhash|LIBHASH|NO_DEMO=1 $R --main tools/test/scripts/t_libhash.luau --frames 120"
|
||||||
|
"demo_open|DRAWHASH|$R --main tools/test/scripts/t_drawhash.luau --frames 150 --open"
|
||||||
|
"demo_random|^|python3 tools/test/run.py --roblox --timeout 600 --frames 400 --open"
|
||||||
|
"pixels|PIXELS SAME|a=\$($R --main tools/test/scripts/t_pixels.luau --frames 600 | grep -P '^PIX\t'); b=\$(RUN_FLAGS=ARG_FORCE=true $R --main tools/test/scripts/t_pixels.luau --frames 600 | grep -P '^PIX\t'); [ -n \"\$a\" ] && [ \"\$a\" = \"\$b\" ] && echo PIXELS SAME"
|
||||||
|
"shot|wrote|python3 tools/test/shot.py /tmp/imgui_shot.png --app"
|
||||||
|
"love|DRAWHASH|python3 tools/bundle.py --target love >/dev/null && luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic && luajit tools/test/love/t_examples.lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
run() { # $1=name $2=expect $3=cmd
|
||||||
|
bash -c "$3" > "$T/$1.txt" 2>&1; code=$?
|
||||||
|
if [ $code -eq 0 ] && grep -q "$2" "$T/$1.txt" && ! grep -qiE "error|traceback|stack begin|FAIL" "$T/$1.txt"; then echo "PASS $1"
|
||||||
|
else echo "FAIL $1 (exit $code, log $T/$1.txt)"; tail -5 "$T/$1.txt" | sed 's/^/ /'; fi
|
||||||
|
}
|
||||||
|
for c in "${CHECKS[@]}"; do IFS='|' read -r n e cmd <<< "$c"; run "$n" "$e" "$cmd" & done > "$T/summary.txt"
|
||||||
|
wait
|
||||||
|
sort "$T/summary.txt"
|
||||||
|
[ -n "$HASHES" ] && tools/test/hashes.sh
|
||||||
|
! grep -q "^FAIL" "$T/summary.txt"
|
||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Draw-data checksums of the working tree vs a git rev (default HEAD), to prove a change renders identically.
|
||||||
|
# tools/test/hashes.sh [rev]
|
||||||
|
# lib = t_libhash (library only, no demo): must stay equal unless rendering is meant to change
|
||||||
|
# demo = t_drawhash with Widgets,Basic / whole demo open: changes with any demo edit
|
||||||
|
cd "$(dirname "$0")/../.."
|
||||||
|
export PATH="$PATH:/nix/store/2m2ja613nndihc8qm10f2rhq6znp1nh4-luau-0.734/bin:/nix/store/4qxq4arv32ly13kl3g0m5q80gm3a35xr-luajit-2.1.1741730670/bin"
|
||||||
|
REV=${1:-HEAD}
|
||||||
|
hashes() { # $1 = repo dir
|
||||||
|
( cd "$1"
|
||||||
|
R="python3 tools/test/run.py --quiet --roblox --timeout 600"
|
||||||
|
echo "lib $(NO_DEMO=1 $R --main "$OLDPWD/tools/test/scripts/t_libhash.luau" --frames 120 | grep -o 'LIBHASH.*')" &
|
||||||
|
echo "demo-basic $($R --main tools/test/scripts/t_drawhash.luau --frames 150 --open-only Widgets,Basic | grep -o 'DRAWHASH.*')" &
|
||||||
|
echo "demo-all $($R --main tools/test/scripts/t_drawhash.luau --frames 150 --open | grep -o 'DRAWHASH.*')" &
|
||||||
|
echo "love-basic $(python3 tools/bundle.py --target love >/dev/null; luajit tools/test/love/t_drawhash.lua 60 Widgets,Basic | grep -o 'DRAWHASH.*')" &
|
||||||
|
wait ) | tr "\t" " " | sort
|
||||||
|
}
|
||||||
|
W=$(mktemp -d /tmp/imgui_rev.XXXX)
|
||||||
|
git worktree add -q --detach "$W" "$REV"
|
||||||
|
hashes "$W" > "$W.old" & hashes . > "$W.new"; wait
|
||||||
|
git worktree remove --force "$W"
|
||||||
|
echo "check $REV working tree"
|
||||||
|
paste "$W.old" "$W.new" | awk -F'\t' '{split($1,a," "); split($2,b," "); printf "%-12s %-30s %-30s %s\n", a[1], a[3], b[3], (a[3]==b[3]?"same":"CHANGED")}'
|
||||||
@@ -4,4 +4,4 @@ set -e
|
|||||||
cd "$(dirname "$0")/../../.."
|
cd "$(dirname "$0")/../../.."
|
||||||
python3 tools/bundle.py --target love >/dev/null
|
python3 tools/bundle.py --target love >/dev/null
|
||||||
nix-shell -p luajit --run "luajit tools/test/love/t_drawhash.lua 150 Widgets,Basic && luajit tools/test/love/t_drawhash.lua 150 all && luajit tools/test/love/t_examples.lua"
|
nix-shell -p luajit --run "luajit tools/test/love/t_drawhash.lua 150 Widgets,Basic && luajit tools/test/love/t_drawhash.lua 150 all && luajit tools/test/love/t_examples.lua"
|
||||||
echo "expected (Luau, tools/test/scripts/t_drawhash.luau): 1385106188 / 1292122782"
|
echo "expected (Luau, tools/test/scripts/t_drawhash.luau): 154977593 / 263656315"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
-- Runs examples/example_love and examples/example_shared main.lua headless under LuaJIT (mock love) for some frames.
|
-- Runs examples/example_love main.lua headless under LuaJIT (mock love) for some frames.
|
||||||
for _, ex in ipairs({ "examples/example_love", "examples/example_shared" }) do
|
for _, ex in ipairs({ "examples/example_love" }) do
|
||||||
dofile("tools/test/love/mock_love.lua")
|
dofile("tools/test/love/mock_love.lua")
|
||||||
love.filesystem.getSource = function() return ex end
|
love.filesystem.getSource = function() return ex end
|
||||||
love.filesystem.load = function(p) return assert(loadfile(ex .. "/" .. p)) end
|
love.filesystem.load = function(p) return assert(loadfile(ex .. "/" .. p)) end
|
||||||
|
|||||||
+3
-1
@@ -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("--!"))
|
||||||
@@ -66,7 +68,7 @@ def main():
|
|||||||
ap.add_argument("--quiet", action="store_true", help="no random input (scripted tests drive io themselves)")
|
ap.add_argument("--quiet", action="store_true", help="no random input (scripted tests drive io themselves)")
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
code, out = run(args, "")
|
code, out = run(args, os.environ.get("RUN_FLAGS", ""))
|
||||||
print(out if os.environ.get("FULL_OUTPUT") else out[-4000:])
|
print(out if os.environ.get("FULL_OUTPUT") else out[-4000:])
|
||||||
sys.exit(code)
|
sys.exit(code)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.DockingEnable)
|
|||||||
G.ImGui_ImplRoblox.Init()
|
G.ImGui_ImplRoblox.Init()
|
||||||
local names = { "ShowExampleAppConsole", "ShowExampleAppLog", "ShowExampleAppLayout", "ShowExampleAppPropertyEditor", "ShowExampleAppLongText",
|
local names = { "ShowExampleAppConsole", "ShowExampleAppLog", "ShowExampleAppLayout", "ShowExampleAppPropertyEditor", "ShowExampleAppLongText",
|
||||||
"ShowExampleAppAutoResize", "ShowExampleAppConstrainedResize", "ShowExampleAppSimpleOverlay", "ShowExampleAppWindowTitles",
|
"ShowExampleAppAutoResize", "ShowExampleAppConstrainedResize", "ShowExampleAppSimpleOverlay", "ShowExampleAppWindowTitles",
|
||||||
"ShowExampleAppCustomRendering", "ShowExampleAppDocuments", "ShowExampleAppDockSpace", "ShowExampleAppFullscreen", "ShowExampleAppAssetsBrowser", "ShowExampleAppMainMenuBar" }
|
"ShowExampleAppCustomRendering", "ShowExampleAppDocuments", "ShowExampleAppDockSpace", "ShowExampleAppFullscreen", "ShowExampleAppAssetsBrowser", "ShowExampleAppMainMenuBar", "ShowExampleAppImageViewer" }
|
||||||
local tools = { "ShowMetricsWindow", "ShowDebugLogWindow", "ShowIDStackToolWindow", "ShowAboutWindow" }
|
local tools = { "ShowMetricsWindow", "ShowDebugLogWindow", "ShowIDStackToolWindow", "ShowAboutWindow" }
|
||||||
local open = {}
|
local open = {}
|
||||||
local frame = 0
|
local frame = 0
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
-- Debug tools + misc imgui.cpp ports: item picker, DebugFlashStyleColor, DebugLocateItem, context hooks, AddInputCharactersUTF8, ImBezierCubicClosestPoint
|
||||||
|
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||||
|
local ImGui, ImVec2 = G.ImGui, G.ImVec2
|
||||||
|
local ctx = ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
|
||||||
|
local io = ImGui.GetIO()
|
||||||
|
local failed = {}
|
||||||
|
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
|
||||||
|
|
||||||
|
-- ImBezierCubicClosestPoint: straight-line cubic (0,0)-(30,0)-(60,0)-(90,0): closest point to (45,10) is (45,0)
|
||||||
|
local p1, p2, p3, p4 = ImVec2(0, 0), ImVec2(30, 0), ImVec2(60, 0), ImVec2(90, 0)
|
||||||
|
local cp = G.ImBezierCubicClosestPoint(p1, p2, p3, p4, ImVec2(45, 10), 20)
|
||||||
|
check(math.abs(cp.x - 45) < 1e-3 and math.abs(cp.y) < 1e-6, ("ImBezierCubicClosestPoint %g,%g"):format(cp.x, cp.y))
|
||||||
|
-- Curved: result must lie on the curve (distance to a dense sampling of the curve ~0) and beat the endpoints
|
||||||
|
local q1, q2, q3, q4 = ImVec2(0, 0), ImVec2(0, 100), ImVec2(100, 100), ImVec2(100, 0)
|
||||||
|
local target = ImVec2(50, 120)
|
||||||
|
for _, r in { G.ImBezierCubicClosestPoint(q1, q2, q3, q4, target, 64), G.ImBezierCubicClosestPointCasteljau(q1, q2, q3, q4, target, 0.25) } do
|
||||||
|
check(math.abs(r.x - 50) < 0.5 and math.abs(r.y - 75) < 0.5, ("closest point on curve %g,%g (expected ~50,75)"):format(r.x, r.y))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- AddInputCharactersUTF8 queues one Text event per codepoint
|
||||||
|
io:AddInputCharactersUTF8("a\u{e9}\u{20ac}\u{1F600}")
|
||||||
|
local chars = {}
|
||||||
|
for _, e in ctx.InputEventsQueue.Data do if e.Type == G.ImGuiInputEventType.Text then table.insert(chars, e.Text.Char) end end
|
||||||
|
-- (this build has IM_UNICODE_CODEPOINT_MAX = 0xFFFF like upstream's default ImWchar16: U+1F600 becomes IM_UNICODE_CODEPOINT_INVALID)
|
||||||
|
check(#chars == 4 and chars[1] == 0x61 and chars[2] == 0xE9 and chars[3] == 0x20AC and chars[4] == ((G.IM_UNICODE_CODEPOINT_MAX >= 0x1F600) and 0x1F600 or 0xFFFD), "AddInputCharactersUTF8 " .. table.concat(chars, ","))
|
||||||
|
|
||||||
|
-- Context hooks
|
||||||
|
local log = {}
|
||||||
|
local function hook(t, name)
|
||||||
|
local h = G.ImGuiContextHook(); h.Type = t; h.Callback = function(c, hk) table.insert(log, name) end
|
||||||
|
return ImGui.AddContextHook(ctx, h)
|
||||||
|
end
|
||||||
|
local T = G.ImGuiContextHookType
|
||||||
|
hook(T.NewFramePre, "nfpre"); hook(T.NewFramePost, "nfpost"); hook(T.RenderPre, "rpre"); local rpost = hook(T.RenderPost, "rpost")
|
||||||
|
|
||||||
|
-- Context defaults (ImGuiContext()/ImGuiIO() constructors, upstream values)
|
||||||
|
check(ctx.DebugFlashStyleColorIdx == G.ImGuiCol.COUNT and ctx.ActiveIdSource == G.ImGuiInputSource.None and io.MouseSource == G.ImGuiMouseSource.Mouse
|
||||||
|
and ctx.DebugBreakKeyChord == G.ImGuiKey.Pause and ctx.DebugItemPickerActive == false and ctx.DebugLocateId == 0 and ctx.CurrentPixelDensity == 1.0, "context defaults")
|
||||||
|
check(T.PendingRemoval_ == 7 and T.Shutdown == 6, "ImGuiContextHookType values")
|
||||||
|
check(io.BackendPlatformName == "imgui_impl_roblox" and io.BackendRendererName == "imgui_impl_roblox", "BackendPlatformName/BackendRendererName")
|
||||||
|
check((os.date == nil) == (ImGui.GetPlatformIO().Platform_SessionDate == 0), "Platform_SessionDate set from os.date() when available") -- (the test sandbox has no os.date)
|
||||||
|
ImGui.GetPlatformIO().Platform_SessionDate = 20261015 -- exercise the metrics Settings section date code
|
||||||
|
|
||||||
|
-- Style color flash
|
||||||
|
local col = ImGui.GetStyle().Colors[G.ImGuiCol.Button]
|
||||||
|
local orig = { col.x, col.y, col.z, col.w }
|
||||||
|
|
||||||
|
local f, rect, picked, broke, button_id = 0, nil, nil, 0, 0
|
||||||
|
ctx.DebugBreakCallback = function() broke += 1 end
|
||||||
|
game:GetService("RunService").Stepped:Connect(function()
|
||||||
|
f += 1
|
||||||
|
G.ImGui_ImplRoblox.NewFrame()
|
||||||
|
if rect and f == 5 then io:AddMousePosEvent((rect[1].x + rect[2].x) / 2, (rect[1].y + rect[2].y) / 2) end
|
||||||
|
if f == 6 then ImGui.DebugStartItemPicker() end
|
||||||
|
if f == 8 then io:AddMouseButtonEvent(0, true) elseif f == 9 then io:AddMouseButtonEvent(0, false) end
|
||||||
|
ImGui.NewFrame()
|
||||||
|
if f == 1 then check(table.concat(log, " ") == "nfpre nfpost", "hooks after NewFrame: " .. table.concat(log, " ")) end
|
||||||
|
if f == 8 then picked = ctx.DebugItemPickerBreakId end
|
||||||
|
if f == 3 then ImGui.DebugFlashStyleColor(G.ImGuiCol.Button) end
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(10, 10), G.ImGuiCond.Always)
|
||||||
|
ImGui.Begin("DebugTools")
|
||||||
|
ImGui.Button("Target")
|
||||||
|
if f == 2 then local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); rect = { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end
|
||||||
|
local target_id = ctx.LastItemData.ID
|
||||||
|
button_id = target_id
|
||||||
|
if f == 12 then ImGui.DebugLocateItem(target_id) end
|
||||||
|
if f == 13 then check(ctx.DebugLocateId == 0, "DebugLocateItem resolved by ItemAdd()") end
|
||||||
|
ImGui.DebugDrawItemRect(); ImGui.DebugDrawCursorPos(); ImGui.DebugDrawLineExtents()
|
||||||
|
ImGui.DebugTextUnformattedWithLocateItem(("id 0x%08X here"):format(target_id))
|
||||||
|
ImGui.End()
|
||||||
|
if f == 3 then -- open the metrics Settings/InputText sections
|
||||||
|
local mw = ImGui.FindWindowByName("Dear ImGui Metrics/Debugger")
|
||||||
|
for _, lbl in { "Settings", "SettingsWindows", "SettingsTables", "InputText" } do mw.DC.StateStorage[mw:GetID(lbl)] = true end
|
||||||
|
ctx.DebugMetricsConfig.SettingsHighlightOldEntries = true
|
||||||
|
end
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(400, 10), G.ImGuiCond.Always); ImGui.ShowMetricsWindow()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(400, 300), G.ImGuiCond.Always); ImGui.ShowDebugLogWindow()
|
||||||
|
if f == 4 then
|
||||||
|
local c = ImGui.GetStyle().Colors[G.ImGuiCol.Button]
|
||||||
|
check(c.w == 1.0 and ctx.DebugFlashStyleColorIdx == G.ImGuiCol.Button, "flash active")
|
||||||
|
end
|
||||||
|
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||||
|
if f == 1 then check(table.concat(log, " ") == "nfpre nfpost rpre rpost", "hooks after Render: " .. table.concat(log, " ")); ImGui.RemoveContextHook(ctx, rpost) end
|
||||||
|
if f == 2 then check(table.concat(log, " ") == "nfpre nfpost rpre rpost nfpre nfpost rpre", "RemoveContextHook: " .. table.concat(log, " ")) end
|
||||||
|
if f == 60 then
|
||||||
|
local c = ImGui.GetStyle().Colors[G.ImGuiCol.Button]
|
||||||
|
check(c.x == orig[1] and c.y == orig[2] and c.z == orig[3] and c.w == orig[4] and ctx.DebugFlashStyleColorIdx == G.ImGuiCol.COUNT, "flash restored color exactly")
|
||||||
|
check(picked ~= nil and picked ~= 0 and picked == button_id, ("item picker set DebugItemPickerBreakId (%s vs %s)"):format(tostring(picked), tostring(button_id)))
|
||||||
|
check(not ctx.DebugItemPickerActive and broke >= 1, ("item picker break hook called (%d)"):format(broke))
|
||||||
|
if #failed == 0 then print("DEBUGTOOLS OK") end
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
-- Regressions vs upstream: clipboard paste filter (InputTextEx), ImParseFormatTrimDecorations (Ctrl+click temp input), ShrinkWidths tie order,
|
||||||
|
-- CalcItemSize by value, PushClipRectFullScreen (drop target outside clip rect), long TextUnformatted, PushFont/PopFont, InputTextMultiline line index (render/scroll/edit), Shortcut() default
|
||||||
|
-- args, IsWindowAppearing() on frame 1 (FrameCount starts at 0), ColorButton -> ColorEdit4 drag and drop
|
||||||
|
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||||
|
local ImGui, ImVec2 = G.ImGui, G.ImVec2
|
||||||
|
ImGui.CreateContext()
|
||||||
|
ImGui.GetIO().Fonts:AddFontDefault()
|
||||||
|
local font2 = ImGui.GetIO().Fonts:AddFontDefault() -- second font for the PushFont/PopFont check
|
||||||
|
G.ImGui_ImplRoblox.Init()
|
||||||
|
local io = ImGui.GetIO()
|
||||||
|
local failed = {}
|
||||||
|
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
|
||||||
|
|
||||||
|
-- ShrinkWidths: equal widths sort by Index descending (upstream ShrinkWidthItemComparer), so the rounding remainder goes to the highest Index first
|
||||||
|
local items = {}
|
||||||
|
for i = 1, 4 do local it = G.ImGuiShrinkWidthItem(); it.Index = i - 1; it.Width = 100; it.InitialWidth = 100; items[i] = it end
|
||||||
|
ImGui.ShrinkWidths(items, 1, 4, 2.0, 10.0)
|
||||||
|
local w = {}
|
||||||
|
for _, it in items do w[it.Index] = it.Width end
|
||||||
|
check(w[3] == 100 and w[2] == 100 and w[1] == 99 and w[0] == 99, ("ShrinkWidths widths %s %s %s %s"):format(w[0], w[1], w[2], w[3]))
|
||||||
|
|
||||||
|
check(G.ImFloor(-1.5) == -2 and G.ImFloor(-0.2) == -1 and G.ImFloor(2.7) == 2, "ImFloor negative")
|
||||||
|
|
||||||
|
local long_text = {}
|
||||||
|
for i = 1, 300 do long_text[i] = string.format("line %04d", i) end
|
||||||
|
long_text = table.concat(long_text, "\n") -- 2999 bytes
|
||||||
|
|
||||||
|
local function edit_text()
|
||||||
|
local st = ImGui.GetCurrentContext().InputTextState
|
||||||
|
return G.ImGui._ByteArrayToString(st.TextA.Data, 1, st.TextLen + 1)
|
||||||
|
end
|
||||||
|
local buf = ImGui.NewTextBuffer("", 64)
|
||||||
|
local f, sf, di = 0, 45.0, 50
|
||||||
|
local rects = {}
|
||||||
|
local function rect() local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); return { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end -- copies: GetItemRectMin() returns the live LastItemData.Rect.Min
|
||||||
|
local function click(name, ctrl)
|
||||||
|
local r = rects[name]
|
||||||
|
io:AddMousePosEvent((r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2)
|
||||||
|
if ctrl then io:AddKeyEvent(G.ImGuiMod_Ctrl, true) end
|
||||||
|
io:AddMouseButtonEvent(0, true)
|
||||||
|
end
|
||||||
|
local function release() io:AddMouseButtonEvent(0, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
|
||||||
|
local function key(k) io:AddKeyEvent(k, true); io:AddKeyEvent(k, false) end
|
||||||
|
local function center(name) local r = rects[name]; return (r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2 end
|
||||||
|
local ml_text = "line1\nline2\nline3\nline4\nline5\nline6"
|
||||||
|
local mbuf = ImGui.NewTextBuffer(ml_text, 256)
|
||||||
|
local wbuf = ImGui.NewTextBuffer("a long line that has to be word wrapped several times inside the narrow box\nsecond", 256)
|
||||||
|
local shortcut_frames = {}
|
||||||
|
local cb_col, ce_col = G.ImVec4(1, 0, 0, 1), { 0, 0, 0, 1 }
|
||||||
|
|
||||||
|
game:GetService("RunService").Stepped:Connect(function()
|
||||||
|
f += 1
|
||||||
|
G.ImGui_ImplRoblox.NewFrame()
|
||||||
|
if f == 3 then click("t") elseif f == 4 then release() end
|
||||||
|
-- clipboard: tab is filtered out (single line, no AllowTabInput), é is 2 bytes
|
||||||
|
if f == 6 then ImGui.SetClipboardText("ab\tc\u{e9}"); io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.V, true) end
|
||||||
|
if f == 7 then io:AddKeyEvent(G.ImGuiKey.V, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
|
||||||
|
if f == 9 then key(G.ImGuiKey.Escape) end
|
||||||
|
if f == 11 then click("s", true) elseif f == 12 then release() end
|
||||||
|
if f == 14 then key(G.ImGuiKey.Escape) end
|
||||||
|
if f == 16 then click("d", true) elseif f == 17 then release() end
|
||||||
|
-- multi-line: click, Ctrl+End, type "x", Enter, "y"
|
||||||
|
if f == 21 then click("m") elseif f == 22 then release() end
|
||||||
|
if f == 23 then io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.End, true) end
|
||||||
|
if f == 24 then io:AddKeyEvent(G.ImGuiKey.End, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
|
||||||
|
if f == 25 then io:AddInputCharacter(string.byte("x")) end
|
||||||
|
if f == 26 then key(G.ImGuiKey.Enter) end
|
||||||
|
if f == 28 then io:AddInputCharacter(string.byte("y")) end
|
||||||
|
if f == 29 then io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.A, true) end -- select all: renders the multi-line selection
|
||||||
|
if f == 30 then io:AddKeyEvent(G.ImGuiKey.A, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
|
||||||
|
if f == 31 then key(G.ImGuiKey.Escape) end
|
||||||
|
-- Shortcut(chord) with default flags/owner
|
||||||
|
if f == 33 then io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.S, true) end
|
||||||
|
if f == 34 then io:AddKeyEvent(G.ImGuiKey.S, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
|
||||||
|
-- drag the ColorButton onto the ColorEdit4
|
||||||
|
if f == 36 then io:AddMousePosEvent(center("cb")) end
|
||||||
|
if f == 37 then io:AddMouseButtonEvent(0, true) end
|
||||||
|
if f == 38 then local x, y = center("cb"); io:AddMousePosEvent(x + 10, y + 5) end
|
||||||
|
if f == 39 then io:AddMousePosEvent(center("ce")) end
|
||||||
|
if f == 42 then io:AddMouseButtonEvent(0, false) end
|
||||||
|
-- word-wrapped multi-line: click, Ctrl+End, type "z"
|
||||||
|
if f == 45 then click("w") elseif f == 46 then release() end
|
||||||
|
if f == 47 then io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.End, true) end
|
||||||
|
if f == 48 then io:AddKeyEvent(G.ImGuiKey.End, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end
|
||||||
|
if f == 49 then io:AddInputCharacter(string.byte("z")) end
|
||||||
|
ImGui.NewFrame()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(300, 200))
|
||||||
|
ImGui.Begin("T")
|
||||||
|
if f == 1 then check(ImGui.GetFrameCount() == 1 and ImGui.IsWindowAppearing(), "IsWindowAppearing() on frame 1") end
|
||||||
|
if f == 2 then check(not ImGui.IsWindowAppearing(), "IsWindowAppearing() on frame 2") end
|
||||||
|
ImGui.InputText("##t", buf, 64); rects.t = rect()
|
||||||
|
sf = ImGui.SliderFloat("##s", sf, 0.0, 100.0, "%.0f deg"); rects.s = rect()
|
||||||
|
di = ImGui.DragInt("##d", di, 1, 0, 100, "%d%%"); rects.d = rect()
|
||||||
|
-- CalcItemSize takes 'size' by value: the caller's ImVec2 must not be written to
|
||||||
|
local bsz, csz = ImVec2(0, 0), ImVec2(-1, 40)
|
||||||
|
ImGui.Button("sz", bsz); check(bsz.x == 0 and bsz.y == 0, "Button wrote size_arg")
|
||||||
|
ImGui.BeginChild("c", csz); ImGui.EndChild(); check(csz.x == -1 and csz.y == 40, "BeginChild wrote size_arg")
|
||||||
|
-- drop target rect sticking out of the window clip rect pushes a full-screen clip rect
|
||||||
|
ImGui.RenderDragDropTargetRectForItem(G.ImRect(-50, -50, 900, 900))
|
||||||
|
ImGui.End()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(320, 10)); ImGui.SetNextWindowSize(ImVec2(300, 200))
|
||||||
|
ImGui.Begin("M")
|
||||||
|
ImGui.InputTextMultiline("##m", mbuf, 256, ImVec2(200, ImGui.GetTextLineHeight() * 3)); rects.m = rect()
|
||||||
|
if f == 30 then
|
||||||
|
local st = ImGui.GetCurrentContext().InputTextState
|
||||||
|
check(st.LineCount == 7, "multiline LineCount " .. tostring(st.LineCount))
|
||||||
|
local cw -- the multi-line child window: cursor on the last line scrolled it
|
||||||
|
for _, w in ImGui.GetCurrentContext().Windows.Data do if w.Name:find("^M/##m_") then cw = w end end
|
||||||
|
check(cw ~= nil and cw.Scroll.y > 0, "multiline scroll " .. tostring(cw and cw.Scroll.y))
|
||||||
|
end
|
||||||
|
ImGui.InputTextMultiline("##w", wbuf, 256, ImVec2(120, 80), G.ImGuiInputTextFlags.WordWrap); rects.w = rect()
|
||||||
|
if f == 30 then check(ImGui.GetCurrentContext().InputTextState:HasSelection(), "multiline select all") end
|
||||||
|
if ImGui.Shortcut(bit32.bor(G.ImGuiMod_Ctrl, G.ImGuiKey.S)) then shortcut_frames[f] = true end
|
||||||
|
ImGui.End()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(10, 220)); ImGui.SetNextWindowSize(ImVec2(300, 150))
|
||||||
|
ImGui.Begin("D")
|
||||||
|
ImGui.ColorButton("cb", cb_col); rects.cb = rect()
|
||||||
|
ImGui.ColorEdit4("##ce", ce_col); rects.ce = rect()
|
||||||
|
ImGui.End()
|
||||||
|
-- TextUnformatted() > 2000 bytes (coarse-clipped long text path): each visible line renders only up to its '\n'
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(320, 220)); ImGui.SetNextWindowSize(ImVec2(300, 150))
|
||||||
|
ImGui.Begin("L")
|
||||||
|
local dl = ImGui.GetWindowDrawList()
|
||||||
|
local v0 = dl.VtxBuffer.Size
|
||||||
|
ImGui.TextUnformatted(long_text)
|
||||||
|
local nv = dl.VtxBuffer.Size - v0
|
||||||
|
if f == 3 then check(nv > 0 and nv <= 14 * 9 * 4, "long TextUnformatted vertices " .. nv) end
|
||||||
|
-- PushFont() saves the CURRENT font: PopFont() must restore it
|
||||||
|
local font1 = ImGui.GetFont()
|
||||||
|
check(font2 ~= font1, "font2 is the default font"); ImGui.PushFont(font2, 0.0); ImGui.PopFont()
|
||||||
|
if f == 3 then check(ImGui.GetFont() == font1, "PopFont() did not restore the previous font") end
|
||||||
|
ImGui.End()
|
||||||
|
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||||
|
if f == 8 then check(edit_text() == "abc\u{e9}", "paste: '" .. edit_text() .. "'") end
|
||||||
|
if f == 13 then check(edit_text() == "45", "SliderFloat temp input: '" .. edit_text() .. "'") end
|
||||||
|
if f == 18 then check(edit_text() == "50", "DragInt temp input: '" .. edit_text() .. "'") end
|
||||||
|
if f == 30 then check(ImGui.TextBufferToString(mbuf) == ml_text .. "x\ny", "multiline buffer: '" .. ImGui.TextBufferToString(mbuf) .. "'") end
|
||||||
|
if f == 35 then check(shortcut_frames[33] and not shortcut_frames[32] and not shortcut_frames[34], "Shortcut(Ctrl+S) default args") end
|
||||||
|
if f == 44 then check(ce_col[1] == 1 and ce_col[2] == 0 and ce_col[3] == 0 and ce_col[4] == 1, ("color drop: %s %s %s %s"):format(ce_col[1], ce_col[2], ce_col[3], ce_col[4])) end
|
||||||
|
if f == 51 then check(ImGui.TextBufferToString(wbuf):sub(-7) == "secondz", "word-wrap buffer: '" .. ImGui.TextBufferToString(wbuf) .. "'") end
|
||||||
|
if f == 52 and #failed == 0 then print("FIXES OK") end
|
||||||
|
end)
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
-- Font pieces: ImFontGlyphRangesBuilder, ImFont:CalcWordWrapPosition() vs TextWrapped(), IsGlyphRangeUnused(),
|
||||||
|
-- stb_decompress + Decode85 via AddFontFromMemoryCompressedBase85TTF() (same glyphs/metrics as the raw TTF).
|
||||||
|
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||||
|
local ImGui, ImVec2 = G.ImGui, G.ImVec2
|
||||||
|
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
|
||||||
|
local io = ImGui.GetIO()
|
||||||
|
local failed = {}
|
||||||
|
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
|
||||||
|
|
||||||
|
-- GlyphRangesBuilder
|
||||||
|
local builder = G.ImFontGlyphRangesBuilder()
|
||||||
|
builder:AddText("Hello \u{e9}")
|
||||||
|
local ranges = G.ImVector()
|
||||||
|
builder:BuildRanges(ranges)
|
||||||
|
local got = table.concat(table.move(ranges.Data, 1, ranges.Size, 1, {}), ",")
|
||||||
|
check(got == "32,32,72,72,101,101,108,108,111,111,233,233,0", "BuildRanges " .. got)
|
||||||
|
builder:Clear()
|
||||||
|
builder:AddRanges(io.Fonts:GetGlyphRangesDefault())
|
||||||
|
builder:AddChar(0x41) -- already in range
|
||||||
|
ranges = G.ImVector(); builder:BuildRanges(ranges)
|
||||||
|
check(ranges.Size == 3 and ranges.Data[1] == 0x20 and ranges.Data[2] == 0xFF and ranges.Data[3] == 0, "AddRanges default")
|
||||||
|
check(builder:GetBit(0x20) and not builder:GetBit(0x1F) and not builder:GetBit(0x100), "GetBit")
|
||||||
|
|
||||||
|
-- stb-compressed + base85 ProggyClean.ttf (generated with an stb_compress-format encoder + binary_to_compressed_c's base85)
|
||||||
|
local B85 = ([==[
|
||||||
|
7])#######hV0qs#####]h?>#/:jl&Ql#v#[Zre=N=K_o(-(##mQ%##B>A0F_%#g(g/'##r[(##PF6'I</,GM*BiC$v%###s,0%J*RB8O+d68%eKRrQ(.d<BW_*vCL%###E%###:d@UC%)BE#=.>>#'####<_[FH
|
||||||
|
O97MKO->>#i6YY#i1JuB5eI`[VRUV$=.<T7oH[^ICAS5#T.>>#K####+>00FnH/5`S7g1TS->>#k%S+Hot3>MYJGiTLPUV$>eFVCUwS>#9G:;$n%###Sh(a1aA.;6BE-hAeI1=#au8eZ2D$CI[Mn20Gb?=iMPrIL
|
||||||
|
DSGkFI5>##Q+`AMB%]`ac<%##CFgn*<WFX:&M'^#3@:B#`2B2#[0b30lOj)#-####.%*=Cvf./LVSiHdaT_n#X$###csu8.r:%)34Dk>$<,;.OOMDgEKnIS7EM9>ZoSAZ$*3)qMQv//L^MkA#wmCAMUnG<-7F>HM
|
||||||
|
DHw>Pt*p+M[ul=Q<O]CPUoINNUm+p7S<oG76Mqr)twv9Mt0[0#=S&r;QDqk+SEqFR@0p--.iPDN+Y')$PP;vnQbxRMqJQ*OD:H-M#C]GQJ-jBPO&%^Qmus+M3@C58qNO&#sNk,%?$xu#Fle&,W6=G2,Hb;-;wcP$
|
||||||
|
tPBuuE1V:vm3SML+x0B#/Dabh^KfW-j(.`&V8WQ-2$ZtQQpp5#l,$R#=`i['#l+S#Q*IX#6GMk#op/i#:n*x#tJV0$t#F=$X6iC$1/oh$TrKb$S/kt$&,)O%=q]E%o]n[%6dRd%lGTq%_#`C&$T1_&>Y^Y&2wbq&
|
||||||
|
TB-K'@IZC'%]#V'Uv'n'[R%1(D6?c(OD1$)e>Lr(Z*24)C5Yb)R/uY)#PPj)uaFu)pEt**@7KY*j<MS*5Nki*SAC?+x/x5+ugwH+;f6w+**;8,V?'+,3Y6W,pGkM,sgEa,%5h7-QbL,-Z8sL-.[r<-r;tI-5hnl-
|
||||||
|
Cnqc-CX86.QEKF.,hI9.1W`S%mQrV[C4s&#t3&KDU+DvZ)$SpA>O*dskh5??pb?^Y44[W7m@O9&/?o&YNgT?-'aPdsd(-qA0[9_#WMT9AcO#wm%e;e3?oWq/R1,XIghlWnL4mEX_v(R^+$4:A6.*x6Y+w_,ED)`,
|
||||||
|
PIDRpbf::fN$F.N4Bv:/>80xm6wgLiuWsFXrv)AHt/sl19::#%T1?Ynm49;SFb8/N7n.a><s3T0OpXm(4@4sf)_W/WEcIZ70Ne5_QYMTBLMbT0%t4Tph<HT^;n@*>$P%O)]J/0j$)macqO]pg2V2u$&t@##5f1$#
|
||||||
|
E@%%#Uqn%#fKb&#v&U'#0WH(#@2<)#Pc/*#a=#+#qnl+#+I`,#;$S-#KTF.#[/:/#l`-0#&;w0#6lj1#oiFJ('Moe2;4HZ$mgfX-b/g/HIt71#DRT<Lq8%dM$bLDNoaVV-Pm^dOJ@6##XaYx')_#&4Za=sQ-,7&4
|
||||||
|
Oe)>Pn<<>P0]15/$[K<Lrhci9h&@2LaX:9$;$Ne?L*'?76@Vp.%XK<LdcPe6%l^>$?:b1#^QZ`*uqmV-8lt.ZRwbVRqjtH*PhBQ(-t]N-0*aYMiEVq7a=W4#`6_c)$/*JL(L-^(]$wIMlcs(UsvHv,,rbA#/2u]O
|
||||||
|
hdcHMucWU-k^n:$VGR;oFHnq7e(Q&#uDdE&];bLcqC/^daSxFNtTGw.P3w.Ln1k,O7$22%r]<WS?OY)NJ[vI:DJNGokW_'6Q;I28n8X,Okie;)sJl_fawt-$II`;-sINN$tre+MLFg;-cG,-M<vp%N`tb48]_c6s
|
||||||
|
:#^#$J?HC$,>2U%qS#n8as:jrQ=$##/)>uuN?Sq7^fV2ivEjJN-MT;-UJ;QMM=6##Cni&O1/Ut'7#[UN>+]K3M@2+8UkW1#k.WUN4DU;-j8(1$)[888aOHpgh<7r7#n8I?%R7p7tGF]u%OtlSE4$##6We'8SdfFI
|
||||||
|
WVZ>nZ6c8MPhc(.i10p7x0'.=;lxb-4r'E>qC0%%?].e;I#72LclTAO0qD>$Vvnq2h^fW-LL4OiYW-mNFJuH&O(35&85c58@<iv%VwpGMX#$*&WFjp'jZCx$5N:q8/sgd4LhC^++re_8de;m8?37w%RICm8o4wd+
|
||||||
|
*OhaW?_m.'3XYjM/gh.(xEe58>Y31#_Lx-[?u>q7:5i8K5(;mOM8#=Q'`+%&7'6i#BtxA?0KPM-1PHM&/esr7gC>B$1wGJ(>Q5t7]#:xT:tl.$Tvx;-%&26&`JT58^V_,FWN-c0F####HSI@1K<8FN&?bO'2[IfM
|
||||||
|
j'YWN,<hn3bqwJN0:kC:(Yf>et`e;)LBf68(24gj1WXW'afQfFv7V.8R02?[LUlDN3cha'&]T&#b96$2*T5s7L5jv[&tsR%3dG2M0.(58=RK=`37;ENkWl9)Y7dp7v_k;fk&Z31P>5r'ZC$M&7&i)<U/5F%<bj/:
|
||||||
|
b5vAmK4$##&J<j$&6G<QMA&D:Hbkxu.NTkM6ev.:XCF&#iR7S82tuF%k$IAOvjw:$ku0p7sTMBm9pUh$KAuu#5[,qpn%(.*;?>5SkFGm;i'72Ljb]1#+BcK:Fgs.F8`aBNT($28/7*.H1GrvqoTa2`b:EO-=sKj9
|
||||||
|
crw0#.4D.M(s<A+,DPc=mTGQ:uaAL:Wjp#(R^uw9KvsDN781]&6tgA=_$Dc%.7vcMO>m;51/tW8Y@@S/#1jn8BZT/-Y,lJa-2Ht$i=(HMv'(P8Y>4AuNb6j9hP6(d?^OE23b<Q8)H*nkb$M#1kJAqgUJSk8kK'dk
|
||||||
|
5#uk%xHA88V`;3;KicD%RE-RLwIsJ%#Et,krIK64v,N/Vn@^5+,(;eMs7jf%Pa64:0nhEYGlk%(I9x9Mk3AN9PV*1#m1OL;Y@0qb]2``]dhsp8HNDLPQ(C-M(NbS85^wek`P@'$fCAV8_hGWSE<?tO(=ZU*EK949
|
||||||
|
9ux0#2nKA=dT'^#>sR&ZePWV&-=i>RM-']:uc%4;r>8R$tZgSN$fo1#8&n/;%G7@[Jf4x+.bjq7rlBEYk>h-%(X8j9VCxj2OM:/1_>qs7fEt-$n17tQE&9A.S+Ne+3]a:%$MLp7t]Wx'>3Ks7wtpDP.%L)$Q]BDN
|
||||||
|
NOZf:iskA#j:8T@)BC[.<2N=8c4G`&EW<a&`aQd+s_Pp'a_&6(M@M.$:t1W?4@0#AKhL&OZr_G5Ndap''9nq&@EQkTL0>@$%,m/:PIh)P<e=K8`cI4;5/hu0$qVv[#aKm<Vh;BmVxk4%x3Kn9+Nkxu6Ro94fPQ:8
|
||||||
|
P_)edQ/G0MPJKU11j?NWE?N(80m^&v+J?J:DRPx^YwdHMNf4-:K3<p^FSfo7kKtWSvQjN9)v[B739$^*i-It]VKVv/>Eq3:+x&'-t0sc;>[:</nIMS2He60Vdig`&6]Hj9QO_&RvTf'&mZ]mBRJ3SabaG'?hIi.$
|
||||||
|
$./32CCP)NBxoO(9L(c%jxcp7a.-e4Y5n3-qg-a/9c639%wJ+G5X1e&?5V?-a?>Q-;4*88df9dk@]?Y'/ehS@A:R&m2_(:)QYJwTme-LEcuko$4B:T1=^J$&2(+@-]Ziu,ae_-tbUGDN`-Ra$;hhjW/v[lA3pKq^
|
||||||
|
H<;&(8#DwP3c)/-SoDu7GaHQ:AX=W%+7k,4dQ,N11A>2Bv%03M_/wU&oNi5A)(&3MslOf-.brg=5SHx#7jg]O.F`c)6jK788At1#UfqV-`K<h=Vdb-8U,$90QK@I-E8o3%$grI;4HtdX.Qs$9nga.-c;@v><=ijV
|
||||||
|
anS,,diDack,uG%(q-&>^Ep&?uTh&UIULu:N*DkDaoK>0T>R@@ho/P+uw<T8#pekXXLZ)GN?Hf,CHuU8>pTdx:O3h,p583DQa5frln2-eHrS`V[r7V<$+%Ej0HN,NbN&WQKP9p9qDP-L%r'r(Mj9P+3*GJ*2$/
|
||||||
|
4_R;-22R2$*LKj:':c_-%4+X.)=a78Q<x0#JP;'PY?=(&BgGp8r;7F%@<PWJYRkH,+GX	ePr^o1%/8JE#QUU8jn'S=uPA$TS'J)G=@>^$r?%=FY%8=cFU_Kffo7m-u8NI/5'$NF81.taNq7)?Kq^T+DB)Wg>jV
|
||||||
|
(ckS*I,1qAb-<p^7Mjf%NcuFNgX12)80EuK,1)g0jPmd;%aZp0ggZdM)j&Z$v>ZQS/U@)2.KDgU#Kk95sor>e@>(QYI<%Z*uB-x0*0K39$uQRX^T]M9_U?^?XD-:8t`MqM0pLP9BmS6(xp?e9dca,bq<3K*^w@T[
|
||||||
|
bub7%Vu;S8/G5F%0'8rVUDCnNj9%dMllOf-B)(Wf2(73)oGHH;IoZp0Q1Gp7%Wc&#v6D39^P()HBs(2';aFq72v6b6P77'&h2da<TJ_pT`rE)-rf,78tAk)>6Rm3/Zt8#QHo4`)XmW7h'<>e+2NgM9dboLavtx$$
|
||||||
|
KIK$>1fAoU4NP^0whIt7^iWx''x:Q:`PRB$lhV29)-.K<)dFu3^;mU8UO1S*KTJ/M$=Iw3qi6JN4AFoOalkGNhxNv'HM)?-0(Oh;0:aJsj[lT'5kWm83sdC%hHV7;LSb<S7du7IeKD'mS9JC9Jxr0$t8l>Pxwh9.
|
||||||
|
L,hs7Elx2)+C30*5d'q7#L?T0W#H^Zths.L2*578H]]^-nO,X&2/Z&FM9%%B8&E#>OgR?RC5'T*;Xx9RhdA7(L01H;Pk1NV87(p7Qd41#fFQ;-xe=C$uf['$C+RPs51b-<8x.4rJ;pt-aA,q7Z6QYq]s/FN6PT[%
|
||||||
|
gdf68Omx0#HA2s-tGPSIU*@w^dr,X/kJm;?T$dF#IwGP$hn1a*]p7,M.S?M9a$nx0;,Gp74,IpgKNdm'@Bt784N&:DUix*=bc^<Vh+&G[/;dFGR$=I$_Y##PPEV+8=uWd=5KWB%j&h'FPb84=_lDQ(Red/:KD/F.
|
||||||
|
-o'^#nvkQ8'FjZSZ96Y/o(,,M[jxJjk)lWNSaX%(x11$8SF=[.P8k2B>_sR8w[gJj7*:6:5`)QLb:^i?lw%rMgu>q78aq5M?^w>%IVPp7M8td=/H+s7>&d:;k:&qK=?ex%a*sYPO[+<%&K2;8;fD.$Wk@vPTlC:R
|
||||||
|
,9R>%-u#6AQ]m&6e32?%THYs%m-n58^<(0E=f2#$wonB$QB]m8R(7mMSpse*)jK29*O$90+$bk)VgB^m/i9R$)8LV4iPpdFBFX)-Q6s58&a3dX&TB59&@_[KAuJmSp13/%8Pp'5D2ZJ:j3:`f+5d+Nc2r.([59p7
|
||||||
|
6A#'v>J6u'm[2Q8CS+5rbs14$nK%B>m%6Q:`%P1):BA*N1a-A)96D3#1TCt7#IgE#U/VR=`(qQ(U:c58:RRPDZ+,58@`EdtKP>[PQd/,M9u#>9d*-g)&%B@-j<1e(>ih29via.-S?5`8FO:;B&?ch'PMk8@YG@*n
|
||||||
|
P@st7QGt-$,``wP[ZNC(uTEq70l)?[nS$i$FKgS8jJN9B<kU';c$/32mbBD$4;qeM?5R=MB:K'd@-K^&L.M'Oq4n2$SK%+dwY&(m7/HJ.emw-MLS]0)75,S32BtQ8f3;nqGb=<%Ckb69s#Z&Q^OVv-/vOp7:Mh,F
|
||||||
|
s2K1:'.vhOt3QA%7rt:(?h+G%2V:<@?O#Qqkl2$;YqE#1:xK,M&%uW)DLo;oA^4e1cnV2([,HKDk)U/dN+dP8:N-Up5d1$1LNP^=EMv^ZR7cP8$Obx%xL>p'CUH-MgU,3D7)D_0'?D_=g>d&QMFJk*7&Pp7Gt3[T
|
||||||
|
TwY+Y0v2o0DvI^#s-%g:26KE>o_`h*%L^VH1LbC=3;xji-O7r'Lf7KW4N+[&i7PW87O,i8DUjTBFRS?$UG1o*(VRd;4QG1#mpl%$>iraIcDKQ(d%g4([email protected]?^O&gCAr7t$.Ae*b8k&;1=6`d=_d'-05fD.D)E.
|
||||||
|
uFxZ(d4vQ8.(J(8.%NX-X3RpK:'oLabx4I-1q.:)0.<q0/jF88;0ESCEE8SRN0]u:kpB+$eu*a*2XTe;?@)%IZ[./$HD]BfZ1jk9iCJ32e9R78a=)c<)]<T^'Kc'@lKLKE,LNn%%cl-MpehT$W2`l2b'nj959tT'
|
||||||
|
1wv*$&9#[JNAC]&[mtvPGwA5;ckQdb`;]R$I&gjDXVfh<NTX1#a8T;-*F:`$qqu,MVqgZ$R(*@II4?uuW%QW@4LoKYVb%R8tks-$HG<f;^31ObK;lR(U@k+MLa'VIt)LE%IrM7)wU:EN&W[39ijMG%#w4g:,Xw0#
|
||||||
|
w0[588iGJ$ps_>.,umo%x+B7$S)0p71m99pnERN93)9I$)#(IM_Yd>#6x;XCg]5<-3efW85,/n1#S]C'qqvcD2n4`8u9j<-E`Y%&`Qhg*tt2e$N$t49<#oW&^a*@,$^^dk7xK)$GJP/M>KO0&ND_j9>>P&#JuF.3
|
||||||
|
S<r>I:OPE*XF>dOo#P-(I7Vj;vrlm968R,tB,cS(]FeW&&O`9$-L-5#aS-Ak*o#iSuvYmTPpq9&E*n&%lBap7aS=r7jnP7Ah-]pK>xo_N]mjvPjtJ.2/*'W$4BFQ8bN((&WpULN`$^3i'@+G%.[#.$_6>3)jr[PA
|
||||||
|
&Wp5Uk_uk%Hk@bNH`(S%T)p/MdebV$4iQr7(QMG%k%8bRfU/L.bn/mSCf>SI$`G%h9JSpB%>xsF,^-Ehgn*Q8&j]K<l(Md8m/9MmPZ4j`a4K2`eI,H:m*Zeohdc,X+fTuYcjEE%M^5t7YsvK3FWHI9$lM4#0*joT
|
||||||
|
?tM56R'Aq7$d95#n3nAI&'cm3Y(L-MnV#x'qj+l+liD.-8W;v>@wMQh=6)m$1_b-;I?Q5;7>`*6lg3G;sv&:`[JLN9w<gPqa/S/Mx@aJ%NNa,MG>gr'mKDKNrkdkasN/x0,l$q71^?Xh*X[8^PL*q%6cv58_S*1#
|
||||||
|
bX`0MITc080x^@ilsnlf[L7I%(3mLC(BZE5+?,P@)$+1#DqsJaN-U:/-u?68)p<9j7E%>i?Ia;-kH/D,v(mn8=0'.$Nu49@Bm*1#XYs5CvbQa86j-bS`c5m8RiE1#4l^&#pCa$UOs068Z1-E5RhYl$Oh%TW*)LTA
|
||||||
|
.#$]UnM;TK9(Dl%8`?6B<MiRWdaTl9'OGG.SAd[IOMvu>6E>WgZ3of$O1Up@XbS2L[OtL:aqtqK:M2j*_@-:)LF5F%sd40:3Fr_&]?'k9W@dF<A]r<J?&]lAL2j&-ntJ<(=skq7w^+Ls<q9et?<a^%+$Y98_&Zob
|
||||||
|
vsUqpARc=<ROu7c=ebbOHwO/6Pm@*Ww0]%X*io?<8IQ<iLwrMg?r?eO%E:^-aB#KWu<7b'tRLdt#'-;?A1^FEMk82bPU#-MT>uw&hMM8Ia^vr'sj#j1>f:^OuVh29)g09Tq-.78(S%?7NCU29fbbgNaEJ)<jB'4r
|
||||||
|
(OSP3WS698I9B.$?KS58QV`g$p=@`HW?U/5S&64;1n%:>7rU2`cGs_*,K)1#FCR(Q=Q7i8sDwUjLe.0M2('B$dwW88%vrv@bNOG%K8tMB`6XF-:/::;-:`6r0U=0$``Xp9Knp*-cUpeHl%w,;s+e<_3cs?-#s&r0
|
||||||
|
1B^]02M(wYNshr00dW_ZG]0a;Gx4ND*&9;-HM%VqIL6GDsDZki/S0K)KF,:pgT4,%4K]dM*x8h8WVl6`g#gG$mhmF,,i2v*wBS1MFM0`&,:PP:[J'LPk%']h5(Wxd>i9UXuQk&18(&H;%Ug'hT6S</?xNx1fa5u7
|
||||||
|
DW2QL',t`-Cx#okPutL<%cY'2pP)xPGHHiS^x6Z:R91ObU-iT9O(4m'+%2a*N9pP9(B.-bX%`XNve$U$Bht]=h6rJW<3VM%_j/'Q_7M3,ej5/-Ncf/sSl1]M(Q:['>v_Q93J04r7<,vNP%2d97xbEgJKQGVgB#r3
|
||||||
|
FO4;89*^EfckAhDPC)c<6v690M?r&(GDc^$Ki+58v&LnF)iRTRpw<8Up^,(dPss3,0Z)kT#1i]$-KIZ7LI)3S`ig1(p^^URhRi*N,%$c4L4udO(jG9%[vg2V7I(e&NY<dX,WKw#_&Yj9>hMBQ4T:r2`e0l:lJB$h
|
||||||
|
.`Pg+e]wY%jfNf$)']6K8lDKaC90I.p<3q7dQtA`Q`5?`(qQvc_6b,3O3)9gwixH-]$SR8vjMhdwMD$[V3xR&vCIpT$Vbk1f=RRblisvHe#Zq9Ske(9iR6XLQES29JsZIM-2>rDjiGr^89KO0-^'q718r_&61D58
|
||||||
|
w+r_&.lCW/6iG<-`*wQ%8DQ^OmBjE%9X`E.1EDS@#j/q'J5x8-cxfJssL4($Uflq7hxad=4O:C4K9+hER0^F9G+UQc8W:uQ&/0<A7t3fWC[J90AEOn%&:+kCUYXjd*c-^O.<(d%&0)1#XrV'POoOS$AX+q8IZ4)6
|
||||||
|
hxEFM,hSgN0aEW+x/;N3xJk0&j`S.;edM2i9D+*0GJlB$x:(29wPX&#?l%oS.cb&#`i[`*lQ/,MH@-##FEV6NTF6##$l84N1w?AO>'IAOl%4a*0Y^GM#RcGM9#ult_Ks$#B@`T.1rC$#`@`T.,Mc##T@`T.74i$#
|
||||||
|
WrG<-N2:W.2`($#irG<-<q@u-?Wd`*eC;,M6WYdM6WYdM*?`T.HEX&#`YYdM6WYdM6WYdM%w@(#.E-(#cPc-NZxtk24Y($Jt1^oD0ZKKFg`]oD+,2eGc:@bHcFx.>@nuc<)0o6D]bdUCFdg+>?Wae3;BR12A::MB
|
||||||
|
h]8;-?(DYGUq`uGNDw1B];8;-SHpw'r?Z.#K0f-#=ic+#E<x-#ZZO.#L)1/#wtu+#F:F&#;)1/#O<x-#YSq/#(ZO.#wc-2MVC/$$;^?w^Gm/aNs*p+M&fsD#*J(v#.c_V$2%@8%6=wo%:UWP&>n82'B0pi'FHPJ(
|
||||||
|
Ja1,)N#ic)R;ID*VS*&+Zla]+_.B>,cF#v,_REmh1W@`$0u68%47no%8ONP&<h/2'@*gi'DBGJ(HZ(,)Ls_c)P5@D*TMw%+XfW]+](9>,a@pu,eXPV-iq18.m3io.qKIP/ud*20#'bi0'?BJ19if,=(N^o232;D3
|
||||||
|
7Jr%4;cR]4?%4>5C=ku5GUKV6Kn,87O0do7SHDP8Wa%29[#]i9`;=J:dSt+;hlTc;l.6D<pFm%=t_M]=xw.>>&:fu>*RFV?.k'8@2-_o@6E?PA:^v1Bikur-m-VS.qE75/u^nl/#wNM0'90/1+Qgf1/jGG23,))3
|
||||||
|
7D``3;]@A4?uwx4C7XY5GO9;6Khpr6O*QS7hPn:ZKe0GMgc>c`Mq&Sn8h?YPLS-JUrBB`WH+,AO,Wm+MTm6fqOR(>PCeG`NqHn@k_;LfUox3creaQ(st)7VZ`5Quc4R>MBmeu:QS(f7eQfi:di++PfiuIoe1#Don
|
||||||
|
^w>igqOb1g:-r+DPn?VQ$(_.hbS8GDRI]1pm*m(EM(1DER=L`E2m(GVYq)>G['EYGbKAVHdW]rH0%]4ol2u4Jn>:PJsSUlJ(UFcVwl6MK%53JLTUxLpbwnWH#N#hF@0*K2,4IL2fCd<-gCd<-hCd<-iCd<-jCd<-
|
||||||
|
kCd<-lCd<-mCd<-nCd<-@Dd<-ADd<-BDd<-CDd<-DDd<-EJ)X-v0P]'Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2Gt.g2TmOh2Y#oTC$NXVCThmfGY4&>-K*IT-K*IT-K*IT-
|
||||||
|
K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-K*IT-18ZI'Et,gjm,(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DPO/(DP+-XlC#m8Atkt>Ns
|
||||||
|
]==]):gsub("\n", "")
|
||||||
|
|
||||||
|
local raw_data, raw_size = G.ImStd.ImFileLoadToMemory("fonts/ProggyClean.ttf", "rb")
|
||||||
|
local raw = io.Fonts:AddFontFromMemoryTTF(raw_data, raw_size, 13.0)
|
||||||
|
local comp = io.Fonts:AddFontFromMemoryCompressedBase85TTF(B85, 13.0)
|
||||||
|
check(comp ~= nil, "AddFontFromMemoryCompressedBase85TTF returned nil")
|
||||||
|
local comp_data = comp.Sources.Data[1].FontData
|
||||||
|
local same = comp.Sources.Data[1].FontDataSize == raw_size
|
||||||
|
for n = 1, raw_size do if not same then break end same = comp_data[n] == raw_data[n] end
|
||||||
|
check(same, "decompressed bytes differ from fonts/ProggyClean.ttf")
|
||||||
|
|
||||||
|
local TEXT = "The quick brown fox jumps over the lazy dog again and again"
|
||||||
|
local W = 100
|
||||||
|
local f = 0
|
||||||
|
game:GetService("RunService").Stepped:Connect(function()
|
||||||
|
f += 1
|
||||||
|
G.ImGui_ImplRoblox.NewFrame()
|
||||||
|
ImGui.NewFrame()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(400, 300))
|
||||||
|
ImGui.Begin("Fonts")
|
||||||
|
ImGui.PushFont(raw, 13.0)
|
||||||
|
ImGui.PushTextWrapPos(ImGui.GetCursorPosX() + W)
|
||||||
|
ImGui.TextWrapped(TEXT)
|
||||||
|
local wrapped_h = ImGui.GetItemRectSize().y
|
||||||
|
ImGui.PopTextWrapPos()
|
||||||
|
local line_h = ImGui.GetTextLineHeight()
|
||||||
|
ImGui.PopFont()
|
||||||
|
if f <= 3 then
|
||||||
|
ImGui.PushFont(comp, 13.0)
|
||||||
|
ImGui.Text("Hello \u{e9}")
|
||||||
|
ImGui.PopFont()
|
||||||
|
end
|
||||||
|
ImGui.End()
|
||||||
|
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||||
|
|
||||||
|
if f == 3 then
|
||||||
|
-- CalcWordWrapPosition(): same line breaks as TextWrapped(). ProggyClean 13px = 7px per char -> 14 chars per 100px line
|
||||||
|
local te = #TEXT + 1
|
||||||
|
local s, lines, breaks = 1, 0, {}
|
||||||
|
while s < te do
|
||||||
|
local e = raw:CalcWordWrapPosition(13.0, TEXT, s, te, W)
|
||||||
|
lines += 1; table.insert(breaks, e)
|
||||||
|
s = e
|
||||||
|
while s < te and TEXT:sub(s, s) == " " do s += 1 end -- ImTextCalcWordWrapNextLineStart(): skip blanks
|
||||||
|
end
|
||||||
|
check(breaks[1] == 10, "first wrap after 'The quick': " .. tostring(breaks[1])) -- "The quick" | "brown fox" | ...
|
||||||
|
check(TEXT:sub(11, breaks[2] - 1) == "brown fox", "second line '" .. TEXT:sub(11, breaks[2] - 1) .. "'")
|
||||||
|
check(wrapped_h == lines * line_h, ("TextWrapped height %s vs %d lines * %s"):format(wrapped_h, lines, line_h))
|
||||||
|
|
||||||
|
-- Compressed font: same glyphs and metrics as the raw TTF
|
||||||
|
local br, bc = raw:GetFontBaked(13.0), comp:GetFontBaked(13.0)
|
||||||
|
check(br.Ascent == bc.Ascent and br.Descent == bc.Descent, "ascent/descent")
|
||||||
|
for _, c in { 0x48, 0x65, 0x6C, 0xE9, 0x41, 0x7E } do
|
||||||
|
local gr, gc = br:FindGlyph(c), bc:FindGlyph(c)
|
||||||
|
check(gc.Visible and gc.X1 > gc.X0 and gc.Y1 > gc.Y0, "compressed glyph empty " .. c)
|
||||||
|
check(gr.AdvanceX == gc.AdvanceX and gr.X0 == gc.X0 and gr.Y0 == gc.Y0 and gr.X1 == gc.X1 and gr.Y1 == gc.Y1, "glyph metrics " .. c)
|
||||||
|
end
|
||||||
|
local str = "Hello \u{e9}"
|
||||||
|
local sr, sc = raw:CalcTextSizeA(13.0, 1e30, 0, str, 1, #str + 1), comp:CalcTextSizeA(13.0, 1e30, 0, str, 1, #str + 1)
|
||||||
|
check(sr.x == sc.x and sr.y == sc.y and sc.x > 0, "CalcTextSizeA")
|
||||||
|
check(not comp:IsGlyphRangeUnused(0, 255) and comp:IsGlyphRangeUnused(0x4E00, 0x9FFF), "IsGlyphRangeUnused")
|
||||||
|
|
||||||
|
-- AddRemapCodepoint(): '$' loads the 'A' glyph (applies to glyphs loaded afterwards: new 20px baked)
|
||||||
|
comp:AddRemapCodepoint(0x24, 0x41)
|
||||||
|
local b20 = comp:GetFontBaked(20.0)
|
||||||
|
local gd, ga = b20:FindGlyph(0x24), raw:GetFontBaked(20.0):FindGlyph(0x41)
|
||||||
|
check(gd.X0 == ga.X0 and gd.Y0 == ga.Y0 and gd.X1 == ga.X1 and gd.Y1 == ga.Y1, "AddRemapCodepoint")
|
||||||
|
|
||||||
|
-- RemoveCustomRect()
|
||||||
|
local atlas = io.Fonts
|
||||||
|
local id = atlas:AddCustomRect(8, 8)
|
||||||
|
check(atlas:GetCustomRect(id, G.ImFontAtlasRect()) == true, "AddCustomRect")
|
||||||
|
atlas:RemoveCustomRect(id)
|
||||||
|
check(not atlas:GetCustomRect(id, G.ImFontAtlasRect()), "RemoveCustomRect")
|
||||||
|
|
||||||
|
-- RemoveFont()
|
||||||
|
local nfonts, nsrc = atlas.Fonts.Size, atlas.Sources.Size
|
||||||
|
atlas:RemoveFont(comp)
|
||||||
|
check(atlas.Fonts.Size == nfonts - 1 and atlas.Sources.Size == nsrc - 1 and not atlas.Fonts:contains(comp), "RemoveFont")
|
||||||
|
|
||||||
|
-- ImFontAtlasTextureBlockFill(), ImTextureDataGetXXXName()
|
||||||
|
local tex = G.ImTextureData(); tex:Create(G.ImTextureFormat.RGBA32, 4, 4)
|
||||||
|
G.ImFontAtlasTextureBlockFill(tex, 1, 2, 2, 1, G.IM_COL32(1, 2, 3, 4))
|
||||||
|
local px = table.concat(tex.Pixels, ",", 33, 48)
|
||||||
|
check(px == "0,0,0,0,1,2,3,4,1,2,3,4,0,0,0,0" and tex.Pixels[29] == 0 and tex.Pixels[53] == 0, "TextureBlockFill " .. px)
|
||||||
|
check(G.ImTextureDataGetStatusName(tex.Status) == "WantCreate" and G.ImTextureDataGetFormatName(tex.Format) == "RGBA32", "texture names")
|
||||||
|
end
|
||||||
|
if f == 5 and #failed == 0 then print("FONTS OK") end
|
||||||
|
end)
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
-- Small upstream helpers: ImGuiInputTextState selection API + ReloadUserBuf*, ImGuiInputTextCallbackData:SetSelection, ImDrawList AddImageQuad/PrimQuadUV/
|
||||||
|
-- CloneOutput, ImDrawData DeIndexAllBuffers/ScaleClipRects, ImBitVector/ImBitArray*, ImGuiPackedDate, ImParseFormatSanitizeForPrinting, drag cancel (ShortcutsForCancel),
|
||||||
|
-- log decorations, ImGuiDataType.S64/U64, BringWindowToDisplayBehind, ImGuiSizeCallbackData
|
||||||
|
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||||
|
local ImGui, ImVec2 = G.ImGui, G.ImVec2
|
||||||
|
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
|
||||||
|
local io = ImGui.GetIO()
|
||||||
|
local failed = {}
|
||||||
|
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
|
||||||
|
|
||||||
|
-- Pure helpers
|
||||||
|
check(G.ImParseFormatSanitizeForPrinting("%.3f%%", nil, 32) == "%.3f", "sanitize %.3f%%")
|
||||||
|
check(G.ImParseFormatSanitizeForPrinting("%'_5.2f units", nil, 32) == "%5.2f", "sanitize stb flags: " .. G.ImParseFormatSanitizeForPrinting("%'_5.2f units", nil, 32))
|
||||||
|
local d = G.ImGuiPackedDate(20260315)
|
||||||
|
check(d:IsValid() and d:Unpack() == 20260315, "PackedDate round trip " .. d:Unpack())
|
||||||
|
d:SubtractMonths(3)
|
||||||
|
check(d:Unpack() == 20251215, "PackedDate SubtractMonths " .. d:Unpack())
|
||||||
|
check(not G.ImGuiPackedDate():IsValid() and G.ImGuiPackedDate():Unpack() == 0, "PackedDate empty")
|
||||||
|
local bv = G.ImBitVector()
|
||||||
|
bv:Create(70)
|
||||||
|
check(bv.Storage.Size == 3, "ImBitVector size")
|
||||||
|
bv:SetBit(1); bv:SetBit(70); bv:SetBit(33)
|
||||||
|
check(bv:TestBit(1) and bv:TestBit(70) and bv:TestBit(33) and not bv:TestBit(2) and not bv:TestBit(69), "ImBitVector SetBit/TestBit")
|
||||||
|
bv:ClearBit(33)
|
||||||
|
check(not bv:TestBit(33) and bv:TestBit(70), "ImBitVector ClearBit")
|
||||||
|
local arr = {}
|
||||||
|
G.ImBitArrayClearAllBits(arr, 96)
|
||||||
|
G.ImBitArraySetBitRange(arr, 3, 70) -- [3..70)
|
||||||
|
local ok_range = true
|
||||||
|
for n = 1, 96 do if G.ImBitArrayTestBit(arr, n) ~= (n >= 3 and n < 70) then ok_range = false end end
|
||||||
|
check(ok_range, "ImBitArraySetBitRange")
|
||||||
|
check(G.ImBitArrayGetStorageSizeInBytes(70) == 12, "ImBitArrayGetStorageSizeInBytes")
|
||||||
|
check(G.ImModPositive(-1, 5) == 4 and G.ImRoundSigned64(-1.5) == -2 and G.ImCharIsXdigitA(string.byte("F")), "math helpers")
|
||||||
|
|
||||||
|
local buf = ImGui.NewTextBuffer("hello world", 64)
|
||||||
|
local cbuf = ImGui.NewTextBuffer("abcdef", 64)
|
||||||
|
local cb_calls = 0
|
||||||
|
local function cb(data)
|
||||||
|
cb_calls += 1
|
||||||
|
if cb_calls == 1 then data:SetSelection(1, 3) end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
local df, rect_df = 10.0, nil
|
||||||
|
local s64_v = 2 ^ 40
|
||||||
|
local f = 0
|
||||||
|
local function center(r) return (r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2 end
|
||||||
|
|
||||||
|
local function vtx(dl, i) -- 1-based vertex
|
||||||
|
local b, o = dl.VtxBuffer.Buf, (i - 1) * G.VTX_STRIDE
|
||||||
|
return buffer.readf64(b, o), buffer.readf64(b, o + 8), buffer.readf64(b, o + 16), buffer.readf64(b, o + 24), buffer.readu32(b, o + 32)
|
||||||
|
end
|
||||||
|
|
||||||
|
game:GetService("RunService").Stepped:Connect(function()
|
||||||
|
f += 1
|
||||||
|
G.ImGui_ImplRoblox.NewFrame()
|
||||||
|
-- drag + Escape cancels and reverts (ShortcutsForCancel)
|
||||||
|
if f == 20 then io:AddMousePosEvent(center(rect_df)) end
|
||||||
|
if f == 21 then io:AddMouseButtonEvent(0, true) end
|
||||||
|
if f == 22 then local x, y = center(rect_df); io:AddMousePosEvent(x + 40, y) end
|
||||||
|
if f == 24 then io:AddKeyEvent(G.ImGuiKey.Escape, true) end
|
||||||
|
if f == 25 then io:AddKeyEvent(G.ImGuiKey.Escape, false) end
|
||||||
|
if f == 26 then io:AddMouseButtonEvent(0, false) end
|
||||||
|
ImGui.NewFrame()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(400, 300))
|
||||||
|
ImGui.Begin("H")
|
||||||
|
if f == 2 then ImGui.SetKeyboardFocusHere() end
|
||||||
|
ImGui.InputText("##t", buf, 64)
|
||||||
|
local id = ImGui.GetItemID()
|
||||||
|
local st = ImGui.GetInputTextState(id)
|
||||||
|
if f == 5 then
|
||||||
|
check(st ~= nil, "InputText active")
|
||||||
|
if st then
|
||||||
|
st:SetSelection(3, 6)
|
||||||
|
check(st:GetSelectionStart() == 3 and st:GetSelectionEnd() == 6 and st:GetCursorPos() == 6 and st:HasSelection(), "SetSelection/Get*")
|
||||||
|
st:ClearSelection()
|
||||||
|
check(st:GetSelectionStart() == 6 and st:GetSelectionEnd() == 6 and not st:HasSelection(), "ClearSelection")
|
||||||
|
check(st:GetPreferredOffsetX() == -1, "GetPreferredOffsetX")
|
||||||
|
-- modify the user buffer externally then reload
|
||||||
|
local s = "bye"; for i = 1, #s do buf[i] = string.byte(s, i) end; buf[#s + 1] = 0
|
||||||
|
st:ReloadUserBufAndSelectAll()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if f == 7 and st then
|
||||||
|
check(G.ImGui._ByteArrayToString(st:GetText(), 1, st.TextLen + 1) == "bye", "ReloadUserBufAndSelectAll text")
|
||||||
|
check(st:GetSelectionStart() == 1 and st:GetSelectionEnd() == 4, ("ReloadUserBufAndSelectAll sel %d..%d"):format(st:GetSelectionStart(), st:GetSelectionEnd()))
|
||||||
|
st:SetSelection(2, 3); st:ReloadUserBufAndKeepSelection()
|
||||||
|
end
|
||||||
|
if f == 9 and st then
|
||||||
|
check(st:GetSelectionStart() == 2 and st:GetSelectionEnd() == 3, "ReloadUserBufAndKeepSelection")
|
||||||
|
st:ReloadUserBufAndMoveToEnd()
|
||||||
|
end
|
||||||
|
if f == 11 and st then
|
||||||
|
check(st:GetCursorPos() == 4 and not st:HasSelection(), "ReloadUserBufAndMoveToEnd " .. st:GetCursorPos())
|
||||||
|
end
|
||||||
|
-- callback SetSelection (0-based callback positions -> 1-based stb)
|
||||||
|
if f == 12 then ImGui.SetKeyboardFocusHere() end
|
||||||
|
ImGui.InputText("##c", cbuf, 64, G.ImGuiInputTextFlags.CallbackAlways, cb)
|
||||||
|
if f == 15 then
|
||||||
|
local cst = ImGui.GetInputTextState(ImGui.GetItemID())
|
||||||
|
check(cst ~= nil and cb_calls > 0 and cst:GetSelectionStart() == 2 and cst:GetSelectionEnd() == 4, "callback SetSelection " .. (cst and (cst:GetSelectionStart() .. ".." .. cst:GetSelectionEnd()) or "nil"))
|
||||||
|
end
|
||||||
|
df = ImGui.DragFloat("##df", df, 1.0)
|
||||||
|
local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); rect_df = { ImVec2(a.x, a.y), ImVec2(b.x, b.y) }
|
||||||
|
if f == 23 then check(df ~= 10.0, "drag changed value " .. df) end
|
||||||
|
if f == 25 then check(df == 10.0 and ImGui.GetActiveID() == 0, "Escape reverts drag " .. df) end
|
||||||
|
|
||||||
|
if f == 30 then
|
||||||
|
local dl = ImGui.GetWindowDrawList()
|
||||||
|
local v0, i0 = dl.VtxBuffer.Size, dl.IdxBuffer.Size
|
||||||
|
local tex = G.ImTextureRef(1234)
|
||||||
|
dl:AddImageQuad(tex, ImVec2(10, 10), ImVec2(50, 12), ImVec2(48, 60), ImVec2(8, 58), ImVec2(0, 0), ImVec2(1, 0), ImVec2(1, 1), ImVec2(0, 1), 0xFF00FF00)
|
||||||
|
check(dl.VtxBuffer.Size == v0 + 4 and dl.IdxBuffer.Size == i0 + 6, "AddImageQuad counts")
|
||||||
|
local x, y, u, v, col = vtx(dl, v0 + 3)
|
||||||
|
check(x == 48 and y == 60 and u == 1 and v == 1 and col == 0xFF00FF00, ("PrimQuadUV vertex %s %s %s %s %x"):format(x, y, u, v, col))
|
||||||
|
local idx = dl.IdxBuffer.Data
|
||||||
|
check(idx[i0 + 1] == v0 + 1 and idx[i0 + 5] == v0 + 3 and idx[i0 + 6] == v0 + 4, "PrimQuadUV indices")
|
||||||
|
local cmin, cmax = dl:GetClipRectMin(), dl:GetClipRectMax()
|
||||||
|
check(cmax.x > cmin.x and cmax.y > cmin.y, "GetClipRectMin/Max")
|
||||||
|
|
||||||
|
local clone = dl:CloneOutput()
|
||||||
|
check(clone.VtxBuffer.Size == dl.VtxBuffer.Size and clone.IdxBuffer.Size == dl.IdxBuffer.Size and clone.CmdBuffer.Size == dl.CmdBuffer.Size
|
||||||
|
and clone.VtxBuffer.Buf ~= dl.VtxBuffer.Buf and clone.CmdBuffer.Data[1] ~= dl.CmdBuffer.Data[1], "CloneOutput copies buffers")
|
||||||
|
local cx = vtx(clone, v0 + 3)
|
||||||
|
check(cx == 48, "CloneOutput vertex data")
|
||||||
|
|
||||||
|
local dd = G.ImDrawData()
|
||||||
|
dd.CmdLists:push_back(clone)
|
||||||
|
local n_idx = clone.IdxBuffer.Size
|
||||||
|
local ex, ey = vtx(clone, clone.IdxBuffer.Data[n_idx])
|
||||||
|
dd:DeIndexAllBuffers()
|
||||||
|
check(clone.IdxBuffer.Size == 0 and clone.VtxBuffer.Size == n_idx and dd.TotalVtxCount == n_idx and dd.TotalIdxCount == 0, "DeIndexAllBuffers counts")
|
||||||
|
local lx, ly = vtx(clone, n_idx)
|
||||||
|
check(lx == ex and ly == ey, "DeIndexAllBuffers vertex data")
|
||||||
|
|
||||||
|
local cr = clone.CmdBuffer.Data[1].ClipRect
|
||||||
|
local x1, y1, z1, w1 = cr.x, cr.y, cr.z, cr.w
|
||||||
|
dd:ScaleClipRects(ImVec2(2, 3))
|
||||||
|
local cr2 = clone.CmdBuffer.Data[1].ClipRect
|
||||||
|
check(cr2.x == x1 * 2 and cr2.y == y1 * 3 and cr2.z == z1 * 2 and cr2.w == w1 * 3 and dl.CmdBuffer.Data[1].ClipRect.z == z1, "ScaleClipRects")
|
||||||
|
end
|
||||||
|
|
||||||
|
if f == 31 then
|
||||||
|
local g = ImGui.GetCurrentContext()
|
||||||
|
local style = ImGui.GetStyle()
|
||||||
|
local window = ImGui.GetCurrentWindow()
|
||||||
|
check(G.ImGuiKey["0"] == G.ImGuiKey.K0 and G.ImGuiKey["9"] == G.ImGuiKey.K9, "ImGuiKey digit aliases")
|
||||||
|
|
||||||
|
-- Style vars (enum values must match GStyleVarsInfo order)
|
||||||
|
local sv = G.ImGuiStyleVar
|
||||||
|
local b1, b2, b3 = style.SeparatorSize, style.SeparatorTextBorderSize, style.DockingSeparatorSize
|
||||||
|
ImGui.PushStyleVar(sv.SeparatorSize, 5); ImGui.PushStyleVar(sv.SeparatorTextBorderSize, 6); ImGui.PushStyleVar(sv.DockingSeparatorSize, 7)
|
||||||
|
check(style.SeparatorSize == 5 and style.SeparatorTextBorderSize == 6 and style.DockingSeparatorSize == 7, "PushStyleVar SeparatorSize/SeparatorTextBorderSize/DockingSeparatorSize")
|
||||||
|
ImGui.PushStyleVar(sv.SeparatorTextPadding, ImVec2(3, 4))
|
||||||
|
check(style.SeparatorTextPadding.x == 3 and style.SeparatorTextPadding.y == 4, "PushStyleVar SeparatorTextPadding")
|
||||||
|
ImGui.PopStyleVar(4)
|
||||||
|
check(style.SeparatorSize == b1 and style.SeparatorTextBorderSize == b2 and style.DockingSeparatorSize == b3, "PopStyleVar restores")
|
||||||
|
|
||||||
|
-- Log flags
|
||||||
|
ImGui.LogToBuffer()
|
||||||
|
check(g.LogEnabled and g.LogFlags == G.ImGuiLogFlags.OutputBuffer, "LogToBuffer sets ImGuiLogFlags.OutputBuffer")
|
||||||
|
ImGui.Text("logged")
|
||||||
|
check(string.find(g.LogBuffer:c_str(), "logged", 1, true) ~= nil, "LogToBuffer captures text")
|
||||||
|
ImGui.LogFinish()
|
||||||
|
check(g.LogFlags == G.ImGuiLogFlags.None and not g.LogEnabled, "LogFinish clears LogFlags")
|
||||||
|
|
||||||
|
-- Combo preview (no ImGuiComboFlags.CustomPreview needed since 1.93)
|
||||||
|
if ImGui.BeginCombo("##cb", "") then ImGui.EndCombo() end
|
||||||
|
local pd = g.ComboPreviewData
|
||||||
|
local work_max_x, content_max_x = window.WorkRect.Max.x, window.ContentRegionRect.Max.x
|
||||||
|
local opened = ImGui.BeginComboPreview()
|
||||||
|
check(opened and pd.WithinPreview and window.WorkRect.Max.x == pd.PreviewRect.Max.x - style.FramePadding.x and window.ContentRegionRect.Max.x == window.WorkRect.Max.x, "BeginComboPreview sets WorkRect")
|
||||||
|
if opened then
|
||||||
|
ImGui.Text("preview")
|
||||||
|
ImGui.EndComboPreview()
|
||||||
|
end
|
||||||
|
check(not pd.WithinPreview and window.WorkRect.Max.x == work_max_x and window.ContentRegionRect.Max.x == content_max_x, "EndComboPreview restores WorkRect")
|
||||||
|
if ImGui.BeginCombo("##cb2", "x", G.ImGuiComboFlags.NoPreview) then ImGui.EndCombo() end
|
||||||
|
check(not ImGui.BeginComboPreview(), "BeginComboPreview with NoPreview returns false")
|
||||||
|
|
||||||
|
-- ListClipper in a table: ImGuiListClipperFlags.NoSetTableRowCounters
|
||||||
|
local rows = {}
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(500, 10)); ImGui.SetNextWindowSize(ImVec2(300, 400))
|
||||||
|
ImGui.Begin("Clip")
|
||||||
|
for pass = 1, 2 do
|
||||||
|
if ImGui.BeginTable("##clip" .. pass, 1, G.ImGuiTableFlags.ScrollY, ImVec2(0, 60)) then
|
||||||
|
local clipper = G.ImGuiListClipper()
|
||||||
|
if pass == 2 then clipper.Flags = G.ImGuiListClipperFlags.NoSetTableRowCounters end
|
||||||
|
clipper:Begin(1000)
|
||||||
|
while clipper:Step() do
|
||||||
|
for i = clipper.DisplayStart, clipper.DisplayEnd - 1 do ImGui.TableNextRow(); ImGui.TableNextColumn(); ImGui.Text("%d", i) end
|
||||||
|
end
|
||||||
|
clipper:End()
|
||||||
|
rows[pass] = ImGui.GetCurrentTable().CurrentRow
|
||||||
|
local tbl = ImGui.GetCurrentTable()
|
||||||
|
ImGui.EndTable()
|
||||||
|
if pass == 1 then ImGui.SetNextItemOpen(true); ImGui.DebugNodeTable(tbl) end -- DebugNodeTableGetSizingPolicyDesc()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ImGui.End()
|
||||||
|
check(rows[1] ~= nil and rows[2] ~= nil and rows[1] >= 990 and rows[2] < 100, ("clipper row counters %s / NoSetTableRowCounters %s"):format(tostring(rows[1]), tostring(rows[2])))
|
||||||
|
|
||||||
|
-- .ini LastUsed dates + CleanupIniSettings()
|
||||||
|
local pio = ImGui.GetPlatformIO()
|
||||||
|
pio.Platform_SessionDate = 20261015
|
||||||
|
g.SessionDate = G.ImGuiPackedDate(pio.Platform_SessionDate) -- (normally done by NewFrame())
|
||||||
|
io.ConfigIniSettingsAutoDiscardMonths = 6
|
||||||
|
ImGui.LoadIniSettingsFromMemory("[Window][Old]\nPos=1,1\nLastUsed=20250101\n\n[Window][NoDate]\nPos=2,2\n\n[Window][Recent]\nPos=3,3\nLastUsed=20260901\n\n")
|
||||||
|
io.ConfigIniSettingsAutoDiscardMonths = 0
|
||||||
|
local function ws(name) for _, st in g.SettingsWindows.Data do if st.Name == name then return st end end; return nil end
|
||||||
|
check(ws("Old").WantDelete and ws("NoDate").WantDelete and not ws("Recent").WantDelete and ws("Recent").LastUsedDate == 20260901, "ConfigIniSettingsAutoDiscardMonths discards old/undated entries")
|
||||||
|
local args = G.ImGuiSettingsCleanupArgs()
|
||||||
|
args.SetCurrentSessionDateToAll = true
|
||||||
|
args.TypeHashFilter = G.ImHashStr("Window")
|
||||||
|
ImGui.CleanupIniSettings(args)
|
||||||
|
check(ws("Recent").LastUsedDate == 20261015, "CleanupIniSettings SetCurrentSessionDateToAll")
|
||||||
|
local ini = ImGui.SaveIniSettingsToMemory()
|
||||||
|
check(not string.find(ini, "[Window][Old]", 1, true) and string.find(ini, "[Window][Recent]\nPos=3,3\nCollapsed=0\nLastUsed=20261015\n", 1, true) ~= nil
|
||||||
|
and string.find(ini, "[Window][H]\nPos=10,10\nSize=400,300\nCollapsed=0\nLastUsed=20261015\n", 1, true) ~= nil, "LastUsed written to .ini:\n" .. ini)
|
||||||
|
io.ConfigIniSettingsSaveLastUsedDate = false
|
||||||
|
check(not string.find(ImGui.SaveIniSettingsToMemory(), "LastUsed", 1, true), "ConfigIniSettingsSaveLastUsedDate = false")
|
||||||
|
io.ConfigIniSettingsSaveLastUsedDate = true
|
||||||
|
pio.Platform_SessionDate = 0
|
||||||
|
|
||||||
|
-- Log decorations: Selectable/Button/Checkbox/MenuItem shortcut
|
||||||
|
ImGui.LogToBuffer()
|
||||||
|
ImGui.Selectable("sel item")
|
||||||
|
ImGui.Button("btn")
|
||||||
|
ImGui.Checkbox("chk", true)
|
||||||
|
ImGui.MenuItem("mi", "Ctrl+M")
|
||||||
|
local log = g.LogBuffer:c_str()
|
||||||
|
ImGui.LogFinish()
|
||||||
|
check(string.find(log, "[sel item]", 1, true) == nil and string.find(log, "sel item", 1, true) ~= nil, "Selectable logs plain label: " .. log)
|
||||||
|
check(string.find(log, "[ btn ]", 1, true) ~= nil and string.find(log, "[x]", 1, true) ~= nil and string.find(log, "mi ( Ctrl+M )", 1, true) ~= nil, "LogSetNextTextDecoration [ ]: " .. log)
|
||||||
|
|
||||||
|
-- DragScalar S64/U64 (doubles) + DataTypeFormatString
|
||||||
|
local v64, ch = ImGui.DragScalar("##s64", G.ImGuiDataType.S64, 2 ^ 40, 1.0)
|
||||||
|
check(v64 == 2 ^ 40 and ch == false, "DragScalar S64 idle")
|
||||||
|
local b64 = {}
|
||||||
|
local n = ImGui.DataTypeFormatString(b64, 64, G.ImGuiDataType.S64, -(2 ^ 40), "%lld")
|
||||||
|
check(G.ImGui._ByteArrayToString(b64, 1, n + 1) == "-1099511627776", "DataTypeFormatString S64")
|
||||||
|
n = ImGui.DataTypeFormatString(b64, 64, G.ImGuiDataType.U64, 18446744073709551615, "%llu")
|
||||||
|
check(G.ImGui._ByteArrayToString(b64, 1, n + 1) == "18446744073709551615", "DataTypeFormatString U64 max")
|
||||||
|
n = ImGui.DataTypeFormatString(b64, 64, G.ImGuiDataType.S32, -5, "%X")
|
||||||
|
check(G.ImGui._ByteArrayToString(b64, 1, n + 1) == "FFFFFFFB", "DataTypeFormatString S32 hex")
|
||||||
|
local tb = ImGui.NewTextBuffer("-123456789012", 64)
|
||||||
|
local r64, c64 = ImGui.DataTypeApplyFromText(tb, G.ImGuiDataType.S64, 0, "%lld")
|
||||||
|
check(r64 == -123456789012 and c64, "DataTypeApplyFromText S64 " .. tostring(r64))
|
||||||
|
check(ImGui.DataTypeApplyOp(G.ImGuiDataType.S64, 43, 2 ^ 62, 2 ^ 62) == 9223372036854775807, "DataTypeApplyOp S64 clamp")
|
||||||
|
check(G.ImGuiDataType.Float == 8 and G.ImGuiDataType.COUNT == 12 and ImGui.DataTypeGetInfo(G.ImGuiDataType.Float).Name == "float", "ImGuiDataType layout")
|
||||||
|
end
|
||||||
|
-- DragScalar S64: drag with the mouse
|
||||||
|
local s64_rect
|
||||||
|
s64_v = ImGui.DragScalar("##drag_s64", G.ImGuiDataType.S64, s64_v, 1.0)
|
||||||
|
do local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); s64_rect = { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end
|
||||||
|
if f == 33 then io:AddMousePosEvent(center(s64_rect)) end
|
||||||
|
if f == 34 then io:AddMouseButtonEvent(0, true) end
|
||||||
|
if f == 35 then local x, y = center(s64_rect); io:AddMousePosEvent(x + 30, y) end
|
||||||
|
if f == 37 then io:AddMouseButtonEvent(0, false); check(s64_v > 2 ^ 40 and s64_v == math.floor(s64_v), "DragScalar S64 drag " .. s64_v) end
|
||||||
|
ImGui.End()
|
||||||
|
|
||||||
|
-- BringWindowToDisplayBehind
|
||||||
|
if f == 38 then
|
||||||
|
local g = ImGui.GetCurrentContext()
|
||||||
|
local wa, wb, wc = ImGui.FindWindowByName("WA"), ImGui.FindWindowByName("WB"), ImGui.FindWindowByName("WC")
|
||||||
|
local function idx(w) return ImGui.FindWindowDisplayIndex(w) end
|
||||||
|
ImGui.BringWindowToDisplayFront(wa); ImGui.BringWindowToDisplayFront(wb); ImGui.BringWindowToDisplayFront(wc)
|
||||||
|
local n0 = g.Windows.Size
|
||||||
|
ImGui.BringWindowToDisplayBehind(wc, wa) -- move forward-placed window behind: WC WA WB
|
||||||
|
check(idx(wc) + 1 == idx(wa) and idx(wa) + 1 == idx(wb) and g.Windows.Size == n0, ("BringWindowToDisplayBehind back %d %d %d"):format(idx(wa), idx(wb), idx(wc)))
|
||||||
|
ImGui.BringWindowToDisplayBehind(wa, wb) -- already directly behind: unchanged
|
||||||
|
check(idx(wc) + 1 == idx(wa) and idx(wa) + 1 == idx(wb), "BringWindowToDisplayBehind no-op")
|
||||||
|
ImGui.BringWindowToDisplayBehind(wc, wb) -- WA WC WB
|
||||||
|
check(idx(wa) + 1 == idx(wc) and idx(wc) + 1 == idx(wb) and g.Windows.Size == n0, ("BringWindowToDisplayBehind fwd %d %d %d"):format(idx(wa), idx(wb), idx(wc)))
|
||||||
|
end
|
||||||
|
for _, name in { "WA", "WB", "WC" } do
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(820, 10)); ImGui.SetNextWindowSize(ImVec2(80, 60))
|
||||||
|
ImGui.Begin(name); ImGui.End()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Size callback (Step + AspectRatio)
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(10, 420))
|
||||||
|
if f <= 3 then ImGui.SetNextWindowSize(ImVec2(237, 160)) end
|
||||||
|
ImGui.SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(G.FLT_MAX, G.FLT_MAX), function(data)
|
||||||
|
local step = data.UserData
|
||||||
|
check(data.Pos ~= nil and data.CurrentSize ~= nil and not rawequal(data.DesiredSize, data.CurrentSize), "ImGuiSizeCallbackData fields")
|
||||||
|
data.DesiredSize = ImVec2(math.floor(data.DesiredSize.x / step + 0.5) * step, math.floor(data.DesiredSize.y / step + 0.5) * step)
|
||||||
|
end, 100.0)
|
||||||
|
ImGui.Begin("Step"); local step_size = ImGui.GetWindowSize(); ImGui.End()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(300, 420))
|
||||||
|
if f <= 3 then ImGui.SetNextWindowSize(ImVec2(320, 100)) end
|
||||||
|
ImGui.SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(G.FLT_MAX, G.FLT_MAX), function(data)
|
||||||
|
data.DesiredSize.y = math.floor(data.DesiredSize.x / data.UserData)
|
||||||
|
end, 16 / 9)
|
||||||
|
ImGui.Begin("Aspect"); local asp_size = ImGui.GetWindowSize(); ImGui.End()
|
||||||
|
if f == 5 then
|
||||||
|
check(step_size.x == 200 and step_size.y == 200, ("size callback Step %gx%g"):format(step_size.x, step_size.y))
|
||||||
|
check(asp_size.x == 320 and asp_size.y == 180, ("size callback AspectRatio %gx%g"):format(asp_size.x, asp_size.y))
|
||||||
|
end
|
||||||
|
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||||
|
if f == 40 and #failed == 0 then print("HELPERS OK") end
|
||||||
|
end)
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
-- Library-only draw-data checksum (no demo, run with NO_DEMO=1): widgets + raw ImDrawList primitives with scripted input.
|
||||||
|
-- Must stay identical across demo edits; changes only when library rendering changes. Prints "LIBHASH <n>".
|
||||||
|
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||||
|
local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4
|
||||||
|
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init(); G.ImGui_ImplRoblox.SetRenderRate(0)
|
||||||
|
local io = ImGui.GetIO()
|
||||||
|
local frame, h = 0, 0
|
||||||
|
local function mix(v) h = (h * 31 + math.floor(v * 64 + 0.5)) % 2147483647 end
|
||||||
|
local st = { b = false, f = 0.25, i = 42, c = ImVec4(0.2, 0.7, 0.4, 1.0), buf = ImGui.NewTextBuffer("hello", 64), r = 0, tab = 0, combo = 1, list = 2 }
|
||||||
|
local plot = {}
|
||||||
|
for i = 1, 40 do plot[i] = math.sin(i * 0.3) end
|
||||||
|
local poly = { ImVec2(0, 0), ImVec2(60, 10), ImVec2(30, 30), ImVec2(70, 60), ImVec2(0, 50) }
|
||||||
|
|
||||||
|
game:GetService("RunService").Stepped:Connect(function()
|
||||||
|
frame += 1
|
||||||
|
G.ImGui_ImplRoblox.NewFrame()
|
||||||
|
io.DeltaTime = 1 / 60
|
||||||
|
io:AddMousePosEvent(20 + (frame * 37) % 560, 20 + (frame * 53) % 560)
|
||||||
|
if frame % 7 == 0 then io:AddMouseButtonEvent(0, frame % 14 == 0) end
|
||||||
|
if frame % 11 == 0 then io:AddMouseWheelEvent(0, -1) end
|
||||||
|
ImGui.NewFrame()
|
||||||
|
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(10, 10), G.ImGuiCond.Always); ImGui.SetNextWindowSize(ImVec2(320, 560), G.ImGuiCond.Always)
|
||||||
|
ImGui.Begin("Widgets")
|
||||||
|
ImGui.Text("Text %d", 123); ImGui.TextColored(ImVec4(1, 0.5, 0, 1), "Colored"); ImGui.TextWrapped("Wrapped text that is long enough to wrap around the window edge at least once.")
|
||||||
|
ImGui.Button("Button"); ImGui.SameLine(); ImGui.SmallButton("Small"); ImGui.SameLine(); ImGui.ArrowButton("##a", G.ImGuiDir.Left)
|
||||||
|
st.b = ImGui.Checkbox("Checkbox", st.b)
|
||||||
|
st.r = ImGui.RadioButton("R0", st.r, 0); ImGui.SameLine(); st.r = ImGui.RadioButton("R1", st.r, 1)
|
||||||
|
st.f = ImGui.SliderFloat("Slider", st.f, 0, 1); st.i = ImGui.DragInt("Drag", st.i, 1, 0, 100)
|
||||||
|
ImGui.InputText("Input", st.buf, 64)
|
||||||
|
ImGui.ColorEdit4("Color", st.c)
|
||||||
|
st.combo = ImGui.Combo("Combo", st.combo, { "AAA", "BBB", "CCC" })
|
||||||
|
st.list = ImGui.ListBox("List", st.list, { "one", "two", "three", "four" }, 4, 3)
|
||||||
|
ImGui.ProgressBar(0.4, ImVec2(-1, 0)); ImGui.ProgressBar(-1.0 * frame / 60, ImVec2(-1, 0), "busy")
|
||||||
|
ImGui.PlotLines("Lines", plot, nil, #plot); ImGui.PlotHistogram("Hist", plot, nil, #plot, 0, nil, -1, 1, ImVec2(0, 40))
|
||||||
|
ImGui.Bullet(); ImGui.Text("bullet"); ImGui.Separator(); ImGui.SeparatorText("Sep")
|
||||||
|
if ImGui.TreeNode("Tree") then ImGui.Text("inside"); ImGui.TreePop() end
|
||||||
|
ImGui.SetNextItemOpen(true, G.ImGuiCond.Once)
|
||||||
|
if ImGui.CollapsingHeader("Header") then
|
||||||
|
if ImGui.BeginTabBar("tabs") then
|
||||||
|
if ImGui.BeginTabItem("Tab A") then ImGui.Text("A"); ImGui.EndTabItem() end
|
||||||
|
if ImGui.BeginTabItem("Tab B") then ImGui.Text("B"); ImGui.EndTabItem() end
|
||||||
|
ImGui.EndTabBar()
|
||||||
|
end
|
||||||
|
if ImGui.BeginTable("t", 3, bit32.bor(G.ImGuiTableFlags.Borders, G.ImGuiTableFlags.RowBg, G.ImGuiTableFlags.Resizable)) then
|
||||||
|
ImGui.TableSetupColumn("One"); ImGui.TableSetupColumn("Two"); ImGui.TableSetupColumn("Three"); ImGui.TableHeadersRow()
|
||||||
|
for row = 0, 4 do ImGui.TableNextRow(); for col = 0, 2 do ImGui.TableSetColumnIndex(col); ImGui.Text("r%d c%d", row, col) end end
|
||||||
|
ImGui.EndTable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ImGui.End()
|
||||||
|
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(340, 10), G.ImGuiCond.Always); ImGui.SetNextWindowSize(ImVec2(300, 560), G.ImGuiCond.Always)
|
||||||
|
ImGui.Begin("Draw")
|
||||||
|
local dl, p = ImGui.GetWindowDrawList(), ImGui.GetCursorScreenPos()
|
||||||
|
local x, y, col = p.x + 4, p.y + 4, 0xFF40C0FF
|
||||||
|
for i, th in { 1.0, 2.5, 6.0 } do
|
||||||
|
local yy = y + (i - 1) * 70
|
||||||
|
dl:AddRect(ImVec2(x, yy), ImVec2(x + 50, yy + 50), col, 10.0, th)
|
||||||
|
dl:AddRectFilled(ImVec2(x + 60, yy), ImVec2(x + 110, yy + 50), col, 10.0, G.ImDrawFlags.RoundTopLeft or G.ImDrawFlags.RoundCornersTopLeft) -- RoundCornersTopLeft: pre-1.93 revs (hashes.sh)
|
||||||
|
dl:AddCircle(ImVec2(x + 145, yy + 25), 25, col, 0, th)
|
||||||
|
dl:AddNgon(ImVec2(x + 200, yy + 25), 25, col, 6, th)
|
||||||
|
dl:AddTriangle(ImVec2(x + 230, yy + 50), ImVec2(x + 255, yy), ImVec2(x + 280, yy + 50), col, th)
|
||||||
|
dl:AddLine(ImVec2(x, yy + 60), ImVec2(x + 280, yy + 55), col, th)
|
||||||
|
end
|
||||||
|
y += 210
|
||||||
|
dl:AddBezierCubic(ImVec2(x, y), ImVec2(x + 60, y - 30), ImVec2(x + 120, y + 60), ImVec2(x + 180, y), col, 3.0)
|
||||||
|
dl:AddBezierQuadratic(ImVec2(x, y + 30), ImVec2(x + 90, y - 10), ImVec2(x + 180, y + 30), col, 2.0)
|
||||||
|
dl:AddEllipse(ImVec2(x + 230, y + 15), ImVec2(40, 15), col, 0.5, 0, 2.0)
|
||||||
|
y += 50
|
||||||
|
local pts = {}
|
||||||
|
for i, v in poly do pts[i] = ImVec2(x + v.x, y + v.y) end
|
||||||
|
dl:AddConcavePolyFilled(pts, #pts, 0xFF8080FF)
|
||||||
|
for i, v in poly do pts[i] = ImVec2(x + 100 + v.x, y + v.y) end
|
||||||
|
dl:AddPolyline(pts, #pts, col, 4.0, G.ImDrawFlags.Closed)
|
||||||
|
for i, v in poly do pts[i] = ImVec2(x + 200 + v.x, y + v.y) end
|
||||||
|
dl:AddPolyline(pts, #pts, col, 1.0, 0)
|
||||||
|
y += 80
|
||||||
|
dl:AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + 100, y + 40), 0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFFFFFF)
|
||||||
|
dl:AddCircleFilled(ImVec2(x + 140, y + 20), 20, col)
|
||||||
|
dl:AddEllipseFilled(ImVec2(x + 210, y + 20), ImVec2(30, 15), col, 0.3)
|
||||||
|
dl:AddText(ImVec2(x, y + 50), 0xFFFFFFFF, "AddText")
|
||||||
|
ImGui.End()
|
||||||
|
|
||||||
|
ImGui.Render()
|
||||||
|
for _, dl in ImGui.GetDrawData().CmdLists:iter() do
|
||||||
|
local vb, id = dl.VtxBuffer.Buf, dl.IdxBuffer.Data
|
||||||
|
for i = 1, dl.VtxBuffer.Size do local o = (i - 1) * 36; mix(buffer.readf64(vb, o)); mix(buffer.readf64(vb, o + 8)); mix(buffer.readf64(vb, o + 16) * 4096); mix(buffer.readf64(vb, o + 24) * 4096); mix(buffer.readu32(vb, o + 32) / 64) end
|
||||||
|
for i = 1, dl.IdxBuffer.Size do mix(id[i]) end
|
||||||
|
for _, c in dl.CmdBuffer:iter() do mix(c.ClipRect.x); mix(c.ClipRect.y); mix(c.ClipRect.z); mix(c.ClipRect.w); mix(c.ElemCount) end
|
||||||
|
end
|
||||||
|
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||||
|
if frame == ARG_FRAMES - 1 then print("LIBHASH", h) end
|
||||||
|
end)
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
-- Multi-select / box-select / typing-select: ImGuiSelectionBasicStorage + Selectable list (click, Ctrl+click, Shift+click, Ctrl+A,
|
||||||
|
-- Escape, box-select drag, keyboard nav + Shift, typing-select), ImGuiListClipper + IncludeItemByIndex(RangeSrcItem),
|
||||||
|
-- ImGuiSelectionExternalStorage on a bool array, checkboxes and tree nodes inside a multi-select scope.
|
||||||
|
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||||
|
local ImGui, ImVec2 = G.ImGui, G.ImVec2
|
||||||
|
local MSF = G.ImGuiMultiSelectFlags
|
||||||
|
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
|
||||||
|
local io = ImGui.GetIO()
|
||||||
|
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.NavEnableKeyboard)
|
||||||
|
local failed = {}
|
||||||
|
local function check(ok, msg) if not ok then table.insert(failed, msg); print("FAIL " .. msg) end end
|
||||||
|
|
||||||
|
local NAMES = { "Apple", "Apricot", "Banana", "Blueberry", "Cherry", "Coconut", "Date", "Durian", "Elderberry", "Fig",
|
||||||
|
"Grape", "Guava", "Kiwi", "Lemon", "Lime", "Mango", "Melon", "Orange", "Papaya", "Peach" }
|
||||||
|
local N = #NAMES
|
||||||
|
|
||||||
|
-- Iterate with GetNextSelectedItem() (sorted by key) -> "a,b,c"
|
||||||
|
local function sel_str(sel)
|
||||||
|
local out, it, id, has_more = {}, nil, 0, false
|
||||||
|
while true do
|
||||||
|
has_more, it, id = sel:GetNextSelectedItem(it)
|
||||||
|
if not has_more then break end
|
||||||
|
table.insert(out, tostring(id))
|
||||||
|
end
|
||||||
|
return table.concat(out, ",")
|
||||||
|
end
|
||||||
|
local function bools_str(t, n)
|
||||||
|
local out = {}
|
||||||
|
for i = 1, n do if t[i] then table.insert(out, tostring(i - 1)) end end
|
||||||
|
return table.concat(out, ",")
|
||||||
|
end
|
||||||
|
|
||||||
|
local sel_list = G.ImGuiSelectionBasicStorage()
|
||||||
|
local sel_clip = G.ImGuiSelectionBasicStorage()
|
||||||
|
local sel_tree = G.ImGuiSelectionBasicStorage()
|
||||||
|
local ext_bools = {}
|
||||||
|
local sel_ext = G.ImGuiSelectionExternalStorage()
|
||||||
|
sel_ext.UserData = ext_bools
|
||||||
|
sel_ext.AdapterSetItemSelected = function(self, idx, selected) self.UserData[idx + 1] = selected end
|
||||||
|
local chk_bools = {}
|
||||||
|
local sel_chk = G.ImGuiSelectionExternalStorage()
|
||||||
|
sel_chk.UserData = chk_bools
|
||||||
|
sel_chk.AdapterSetItemSelected = function(self, idx, selected) self.UserData[idx + 1] = selected end
|
||||||
|
|
||||||
|
local rects = {} -- [name] = { min, max } (copies)
|
||||||
|
local ids = {} -- [name] = item id
|
||||||
|
local function rect() local a, b = ImGui.GetItemRectMin(), ImGui.GetItemRectMax(); return { ImVec2(a.x, a.y), ImVec2(b.x, b.y) } end
|
||||||
|
local function center(name) local r = rects[name]; return (r[1].x + r[2].x) / 2, (r[1].y + r[2].y) / 2 end
|
||||||
|
local function key(k) io:AddKeyEvent(k, true); io:AddKeyEvent(k, false) end
|
||||||
|
local function mods(ctrl, shift) io:AddKeyEvent(G.ImGuiMod_Ctrl, ctrl == true); io:AddKeyEvent(G.ImGuiMod_Shift, shift == true) end
|
||||||
|
|
||||||
|
-- Schedule: each step = { input = fn (before NewFrame), check = fn (after Render) }. Steps run on consecutive frames.
|
||||||
|
local steps = {}
|
||||||
|
local function step(input, chk) table.insert(steps, { input = input, check = chk }) end
|
||||||
|
local function idle(n) for _ = 1, n or 1 do step(nil, nil) end end
|
||||||
|
-- click: press (+mods) on frame A, release on frame B, then one idle frame for EndMultiSelect requests to be applied
|
||||||
|
local function click(name, ctrl, shift)
|
||||||
|
step(function() mods(ctrl, shift); io:AddMousePosEvent(center(name)); io:AddMouseButtonEvent(0, true) end)
|
||||||
|
step(function() io:AddMouseButtonEvent(0, false) end)
|
||||||
|
step(function() mods(false, false) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local typed_focus_idx = -1
|
||||||
|
local clip_scroll_request = false
|
||||||
|
local f = 0
|
||||||
|
|
||||||
|
idle(3)
|
||||||
|
-- 1. List: click selects one
|
||||||
|
click("L2"); step(nil, function() check(sel_str(sel_list) == "2", "click: " .. sel_str(sel_list)) end)
|
||||||
|
-- 2. Ctrl+click toggles
|
||||||
|
click("L5", true); step(nil, function() check(sel_str(sel_list) == "2,5", "ctrl+click add: " .. sel_str(sel_list)) end)
|
||||||
|
click("L2", true); step(nil, function() check(sel_str(sel_list) == "5", "ctrl+click remove: " .. sel_str(sel_list)) end)
|
||||||
|
-- 3. Shift+click range (from RangeSrc = 2, the last Ctrl+clicked item)
|
||||||
|
click("L8", false, true); step(nil, function() check(sel_str(sel_list) == "2,3,4,5,6,7,8", "shift+click range: " .. sel_str(sel_list)) end)
|
||||||
|
-- 4. Ctrl+A
|
||||||
|
step(function() io:AddKeyEvent(G.ImGuiMod_Ctrl, true); io:AddKeyEvent(G.ImGuiKey.A, true) end)
|
||||||
|
step(function() io:AddKeyEvent(G.ImGuiKey.A, false); io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end)
|
||||||
|
step(nil, function() check(sel_list.Size == N, "ctrl+A size " .. sel_list.Size) end)
|
||||||
|
-- 5. Escape clears (ClearOnEscape)
|
||||||
|
step(function() key(G.ImGuiKey.Escape) end)
|
||||||
|
step(nil, function() check(sel_list.Size == 0 and sel_str(sel_list) == "", "escape clear: " .. sel_str(sel_list)) end)
|
||||||
|
-- 6. Box-select drag (BoxSelect1d) from item 2 to item 5
|
||||||
|
step(function() io:AddMousePosEvent(center("L2")); io:AddMouseButtonEvent(0, true) end)
|
||||||
|
step(function() local x, y = center("L2"); io:AddMousePosEvent(x + 2, y + 10) end)
|
||||||
|
step(function() local x, y = center("L4"); io:AddMousePosEvent(x + 4, y) end)
|
||||||
|
step(function() local x, y = center("L5"); io:AddMousePosEvent(x + 4, y) end,
|
||||||
|
function() check(ImGui.GetCurrentContext().BoxSelectState.IsActive, "box-select active while dragging") end)
|
||||||
|
step(nil)
|
||||||
|
step(function() io:AddMouseButtonEvent(0, false) end)
|
||||||
|
step(nil, function() check(sel_str(sel_list) == "2,3,4,5", "box-select: " .. sel_str(sel_list)); check(not ImGui.GetCurrentContext().BoxSelectState.IsActive, "box-select ended") end)
|
||||||
|
-- 7. Keyboard nav: click 3, Down -> 4 (auto-select), Shift+Down x2 -> 4..6
|
||||||
|
click("L3"); step(nil, function() check(sel_str(sel_list) == "3", "click before nav: " .. sel_str(sel_list)) end)
|
||||||
|
step(function() key(G.ImGuiKey.DownArrow) end); idle(2)
|
||||||
|
step(nil, function() check(sel_str(sel_list) == "4", "nav down auto-select: " .. sel_str(sel_list)) end)
|
||||||
|
step(function() io:AddKeyEvent(G.ImGuiMod_Shift, true); key(G.ImGuiKey.DownArrow) end); idle(1)
|
||||||
|
step(function() key(G.ImGuiKey.DownArrow) end); idle(1)
|
||||||
|
step(function() io:AddKeyEvent(G.ImGuiMod_Shift, false) end); idle(1)
|
||||||
|
step(nil, function() check(sel_str(sel_list) == "4,5,6", "shift+nav range: " .. sel_str(sel_list)) end)
|
||||||
|
-- 8. Typing-select: "ch" -> Cherry (5), then single-char mode "b" "b" -> Banana (2) then Blueberry (3)
|
||||||
|
step(function() io:AddInputCharacter(string.byte("c")); io:AddInputCharacter(string.byte("h")) end); idle(2)
|
||||||
|
step(nil, function() check(ImGui.GetCurrentContext().NavId == ids.L4, "typing 'ch' -> Cherry"); check(sel_str(sel_list) == "4", "typing focus selects: " .. sel_str(sel_list)) end)
|
||||||
|
idle(120) -- TYPING_SELECT_RESET_TIMER
|
||||||
|
step(function() io:AddInputCharacter(string.byte("b")) end); idle(2)
|
||||||
|
step(nil, function() check(ImGui.GetCurrentContext().NavId == ids.L2, "typing 'b' -> Banana") end)
|
||||||
|
step(function() io:AddInputCharacter(string.byte("b")) end); idle(2)
|
||||||
|
step(nil, function() check(ImGui.GetCurrentContext().NavId == ids.L3, "typing 'bb' -> Blueberry") end)
|
||||||
|
idle(120)
|
||||||
|
|
||||||
|
-- 9. Clipper (1000 items): click 5, scroll down, Shift+click item 40 -> 5..40 (RangeSrcItem 5 kept submitted by IncludeItemByIndex)
|
||||||
|
click("C5")
|
||||||
|
-- move NavId away with Ctrl+Down (no selection change, RangeSrc stays 5) so the clipper doesn't keep item 5 alive as the nav item
|
||||||
|
step(function() io:AddKeyEvent(G.ImGuiMod_Ctrl, true); key(G.ImGuiKey.DownArrow) end); idle(1)
|
||||||
|
step(function() key(G.ImGuiKey.DownArrow) end); idle(1)
|
||||||
|
step(function() key(G.ImGuiKey.DownArrow) end); idle(1)
|
||||||
|
step(function() io:AddKeyEvent(G.ImGuiMod_Ctrl, false) end)
|
||||||
|
step(nil, function() check(sel_str(sel_clip) == "5", "clipper ctrl+nav keeps selection: " .. sel_str(sel_clip)) end)
|
||||||
|
step(function() clip_scroll_request = true end); idle(2)
|
||||||
|
step(nil, function() check(rects.C40 ~= nil and rects.C5 == nil, "clipper scrolled (item 5 not visible)") end)
|
||||||
|
click("C40", false, true)
|
||||||
|
step(nil, function() check(sel_clip.Size == 36, "clipper shift+click range size " .. sel_clip.Size); check(sel_clip:Contains(5) and sel_clip:Contains(40) and not sel_clip:Contains(4) and not sel_clip:Contains(41), "clipper range bounds") end)
|
||||||
|
|
||||||
|
-- 10. External storage on bool array: click 1, Shift+click 3, Ctrl+click 2
|
||||||
|
click("E1"); click("E3", false, true)
|
||||||
|
step(nil, function() check(bools_str(ext_bools, 10) == "1,2,3", "external storage range: " .. bools_str(ext_bools, 10)) end)
|
||||||
|
click("E2", true)
|
||||||
|
step(nil, function() check(bools_str(ext_bools, 10) == "1,3", "external storage ctrl+click: " .. bools_str(ext_bools, 10)) end)
|
||||||
|
|
||||||
|
-- 11. Checkboxes (NoAutoSelect|NoAutoClear): click 1 toggles on, Shift+click 4 -> 1..4 on, click 2 toggles off
|
||||||
|
click("K1"); step(nil, function() check(bools_str(chk_bools, 8) == "1", "checkbox click: " .. bools_str(chk_bools, 8)) end)
|
||||||
|
click("K4", false, true); step(nil, function() check(bools_str(chk_bools, 8) == "1,2,3,4", "checkbox shift+click: " .. bools_str(chk_bools, 8)) end)
|
||||||
|
click("K2"); step(nil, function() check(bools_str(chk_bools, 8) == "1,3,4", "checkbox toggle off: " .. bools_str(chk_bools, 8)) end)
|
||||||
|
|
||||||
|
-- 12. Tree nodes: click 0, Ctrl+click 2, Shift+click 4 (range from 2)
|
||||||
|
click("T0"); click("T2", true)
|
||||||
|
step(nil, function() check(sel_str(sel_tree) == "0,2", "tree ctrl+click: " .. sel_str(sel_tree)) end)
|
||||||
|
click("T4", false, true)
|
||||||
|
step(nil, function() check(sel_str(sel_tree) == "2,3,4", "tree shift+click: " .. sel_str(sel_tree)) end)
|
||||||
|
|
||||||
|
-- 13. Metrics window renders the TypingSelect / MultiSelect debug nodes
|
||||||
|
local show_metrics = false
|
||||||
|
step(function() show_metrics = true end); idle(3)
|
||||||
|
step(nil, function() if #failed == 0 then print("MULTISELECT OK") end end)
|
||||||
|
|
||||||
|
local function list_window()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(10, 10)); ImGui.SetNextWindowSize(ImVec2(200, 420))
|
||||||
|
ImGui.Begin("L")
|
||||||
|
local flags = bit32.bor(MSF.ClearOnEscape, MSF.BoxSelect1d)
|
||||||
|
local ms_io = ImGui.BeginMultiSelect(flags, sel_list.Size, N)
|
||||||
|
sel_list:ApplyRequests(ms_io)
|
||||||
|
-- typing-select
|
||||||
|
local focus_idx = -1
|
||||||
|
if ImGui.IsWindowFocused() then
|
||||||
|
local req = ImGui.GetTypingSelectRequest(G.ImGuiTypingSelectFlags.AllowSingleCharMode)
|
||||||
|
local nav_idx = (type(ms_io.NavIdItem) == "number") and ms_io.NavIdItem or -1
|
||||||
|
focus_idx = ImGui.TypingSelectFindMatch(req, N, function(_, idx) return NAMES[idx + 1] end, nil, nav_idx)
|
||||||
|
end
|
||||||
|
for n = 0, N - 1 do
|
||||||
|
if n == focus_idx then ImGui.SetKeyboardFocusHere() end
|
||||||
|
ImGui.SetNextItemSelectionUserData(n)
|
||||||
|
ImGui.Selectable(NAMES[n + 1], sel_list:Contains(n))
|
||||||
|
rects["L" .. n] = rect(); ids["L" .. n] = ImGui.GetItemID()
|
||||||
|
end
|
||||||
|
ms_io = ImGui.EndMultiSelect()
|
||||||
|
sel_list:ApplyRequests(ms_io)
|
||||||
|
ImGui.End()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function clipper_window()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(220, 10)); ImGui.SetNextWindowSize(ImVec2(200, 300))
|
||||||
|
ImGui.Begin("C")
|
||||||
|
if clip_scroll_request then ImGui.SetScrollY(ImGui.GetTextLineHeightWithSpacing() * 30); clip_scroll_request = false end
|
||||||
|
local COUNT = 1000
|
||||||
|
local ms_io = ImGui.BeginMultiSelect(MSF.ClearOnEscape, sel_clip.Size, COUNT)
|
||||||
|
sel_clip:ApplyRequests(ms_io)
|
||||||
|
local clipper = G.ImGuiListClipper()
|
||||||
|
clipper:Begin(COUNT)
|
||||||
|
if ms_io.RangeSrcItem ~= -1 then clipper:IncludeItemByIndex(ms_io.RangeSrcItem) end -- Ensure RangeSrc item is not clipped.
|
||||||
|
for k in rects do if string.sub(k, 1, 1) == "C" then rects[k] = nil end end
|
||||||
|
while clipper:Step() do
|
||||||
|
for n = clipper.DisplayStart, clipper.DisplayEnd - 1 do
|
||||||
|
ImGui.SetNextItemSelectionUserData(n)
|
||||||
|
ImGui.Selectable(("Object %04d"):format(n), sel_clip:Contains(n))
|
||||||
|
if ImGui.IsItemVisible() then rects["C" .. n] = rect() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ms_io = ImGui.EndMultiSelect()
|
||||||
|
sel_clip:ApplyRequests(ms_io)
|
||||||
|
ImGui.End()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function external_window()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(430, 10)); ImGui.SetNextWindowSize(ImVec2(180, 220))
|
||||||
|
ImGui.Begin("E")
|
||||||
|
local ms_io = ImGui.BeginMultiSelect(MSF.None, -1, 10)
|
||||||
|
sel_ext:ApplyRequests(ms_io)
|
||||||
|
for n = 0, 9 do
|
||||||
|
ImGui.SetNextItemSelectionUserData(n)
|
||||||
|
ImGui.Selectable("Ext " .. n, ext_bools[n + 1] == true)
|
||||||
|
rects["E" .. n] = rect()
|
||||||
|
end
|
||||||
|
ms_io = ImGui.EndMultiSelect()
|
||||||
|
sel_ext:ApplyRequests(ms_io)
|
||||||
|
ImGui.End()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function checkbox_window()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(430, 240)); ImGui.SetNextWindowSize(ImVec2(180, 200))
|
||||||
|
ImGui.Begin("K")
|
||||||
|
local ms_io = ImGui.BeginMultiSelect(bit32.bor(MSF.NoAutoSelect, MSF.NoAutoClear, MSF.ClearOnEscape), -1, 8)
|
||||||
|
sel_chk:ApplyRequests(ms_io)
|
||||||
|
for n = 0, 7 do
|
||||||
|
ImGui.SetNextItemSelectionUserData(n)
|
||||||
|
local _, v = ImGui.Checkbox("Check " .. n, chk_bools[n + 1] == true)
|
||||||
|
rects["K" .. n] = rect()
|
||||||
|
local _ = v -- the selection is updated through requests (ApplyRequests), like upstream's demo
|
||||||
|
end
|
||||||
|
ms_io = ImGui.EndMultiSelect()
|
||||||
|
sel_chk:ApplyRequests(ms_io)
|
||||||
|
ImGui.End()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tree_window()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(220, 320)); ImGui.SetNextWindowSize(ImVec2(200, 160))
|
||||||
|
ImGui.Begin("T")
|
||||||
|
local ms_io = ImGui.BeginMultiSelect(MSF.None, sel_tree.Size, 5)
|
||||||
|
sel_tree:ApplyRequests(ms_io)
|
||||||
|
for n = 0, 4 do
|
||||||
|
local flags = bit32.bor(G.ImGuiTreeNodeFlags.Leaf, G.ImGuiTreeNodeFlags.NoTreePushOnOpen, G.ImGuiTreeNodeFlags.SpanAvailWidth)
|
||||||
|
if sel_tree:Contains(n) then flags = bit32.bor(flags, G.ImGuiTreeNodeFlags.Selected) end
|
||||||
|
ImGui.SetNextItemSelectionUserData(n)
|
||||||
|
ImGui.TreeNodeEx("Node " .. n, flags)
|
||||||
|
rects["T" .. n] = rect()
|
||||||
|
end
|
||||||
|
ms_io = ImGui.EndMultiSelect()
|
||||||
|
sel_tree:ApplyRequests(ms_io)
|
||||||
|
ImGui.End()
|
||||||
|
end
|
||||||
|
|
||||||
|
game:GetService("RunService").Stepped:Connect(function()
|
||||||
|
f += 1
|
||||||
|
G.ImGui_ImplRoblox.NewFrame()
|
||||||
|
local s = steps[f]
|
||||||
|
if s and s.input then s.input() end
|
||||||
|
ImGui.NewFrame()
|
||||||
|
list_window()
|
||||||
|
clipper_window()
|
||||||
|
external_window()
|
||||||
|
checkbox_window()
|
||||||
|
tree_window()
|
||||||
|
if show_metrics then
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(620, 10)); ImGui.SetNextWindowSize(ImVec2(400, 600))
|
||||||
|
ImGui.ShowMetricsWindow()
|
||||||
|
local ctx = ImGui.GetCurrentContext()
|
||||||
|
ImGui.Begin("DebugNodes")
|
||||||
|
ImGui.DebugNodeTypingSelectState(ctx.TypingSelectState)
|
||||||
|
for n = 0, ctx.MultiSelectStorage:GetMapSize() - 1 do
|
||||||
|
local st = ctx.MultiSelectStorage:GetByIndex(n)
|
||||||
|
if st then ImGui.SetNextItemOpen(true); ImGui.DebugNodeMultiSelectState(st) end
|
||||||
|
end
|
||||||
|
ImGui.End()
|
||||||
|
end
|
||||||
|
ImGui.Render(); G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||||
|
if s and s.check then s.check() end
|
||||||
|
end)
|
||||||
|
print(("t_multiselect: %d steps"):format(#steps))
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
-- Backend pixel oracle: composites the ScreenGui (like t_composite) at checkpoints and prints "PIX <frame> <hash>".
|
||||||
|
-- Phases: resize (bottom-right, then top-left), move, scroll, hover, typing, new font sizes (glyph baking), colour drag.
|
||||||
|
-- Each phase ends with a few still frames, then a checkpoint. Run with RUN_FLAGS="ARG_FORCE=true" for a full redraw
|
||||||
|
-- every frame: incremental output must match it exactly (and match the previous backend version).
|
||||||
|
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||||
|
local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4
|
||||||
|
ImGui.CreateContext()
|
||||||
|
G.ImGui_ImplRoblox.Init()
|
||||||
|
G.ImGui_ImplRoblox.SetRenderRate(0)
|
||||||
|
if ARG_TILELIMIT then ImGui.GetIO().BackendPlatformUserData.TileLimit = ARG_TILELIMIT end
|
||||||
|
local io = ImGui.GetIO()
|
||||||
|
local W, H = 1280, 760
|
||||||
|
local col = ImVec4(0.4, 0.7, 0.2, 0.5)
|
||||||
|
local buf = ImGui.NewTextBuffer("type here", 128)
|
||||||
|
local slider = 0.3
|
||||||
|
|
||||||
|
local function composite(gui)
|
||||||
|
local screen = buffer.create(W * H * 3)
|
||||||
|
local function walk(inst, ox, oy, kx0, ky0, kx1, ky1)
|
||||||
|
local kids = inst:GetChildren()
|
||||||
|
table.sort(kids, function(a, b) return (a.ZIndex or 1) < (b.ZIndex or 1) end)
|
||||||
|
for _, c in ipairs(kids) do
|
||||||
|
if c.Visible ~= false then
|
||||||
|
local x, y = ox + (c.Position and c.Position.X or 0), oy + (c.Position and c.Position.Y or 0)
|
||||||
|
local cx0, cy0, cx1, cy1 = kx0, ky0, kx1, ky1
|
||||||
|
if c.ClipsDescendants then
|
||||||
|
cx0, cy0 = math.max(cx0, x), math.max(cy0, y)
|
||||||
|
cx1, cy1 = math.min(cx1, x + c.Size.X), math.min(cy1, y + c.Size.Y)
|
||||||
|
end
|
||||||
|
if c.ClassName == "ImageLabel" then
|
||||||
|
local img = c.ImageContent.Object
|
||||||
|
local px, sz = img.Pixels, img.Size.X
|
||||||
|
for ty = 0, sz - 1 do
|
||||||
|
local sy = y + ty
|
||||||
|
if sy >= cy0 and sy < cy1 then
|
||||||
|
for tx = 0, sz - 1 do
|
||||||
|
local sx = x + tx
|
||||||
|
if sx >= cx0 and sx < cx1 then
|
||||||
|
local o = (ty * sz + tx) * 4
|
||||||
|
local a = buffer.readu8(px, o + 3)
|
||||||
|
if a > 0 then
|
||||||
|
local d = (sy * W + sx) * 3
|
||||||
|
local fa = a / 255
|
||||||
|
for ch = 0, 2 do
|
||||||
|
local v = buffer.readu8(px, o + ch) * fa + buffer.readu8(screen, d + ch) * (1 - fa)
|
||||||
|
buffer.writeu8(screen, d + ch, math.floor(v + 0.5))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
walk(c, x, y, cx0, cy0, cx1, cy1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
walk(gui, 0, 0, 0, 0, W, H)
|
||||||
|
local h = 0
|
||||||
|
for i = 0, W * H * 3 - 4, 4 do h = (h * 31 + buffer.readu32(screen, i)) % 2147483647 end
|
||||||
|
return h
|
||||||
|
end
|
||||||
|
|
||||||
|
-- phase list: name, frames of action (then 6 still frames + checkpoint)
|
||||||
|
local PHASES = { { "start", 4 }, { "resize_br", 24 }, { "resize_tl", 24 }, { "move", 24 }, { "scroll", 30 }, { "hover", 30 },
|
||||||
|
{ "type", 24 }, { "fonts", 24 }, { "color", 20 } }
|
||||||
|
local STILL = ARG_STILL or (ARG_TILELIMIT and 40 or 6) -- tile limit: deferred tiles need more frames to settle
|
||||||
|
local PT = {}
|
||||||
|
local pi, pf = 1, 0
|
||||||
|
local pos, size, scroll, fsize = ImVec2(40, 40), ImVec2(460, 520), 0, 13
|
||||||
|
local frame = 0
|
||||||
|
local typing = "Hello, glyphs! 0123 xyz"
|
||||||
|
|
||||||
|
game:GetService("RunService").Stepped:Connect(function()
|
||||||
|
local ph = PHASES[pi]
|
||||||
|
if not ph then return end
|
||||||
|
frame += 1; pf += 1
|
||||||
|
local name, n = ph[1], ph[2]
|
||||||
|
local act = pf <= n
|
||||||
|
G.ImGui_ImplRoblox.NewFrame()
|
||||||
|
io.DeltaTime = 1 / 60
|
||||||
|
io:AddMousePosEvent(1200, 740)
|
||||||
|
if act then
|
||||||
|
if name == "resize_br" then size = ImVec2(460 + pf * 7, 520 + pf * 5)
|
||||||
|
elseif name == "resize_tl" then pos = ImVec2(40 + pf * 3, 40 + pf * 2); size = ImVec2(size.x - 3, size.y - 2)
|
||||||
|
elseif name == "move" then pos = ImVec2(pos.x + 5, pos.y + 3)
|
||||||
|
elseif name == "scroll" then scroll += 9
|
||||||
|
elseif name == "hover" then io:AddMousePosEvent(pos.x + 30 + (pf * 17) % 300, pos.y + 40 + (pf * 23) % 400)
|
||||||
|
elseif name == "type" then
|
||||||
|
if pf == 1 then io:AddMousePosEvent(pos.x + 120, pos.y + 37); io:AddMouseButtonEvent(0, true)
|
||||||
|
elseif pf == 2 then io:AddMouseButtonEvent(0, false)
|
||||||
|
else io:AddInputCharacter(string.byte(typing, (pf - 3) % #typing + 1)) end
|
||||||
|
elseif name == "fonts" then fsize = 13 + pf * 0.75
|
||||||
|
elseif name == "color" then
|
||||||
|
local p = ImVec2(700 + pf * 4, 120 + pf * 3)
|
||||||
|
io:AddMousePosEvent(p.x, p.y); io:AddMouseButtonEvent(0, pf < n)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ImGui.NewFrame()
|
||||||
|
ImGui.SetNextWindowPos(pos, G.ImGuiCond.Always); ImGui.SetNextWindowSize(size, G.ImGuiCond.Always)
|
||||||
|
ImGui.Begin("Stress")
|
||||||
|
ImGui.InputText("##in", buf, 128)
|
||||||
|
if name == "scroll" or scroll > 0 then ImGui.SetScrollY(scroll) end
|
||||||
|
for i = 1, 12 do ImGui.Button("Button " .. i); ImGui.SameLine(); slider = ImGui.SliderFloat("##s" .. i, slider, 0, 1) end
|
||||||
|
if ImGui.BeginTable("tbl", 4, bit32.bor(G.ImGuiTableFlags.Borders, G.ImGuiTableFlags.RowBg)) then
|
||||||
|
for r = 1, 30 do ImGui.TableNextRow(); for c = 1, 4 do ImGui.TableNextColumn(); ImGui.Text("r%d c%d", r, c) end end
|
||||||
|
ImGui.EndTable()
|
||||||
|
end
|
||||||
|
ImGui.PushFont(nil, fsize); ImGui.Text("Font size %.2f AaBbCc", fsize); ImGui.PopFont()
|
||||||
|
for i = 1, 30 do ImGui.TextWrapped("Line %d: the quick brown fox jumps over the lazy dog.", i) end
|
||||||
|
ImGui.End()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(660, 60), G.ImGuiCond.Always); ImGui.SetNextWindowSize(ImVec2(500, 620), G.ImGuiCond.Always)
|
||||||
|
ImGui.Begin("Pickers")
|
||||||
|
ImGui.ColorPicker4("bar", col, G.ImGuiColorEditFlags.PickerHueBar)
|
||||||
|
ImGui.End()
|
||||||
|
ImGui.Render()
|
||||||
|
local clk = os.realclock or os.clock
|
||||||
|
local t0 = clk()
|
||||||
|
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||||
|
if act then PT[name] = (PT[name] or 0) + (clk() - t0) * 1000 end
|
||||||
|
if pf == n then -- mid-action (not settled): only exact without a redraw budget
|
||||||
|
print("PIXA", name, frame, composite(game:GetService("Players").LocalPlayer.PlayerGui:FindFirstChild("ImGui")))
|
||||||
|
end
|
||||||
|
if pf == n + STILL then
|
||||||
|
local gui = game:GetService("Players").LocalPlayer.PlayerGui:FindFirstChild("ImGui")
|
||||||
|
print("PIX", name, frame, composite(gui))
|
||||||
|
pi += 1; pf = 0
|
||||||
|
if not PHASES[pi] then
|
||||||
|
for _, q in PHASES do print(string.format("PIXT %-10s %7.2f ms/frame", q[1], PT[q[1]] / q[2])) end
|
||||||
|
print("PIXELS DONE")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
-- Screenshot of a fixed scene (t_libhash's widgets + primitives, optionally the Custom Rendering example):
|
||||||
|
-- composites the ScreenGui like t_composite and prints "ROW y hex". tools/test/shot.py turns it into a PNG.
|
||||||
|
-- RUN_FLAGS: ARG_APP=true also opens ShowExampleAppCustomRendering (needs the demo).
|
||||||
|
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||||
|
local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4
|
||||||
|
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init(); G.ImGui_ImplRoblox.SetRenderRate(0)
|
||||||
|
local io = ImGui.GetIO()
|
||||||
|
local W, H = 1280, 760
|
||||||
|
local frame = 0
|
||||||
|
local st = { b = false, f = 0.25, i = 42, c = ImVec4(0.2, 0.7, 0.4, 1.0), buf = ImGui.NewTextBuffer("hello", 64), r = 0, tab = 0, combo = 1, list = 2 }
|
||||||
|
local plot = {}
|
||||||
|
for i = 1, 40 do plot[i] = math.sin(i * 0.3) end
|
||||||
|
local poly = { ImVec2(0, 0), ImVec2(60, 10), ImVec2(30, 30), ImVec2(70, 60), ImVec2(0, 50) }
|
||||||
|
|
||||||
|
local function composite(gui)
|
||||||
|
local screen = buffer.create(W * H * 3)
|
||||||
|
local function walk(inst, ox, oy, kx0, ky0, kx1, ky1)
|
||||||
|
local kids = inst:GetChildren()
|
||||||
|
table.sort(kids, function(a, b) return (a.ZIndex or 1) < (b.ZIndex or 1) end) -- table.sort isn't stable, but siblings with equal ZIndex never overlap here
|
||||||
|
for _, c in ipairs(kids) do
|
||||||
|
if c.Visible ~= false then
|
||||||
|
local x, y = ox + (c.Position and c.Position.X or 0), oy + (c.Position and c.Position.Y or 0)
|
||||||
|
local cx0, cy0, cx1, cy1 = kx0, ky0, kx1, ky1
|
||||||
|
if c.ClipsDescendants then
|
||||||
|
cx0, cy0 = math.max(cx0, x), math.max(cy0, y)
|
||||||
|
cx1, cy1 = math.min(cx1, x + c.Size.X), math.min(cy1, y + c.Size.Y)
|
||||||
|
end
|
||||||
|
if c.ClassName == "ImageLabel" then
|
||||||
|
local img = c.ImageContent.Object
|
||||||
|
local px, sz = img.Pixels, img.Size.X
|
||||||
|
for ty = 0, sz - 1 do
|
||||||
|
local sy = y + ty
|
||||||
|
if sy >= ky0 and sy < ky1 then
|
||||||
|
for tx = 0, sz - 1 do
|
||||||
|
local sx = x + tx
|
||||||
|
if sx >= kx0 and sx < kx1 then
|
||||||
|
local o = (ty * sz + tx) * 4
|
||||||
|
local a = buffer.readu8(px, o + 3)
|
||||||
|
if a > 0 then
|
||||||
|
local d = (sy * W + sx) * 3
|
||||||
|
local fa = a / 255
|
||||||
|
for ch = 0, 2 do
|
||||||
|
local v = buffer.readu8(px, o + ch) * fa + buffer.readu8(screen, d + ch) * (1 - fa)
|
||||||
|
buffer.writeu8(screen, d + ch, math.floor(v + 0.5))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
walk(c, x, y, cx0, cy0, cx1, cy1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
walk(gui, 0, 0, 0, 0, W, H)
|
||||||
|
return screen
|
||||||
|
end
|
||||||
|
|
||||||
|
game:GetService("RunService").Stepped:Connect(function()
|
||||||
|
frame += 1
|
||||||
|
G.ImGui_ImplRoblox.NewFrame()
|
||||||
|
io.DeltaTime = 1 / 60
|
||||||
|
ImGui.NewFrame()
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(10, 10), G.ImGuiCond.Always); ImGui.SetNextWindowSize(ImVec2(320, 560), G.ImGuiCond.Always)
|
||||||
|
ImGui.Begin("Widgets")
|
||||||
|
ImGui.Text("Text %d", 123); ImGui.TextColored(ImVec4(1, 0.5, 0, 1), "Colored"); ImGui.TextWrapped("Wrapped text that is long enough to wrap around the window edge at least once.")
|
||||||
|
ImGui.Button("Button"); ImGui.SameLine(); ImGui.SmallButton("Small"); ImGui.SameLine(); ImGui.ArrowButton("##a", G.ImGuiDir.Left)
|
||||||
|
st.b = ImGui.Checkbox("Checkbox", st.b)
|
||||||
|
st.r = ImGui.RadioButton("R0", st.r, 0); ImGui.SameLine(); st.r = ImGui.RadioButton("R1", st.r, 1)
|
||||||
|
st.f = ImGui.SliderFloat("Slider", st.f, 0, 1); st.i = ImGui.DragInt("Drag", st.i, 1, 0, 100)
|
||||||
|
ImGui.InputText("Input", st.buf, 64)
|
||||||
|
ImGui.ColorEdit4("Color", st.c)
|
||||||
|
st.combo = ImGui.Combo("Combo", st.combo, { "AAA", "BBB", "CCC" })
|
||||||
|
st.list = ImGui.ListBox("List", st.list, { "one", "two", "three", "four" }, 4, 3)
|
||||||
|
ImGui.ProgressBar(0.4, ImVec2(-1, 0)); ImGui.ProgressBar(-1.0 * frame / 60, ImVec2(-1, 0), "busy")
|
||||||
|
ImGui.PlotLines("Lines", plot, nil, #plot); ImGui.PlotHistogram("Hist", plot, nil, #plot, 0, nil, -1, 1, ImVec2(0, 40))
|
||||||
|
ImGui.Bullet(); ImGui.Text("bullet"); ImGui.Separator(); ImGui.SeparatorText("Sep")
|
||||||
|
if ImGui.TreeNode("Tree") then ImGui.Text("inside"); ImGui.TreePop() end
|
||||||
|
ImGui.SetNextItemOpen(true, G.ImGuiCond.Once)
|
||||||
|
if ImGui.CollapsingHeader("Header") then
|
||||||
|
if ImGui.BeginTabBar("tabs") then
|
||||||
|
if ImGui.BeginTabItem("Tab A") then ImGui.Text("A"); ImGui.EndTabItem() end
|
||||||
|
if ImGui.BeginTabItem("Tab B") then ImGui.Text("B"); ImGui.EndTabItem() end
|
||||||
|
ImGui.EndTabBar()
|
||||||
|
end
|
||||||
|
if ImGui.BeginTable("t", 3, bit32.bor(G.ImGuiTableFlags.Borders, G.ImGuiTableFlags.RowBg, G.ImGuiTableFlags.Resizable)) then
|
||||||
|
ImGui.TableSetupColumn("One"); ImGui.TableSetupColumn("Two"); ImGui.TableSetupColumn("Three"); ImGui.TableHeadersRow()
|
||||||
|
for row = 0, 4 do ImGui.TableNextRow(); for col = 0, 2 do ImGui.TableSetColumnIndex(col); ImGui.Text("r%d c%d", row, col) end end
|
||||||
|
ImGui.EndTable()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ImGui.End()
|
||||||
|
|
||||||
|
ImGui.SetNextWindowPos(ImVec2(340, 10), G.ImGuiCond.Always); ImGui.SetNextWindowSize(ImVec2(300, 560), G.ImGuiCond.Always)
|
||||||
|
ImGui.Begin("Draw")
|
||||||
|
local dl, p = ImGui.GetWindowDrawList(), ImGui.GetCursorScreenPos()
|
||||||
|
local x, y, col = p.x + 4, p.y + 4, 0xFF40C0FF
|
||||||
|
for i, th in { 1.0, 2.5, 6.0 } do
|
||||||
|
local yy = y + (i - 1) * 70
|
||||||
|
dl:AddRect(ImVec2(x, yy), ImVec2(x + 50, yy + 50), col, 10.0, th)
|
||||||
|
dl:AddRectFilled(ImVec2(x + 60, yy), ImVec2(x + 110, yy + 50), col, 10.0, G.ImDrawFlags.RoundTopLeft or G.ImDrawFlags.RoundCornersTopLeft) -- RoundCornersTopLeft: pre-1.93 revs (hashes.sh)
|
||||||
|
dl:AddCircle(ImVec2(x + 145, yy + 25), 25, col, 0, th)
|
||||||
|
dl:AddNgon(ImVec2(x + 200, yy + 25), 25, col, 6, th)
|
||||||
|
dl:AddTriangle(ImVec2(x + 230, yy + 50), ImVec2(x + 255, yy), ImVec2(x + 280, yy + 50), col, th)
|
||||||
|
dl:AddLine(ImVec2(x, yy + 60), ImVec2(x + 280, yy + 55), col, th)
|
||||||
|
end
|
||||||
|
y += 210
|
||||||
|
dl:AddBezierCubic(ImVec2(x, y), ImVec2(x + 60, y - 30), ImVec2(x + 120, y + 60), ImVec2(x + 180, y), col, 3.0)
|
||||||
|
dl:AddBezierQuadratic(ImVec2(x, y + 30), ImVec2(x + 90, y - 10), ImVec2(x + 180, y + 30), col, 2.0)
|
||||||
|
dl:AddEllipse(ImVec2(x + 230, y + 15), ImVec2(40, 15), col, 0.5, 0, 2.0)
|
||||||
|
y += 50
|
||||||
|
local pts = {}
|
||||||
|
for i, v in poly do pts[i] = ImVec2(x + v.x, y + v.y) end
|
||||||
|
dl:AddConcavePolyFilled(pts, #pts, 0xFF8080FF)
|
||||||
|
for i, v in poly do pts[i] = ImVec2(x + 100 + v.x, y + v.y) end
|
||||||
|
dl:AddPolyline(pts, #pts, col, 4.0, G.ImDrawFlags.Closed)
|
||||||
|
for i, v in poly do pts[i] = ImVec2(x + 200 + v.x, y + v.y) end
|
||||||
|
dl:AddPolyline(pts, #pts, col, 1.0, 0)
|
||||||
|
y += 80
|
||||||
|
dl:AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + 100, y + 40), 0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFFFFFF)
|
||||||
|
dl:AddCircleFilled(ImVec2(x + 140, y + 20), 20, col)
|
||||||
|
dl:AddEllipseFilled(ImVec2(x + 210, y + 20), ImVec2(30, 15), col, 0.3)
|
||||||
|
dl:AddText(ImVec2(x, y + 50), 0xFFFFFFFF, "AddText")
|
||||||
|
ImGui.End()
|
||||||
|
|
||||||
|
if ARG_APP then ImGui.SetNextWindowPos(ImVec2(650, 10), G.ImGuiCond.Always); ImGui.SetNextWindowSize(ImVec2(620, 740), G.ImGuiCond.Always); G.ShowExampleAppCustomRendering(true) end
|
||||||
|
ImGui.Render()
|
||||||
|
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||||
|
if frame == ARG_FRAMES - 1 then
|
||||||
|
local screen = composite(game:GetService("Players").LocalPlayer.PlayerGui:FindFirstChild("ImGui"))
|
||||||
|
for y = 0, H - 1 do
|
||||||
|
local t = table.create(W * 3)
|
||||||
|
for i = 0, W * 3 - 1 do t[i + 1] = string.format("%02x", buffer.readu8(screen, y * W * 3 + i)) end
|
||||||
|
print("ROW", y, table.concat(t))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Headless screenshot: tools/test/shot.py out.png [--app] (runs scripts/t_shot.luau, writes a PNG; stdlib only)"""
|
||||||
|
import os, subprocess, sys, struct, zlib
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
env = dict(os.environ, FULL_OUTPUT="1", RUN_FLAGS="ARG_APP=true" if "--app" in sys.argv else "")
|
||||||
|
out = subprocess.run([sys.executable, os.path.join(HERE, "run.py"), "--quiet", "--roblox", "--frames", "30", "--timeout", "600",
|
||||||
|
"--main", os.path.join(HERE, "scripts", "t_shot.luau")], env=env, capture_output=True, text=True).stdout
|
||||||
|
rows = {int(l.split()[1]): bytes.fromhex(l.split()[2]) for l in out.splitlines() if l.startswith("ROW")}
|
||||||
|
assert rows, out[-2000:]
|
||||||
|
w, h = len(rows[0]) // 3, len(rows)
|
||||||
|
raw = b"".join(b"\0" + rows[y] for y in range(h))
|
||||||
|
chunk = lambda t, d: struct.pack(">I", len(d)) + t + d + struct.pack(">I", zlib.crc32(t + d) & 0xffffffff)
|
||||||
|
open(sys.argv[1], "wb").write(b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0)) + chunk(b"IDAT", zlib.compress(raw, 6)) + chunk(b"IEND", b""))
|
||||||
|
print("wrote", sys.argv[1], w, h)
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "4dbug/imgui-luau"
|
name = "4dbug/imgui-luau"
|
||||||
version = "0.1.2"
|
version = "0.3.0"
|
||||||
registry = "https://github.com/UpliftGames/wally-index"
|
registry = "https://github.com/UpliftGames/wally-index"
|
||||||
realm = "shared"
|
realm = "shared"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
Reference in New Issue
Block a user