6 Commits
Author SHA1 Message Date
4DBug ff86ea5aa2 0.2.0
Build & Release / build (push) Waiting to run
2026-10-15 01:06:11 -05:00
4DBug 4ac57cb571 Roblox backend: redraw budget is opt-in (SetRedrawBudget default 0 = every changed tile drawn the same frame) 2026-10-15 00:55:43 -05:00
4DBug 15dc766d82 bundle.py: build-time Luau optimizations (draw-data identical; IMGUI_BUNDLE_NOOPT=1 disables): hot library globals as bundle locals, enum/constant folding (values dumped by the luau CLI; IMGUI_BUNDLE_STRICT=1 in CI), --@hoist pure helpers to inlinable bundle-level local functions, statement IM_ASSERT -> assert; builtin type instead of ImGui._GetTypeFunc() alias (GETGLOBAL 15903 -> 540) 2026-10-15 00:23:20 -05:00
4DBug ed53d0683e Library perf (draw-data identical): layout core (no ImVec2 __index in ItemSize, inlined ID/style/clip stacks, scalar ItemAdd/IsClippedEx/CalcItemSize/GetColorU32), text + widgets (scalar TextEx/ButtonEx/Checkbox/Slider/Drag/Selectable/TreeNode, word-wrap memo), tables (in-place clip rects, no per-row/cell temporaries, @native cell functions, scalar ClipWith), draw list (PrimRectXY, scalar AddRect/AddRectFilled/AddLine/PathRect/arcs) 2026-10-14 23:27:23 -05:00
4DBug b71e29ee35 Roblox backend perf (pixel-identical): redraw budget (SetRedrawBudget, default 1.5 ms; RenderRate default 0), exact in-place draw list compare + whole-pixel move fast path, partial tile redraws, flat rects hashed by in-tile rect, quads hashed by TL/BR, no full redraw on texture updates, @native CopyTexturePixels, reused partial upload buffers; imgui_draw: @native word wrap, ImStrByte/classifier inlined, text size cache covers wrapped text; t_pixels pixel oracle; run.py RUN_FLAGS 2026-10-14 22:26:38 -05:00
4DBug bcf383a079 tools/compare_upstream.py: port coverage + code similarity vs upstream, writes docs/PORT_STATUS.txt 2026-10-14 22:26:38 -05:00
21 changed files with 15113 additions and 9824 deletions
+4 -3
View File
@@ -116,7 +116,7 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
### Getting Started & Integration (Roblox) ### Getting Started & Integration (Roblox)
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/imgui-luau@0.1.4"` 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.2.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 `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. Optionally capped with `SetRenderRate(hz)` / `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).
+2
View File
@@ -30,6 +30,8 @@ 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)
+4 -3
View File
@@ -113,7 +113,7 @@ The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.Sh
### Getting Started & Integration (Roblox) ### Getting Started & Integration (Roblox)
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/imgui-luau@0.1.4"` 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.2.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 `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. Optionally capped with `SetRenderRate(hz)` / `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).
+357 -61
View File
@@ -142,6 +142,7 @@ end
--------------------------------------------------------- ---------------------------------------------------------
-- Copies a region of ImTextureData pixels (1-based byte table) into our RGBA8 buffer -- Copies a region of ImTextureData pixels (1-based byte table) into our RGBA8 buffer
@native
local function CopyTexturePixels(tex, dst, x, y, w, h) local function CopyTexturePixels(tex, dst, x, y, w, h)
local bpp, tex_w = tex.BytesPerPixel, tex.Width local bpp, tex_w = tex.BytesPerPixel, tex.Width
for row = 0, h - 1 do for row = 0, h - 1 do
@@ -208,6 +209,8 @@ end
-- Blending is non-premultiplied "over" with a transparent (alpha 0) background, so the game stays visible behind ImGui. -- Blending is non-premultiplied "over" with a transparent (alpha 0) background, so the game stays visible behind ImGui.
local buf, buf_w -- buffer/width of the tile being drawn local buf, buf_w -- buffer/width of the tile being drawn
local rect_cols = table.create(256, 0) -- scratch: texel column offsets for DrawRect
local sub_bufs, n_sub_bufs = {}, 0 -- scratch: partial tile upload buffers by size
local TILE_SIZE -- Vector2, set below local TILE_SIZE -- Vector2, set below
-- Blends one pixel (r, g, b, a are 0..255). One u32 read/write, one division in the general case. -- Blends one pixel (r, g, b, a are 0..255). One u32 read/write, one division in the general case.
@@ -337,18 +340,30 @@ local function DrawRect(x0, y0, x1, y1, u0, v0, u1, v1, col, t, cx0, cy0, cx1, c
local du, dv = (u1 - u0) / (x1 - x0), (v1 - v0) / (y1 - y0) local du, dv = (u1 - u0) / (x1 - x0), (v1 - v0) / (y1 - y0)
local tw, th, tbuf = t.Width, t.Height, t.Buffer local tw, th, tbuf = t.Width, t.Height, t.Buffer
local r255, g255, b255, a255 = r / 255, g / 255, b / 255, a / 255 local r255, g255, b255, a255 = r / 255, g / 255, b / 255, a / 255
-- The texel column only depends on x: compute it once per column instead of once per pixel
local ncol = px1 - px0
local cols = rect_cols
for c = 1, ncol do
local tx = floor((u0 + (px0 + c - 1 + 0.5 - x0) * du) * tw)
if tx < 0 then tx = 0 elseif tx >= tw then tx = tw - 1 end
cols[c] = tx * 4
end
for y = py0, py1 - 1 do for y = py0, py1 - 1 do
local ty = floor((v0 + (y + 0.5 - y0) * dv) * th) local ty = floor((v0 + (y + 0.5 - y0) * dv) * th)
if ty < 0 then ty = 0 elseif ty >= th then ty = th - 1 end if ty < 0 then ty = 0 elseif ty >= th then ty = th - 1 end
local trow = ty * tw local trow4 = ty * tw * 4
local o = (y * buf_w + px0) * 4 local o = (y * buf_w + px0) * 4
for x = px0, px1 - 1 do for c = 1, ncol do
local tx = floor((u0 + (x + 0.5 - x0) * du) * tw) local to = trow4 + cols[c]
if tx < 0 then tx = 0 elseif tx >= tw then tx = tw - 1 end
local to = (trow + tx) * 4
local ta = readu8(tbuf, to + 3) local ta = readu8(tbuf, to + 3)
if ta > 0 then if ta > 0 then
BlendPixel(o, readu8(tbuf, to) * r255, readu8(tbuf, to + 1) * g255, readu8(tbuf, to + 2) * b255, ta * a255) local sa = ta * a255
local sr, sg, sb = readu8(tbuf, to) * r255, readu8(tbuf, to + 1) * g255, readu8(tbuf, to + 2) * b255
if sa >= 255 then -- (BlendPixel's opaque case, inlined: most glyph texels)
writeu32(buf, o, floor(sr + 0.5) + floor(sg + 0.5) * 256 + floor(sb + 0.5) * 65536 + 4278190080)
else
BlendPixel(o, sr, sg, sb, sa)
end
end end
o = o + 4 o = o + 4
end end
@@ -442,6 +457,7 @@ local P1, P2 = 2147483647, 2147483629 -- two independent 31 bit hashes per tile
local KEY_W = 64 -- tile key = ty * KEY_W + tx (layers up to 16384 px wide) local KEY_W = 64 -- tile key = ty * KEY_W + tx (layers up to 16384 px wide)
local CLIP_PAD = 2048 -- content up to this far off screen is kept, so dragging a window past the edge doesn't redraw it local CLIP_PAD = 2048 -- content up to this far off screen is kept, so dragging a window past the edge doesn't redraw it
local LAYER_KEEP_FRAMES = 120 -- unused layers are destroyed after this many rendered frames local LAYER_KEEP_FRAMES = 120 -- unused layers are destroyed after this many rendered frames
local MAX_DEFER_FRAMES = 8 -- a tile is redrawn at the latest this many frames after it changed, even over the redraw budget
-- Primitive arrays (reused between frames, no per-frame allocation) -- Primitive arrays (reused between frames, no per-frame allocation)
local pr_a, pr_b, pr_c, pr_d, pr_t, pr_g = {}, {}, {}, {}, {}, {} -- vertices (pr_d == false -> triangle), texture, gradient kind local pr_a, pr_b, pr_c, pr_d, pr_t, pr_g = {}, {}, {}, {}, {}, {} -- vertices (pr_d == false -> triangle), texture, gradient kind
@@ -451,6 +467,7 @@ local pr_tex = {} -- texture id
local pr_vb = {} -- vertex buffer (draw list VtxBuffer.Buf) local pr_vb = {} -- vertex buffer (draw list VtxBuffer.Buf)
local pr_k = {} -- layer kind (1..3) local pr_k = {} -- layer kind (1..3)
local plist1, plist2, plist3 = {}, {}, {} -- primitive indices per layer kind local plist1, plist2, plist3 = {}, {}, {} -- primitive indices per layer kind
local cm_free, cm_kind, cm_x0, cm_y0, cm_x1, cm_y1 = {}, {}, {}, {}, {}, {} -- per draw command (collect -> Snapshot)
@native @native
local function HashVertex(h1, h2, vbuf, v, ox, oy) local function HashVertex(h1, h2, vbuf, v, ox, oy)
@@ -540,7 +557,7 @@ local function GetLayer(bd, draw_list, kind)
else else
frame = NewFrame(bd.Gui, false); root = frame frame = NewFrame(bd.Gui, false); root = frame
end end
layer = { Root = root, Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, TX = {}, TY = {}, Touched = {}, NTouched = 0, layer = { Root = root, Frame = frame, Tiles = {}, TH1 = {}, TH2 = {}, TN = {}, TL = {}, TL1 = {}, TL2 = {}, TLF = {}, TX = {}, TY = {}, Touched = {}, NTouched = 0,
Visible = false, X = false, Y = false, Z = -1, CX0 = false, CY0 = false, CX1 = false, CY1 = false } Visible = false, X = false, Y = false, Z = -1, CX0 = false, CY0 = false, CX1 = false, CY1 = false }
set[kind] = layer set[kind] = layer
return layer return layer
@@ -567,7 +584,8 @@ local function GetTile(bd, layer, key, tx, ty)
label.Visible = false label.Visible = false
label.Active = bd.TilesActive label.Active = bd.TilesActive
label.Parent = layer.Frame label.Parent = layer.Frame
tile = { Image = img, Label = label, Buffer = buffer.create(TILE * TILE * 4), H1 = -1, H2 = -1, Visible = false, BX0 = TILE, BY0 = TILE, BX1 = 0, BY1 = 0 } tile = { Image = img, Label = label, Buffer = buffer.create(TILE * TILE * 4), H1 = -1, H2 = -1, Visible = false, BX0 = TILE, BY0 = TILE, BX1 = 0, BY1 = 0,
ON = -1, OH1 = {}, OH2 = {}, OF = {}, OB0 = {}, OB1 = {}, OB2 = {}, OB3 = {} } -- last drawn primitive list (hashes, bounds in tile)
layer.Tiles[key] = tile layer.Tiles[key] = tile
return tile return tile
end end
@@ -587,31 +605,129 @@ local function DestroyTiles(bd)
bd.Layers = {} bd.Layers = {}
end end
-- Exact signature of everything in a draw list that affects its layers (nil = can't skip: user callbacks) -- Snapshot of everything in a draw list that affects its layers, compared exactly with the next frame's list:
local sig_buf = buffer.create(4096) -- 1 = identical (only restack), 2 = everything moved by the same whole pixels (dx, dy) (only move the layers: a window
-- being dragged), 0 = changed (full path). Compared in place (no strings built): early exit at the first difference.
local writef64 = buffer.writef64
local CMD_STRIDE = 70 -- clip (4 f64), ElemCount, IdxOffset, VtxOffset (u32), tex id (f64), kind (u8), free (u8), bounds of its primitives (4 x i32)
@native @native
local function DrawListSignature(draw_list, ox, oy) local function Snapshot(set, draw_list, ox, oy, sw, sh)
local nv = draw_list.VtxBuffer.Size * VTX_STRIDE
local idx, ni = draw_list.IdxBuffer.Data, draw_list.IdxBuffer.Size local idx, ni = draw_list.IdxBuffer.Data, draw_list.IdxBuffer.Size
local ncmd = draw_list.CmdBuffer.Size local ncmd = draw_list.CmdBuffer.Size
local need = ni * 4 + ncmd * 64 + 64 local sv, si, sc = set.SV, set.SI, set.SC
if buffer.len(sig_buf) < need then sig_buf = buffer.create(need * 2) end if not sv or buffer.len(sv) < nv then sv = buffer.create(max(nv, 64) * 2); set.SV = sv end
local b, o = sig_buf, 0 if not si or buffer.len(si) < ni * 4 then si = buffer.create(max(ni, 16) * 8); set.SI = si end
for i = 1, ni do writeu32(b, o, idx[i]); o += 4 end if not sc or buffer.len(sc) < ncmd * CMD_STRIDE then sc = buffer.create(max(ncmd, 4) * CMD_STRIDE * 2); set.SC = sc end
buffer.copy(sv, 0, draw_list.VtxBuffer.Buf, 0, nv)
for i = 1, ni do writeu32(si, (i - 1) * 4, idx[i]) end
local o, ci = 0, 0
for _, pcmd in draw_list.CmdBuffer:iter() do for _, pcmd in draw_list.CmdBuffer:iter() do
if pcmd.UserCallback ~= nil then return nil end if pcmd.UserCallback ~= nil then set.SnapOK = false; return end
local c = pcmd.ClipRect local c = pcmd.ClipRect
buffer.writef64(b, o, c.x); buffer.writef64(b, o + 8, c.y); buffer.writef64(b, o + 16, c.z); buffer.writef64(b, o + 24, c.w) writef64(sc, o, c.x); writef64(sc, o + 8, c.y); writef64(sc, o + 16, c.z); writef64(sc, o + 24, c.w)
writeu32(b, o + 32, pcmd.ElemCount); writeu32(b, o + 36, pcmd.IdxOffset); writeu32(b, o + 40, pcmd.VtxOffset) writeu32(sc, o + 32, pcmd.ElemCount); writeu32(sc, o + 36, pcmd.IdxOffset); writeu32(sc, o + 40, pcmd.VtxOffset)
buffer.writef64(b, o + 44, pcmd:GetTexID()) writef64(sc, o + 44, pcmd:GetTexID())
o += 52 ci += 1
buffer.writeu8(sc, o + 52, cm_kind[ci] or 0); buffer.writeu8(sc, o + 53, cm_free[ci] and 1 or 0)
if cm_free[ci] then
buffer.writei32(sc, o + 54, cm_x0[ci]); buffer.writei32(sc, o + 58, cm_y0[ci]); buffer.writei32(sc, o + 62, cm_x1[ci]); buffer.writei32(sc, o + 66, cm_y1[ci])
end
o += CMD_STRIDE
end
set.SVN, set.SIN, set.SCN = nv, ni, ncmd
set.SOX, set.SOY, set.SSW, set.SSH = ox, oy, sw, sh
set.SCC = draw_list._ContentClipX0 ~= nil
set.SCX0, set.SCY0, set.SCX1, set.SCY1 = draw_list._ContentClipX0 or 0, draw_list._ContentClipY0 or 0, draw_list._ContentClipX1 or 0, draw_list._ContentClipY1 or 0
set.SSX, set.SSY = draw_list._ScrollOriginX or 0, draw_list._ScrollOriginY or 0
set.SnapOK = true
end
-- a + d == b exactly, with no rounding in a + d (so every later "x - origin" gives the same result as before the move)
@native
local function ShiftedExact(a, d, b)
local s = a + d
if s ~= b then return false end
local bb = s - a
return (a - (s - bb)) + (d - bb) == 0
end
@native
local function CompareDrawList(set, draw_list, ox, oy, sw, sh)
if not set or not set.SnapOK then return 0 end
local nv = draw_list.VtxBuffer.Size * VTX_STRIDE
local ni, ncmd = draw_list.IdxBuffer.Size, draw_list.CmdBuffer.Size
if nv ~= set.SVN or ni ~= set.SIN or ncmd ~= set.SCN or ox ~= set.SOX or oy ~= set.SOY or sw ~= set.SSW or sh ~= set.SSH then return 0 end
if (draw_list._ContentClipX0 ~= nil) ~= set.SCC then return 0 end
local vb, sv = draw_list.VtxBuffer.Buf, set.SV
if nv == 0 then return 1 end
-- translation from the first vertex: whole pixels only, and only with DisplayPos at 0 (so it stays exact)
local dx, dy = readf64(vb, 0) - readf64(sv, 0), readf64(vb, 8) - readf64(sv, 8)
if dx ~= dx or dy ~= dy then return 0 end
if dx ~= 0 or dy ~= 0 then
if dx ~= floor(dx) or dy ~= floor(dy) or ox ~= 0 or oy ~= 0 or not set.SLimFree then return 0 end
-- everything must stay as far from the clip limits as before (nothing newly clamped)
if set.SEX0 + dx < -CLIP_PAD or set.SEY0 + dy < -CLIP_PAD or set.SEX1 + dx > sw + CLIP_PAD or set.SEY1 + dy > sh + CLIP_PAD then return 0 end
end
local idx, si = draw_list.IdxBuffer.Data, set.SI
for i = 1, ni do if idx[i] ~= readu32(si, (i - 1) * 4) then return 0 end end
local sc, o = set.SC, 0
local ccx0, ccy0, ccx1, ccy1 = draw_list._ContentClipX0, draw_list._ContentClipY0, draw_list._ContentClipX1, draw_list._ContentClipY1
local seen_content = false
for _, pcmd in draw_list.CmdBuffer:iter() do
if pcmd.UserCallback ~= nil then return 0 end
if pcmd.ElemCount ~= readu32(sc, o + 32) or pcmd.IdxOffset ~= readu32(sc, o + 36) or pcmd.VtxOffset ~= readu32(sc, o + 40) or pcmd:GetTexID() ~= readf64(sc, o + 44) then return 0 end
local c = pcmd.ClipRect
if dx == 0 and dy == 0 then
if c.x ~= readf64(sc, o) or c.y ~= readf64(sc, o + 8) or c.z ~= readf64(sc, o + 16) or c.w ~= readf64(sc, o + 24) then return 0 end
elseif not (ShiftedExact(readf64(sc, o), dx, c.x) and ShiftedExact(readf64(sc, o + 8), dy, c.y)
and ShiftedExact(readf64(sc, o + 16), dx, c.z) and ShiftedExact(readf64(sc, o + 24), dy, c.w)) then
-- An unchanged clip rect (e.g. the viewport) is fine if it clipped none of this command's primitives before
-- the move and still clips none after it
if c.x ~= readf64(sc, o) or c.y ~= readf64(sc, o + 8) or c.z ~= readf64(sc, o + 16) or c.w ~= readf64(sc, o + 24)
or buffer.readu8(sc, o + 53) == 0 then return 0 end
if buffer.readi32(sc, o + 54) + dx < floor(c.x) or buffer.readi32(sc, o + 58) + dy < floor(c.y)
or buffer.readi32(sc, o + 62) + dx > floor(c.z) or buffer.readi32(sc, o + 66) + dy > floor(c.w) then return 0 end
end
-- same layer kind as before (same rule as the collect pass)
if pcmd.ElemCount > 0 then
local kind = 1
if ccx0 and c.x >= ccx0 and c.y >= ccy0 and c.z <= ccx1 and c.w <= ccy1 then kind = 2; seen_content = true
elseif seen_content then kind = 3 end
if kind ~= buffer.readu8(sc, o + 52) then return 0 end
end
o += CMD_STRIDE
end
if dx == 0 and dy == 0 then
if set.SCC and (draw_list._ContentClipX0 ~= set.SCX0 or draw_list._ContentClipY0 ~= set.SCY0 or draw_list._ContentClipX1 ~= set.SCX1
or draw_list._ContentClipY1 ~= set.SCY1 or draw_list._ScrollOriginX ~= set.SSX or draw_list._ScrollOriginY ~= set.SSY) then return 0 end
for v = 0, nv - VTX_STRIDE, VTX_STRIDE do
if readf64(vb, v) ~= readf64(sv, v) or readf64(vb, v + 8) ~= readf64(sv, v + 8) or readf64(vb, v + 16) ~= readf64(sv, v + 16)
or readf64(vb, v + 24) ~= readf64(sv, v + 24) or readu32(vb, v + 32) ~= readu32(sv, v + 32) then return 0 end
end
return 1
end
if set.SCC and not (ShiftedExact(set.SCX0, dx, draw_list._ContentClipX0) and ShiftedExact(set.SCY0, dy, draw_list._ContentClipY0)
and ShiftedExact(set.SCX1, dx, draw_list._ContentClipX1) and ShiftedExact(set.SCY1, dy, draw_list._ContentClipY1)
and ShiftedExact(set.SSX, dx, draw_list._ScrollOriginX) and ShiftedExact(set.SSY, dy, draw_list._ScrollOriginY)) then return 0 end
for v = 0, nv - VTX_STRIDE, VTX_STRIDE do
if not ShiftedExact(readf64(sv, v), dx, readf64(vb, v)) or not ShiftedExact(readf64(sv, v + 8), dy, readf64(vb, v + 8))
or readf64(vb, v + 16) ~= readf64(sv, v + 16) or readf64(vb, v + 24) ~= readf64(sv, v + 24) or readu32(vb, v + 32) ~= readu32(sv, v + 32) then return 0 end
end
return 2, dx, dy
end
-- A window moved by whole pixels: its tiles are unchanged, only the layer frames move
local function ShiftLayer(layer, dx, dy, scale)
layer.X += dx; layer.Y += dy
local root = layer.Root
if root ~= layer.Frame then
layer.CX0 += dx; layer.CY0 += dy; layer.CX1 += dx; layer.CY1 += dy
root.Position = UDim2.fromOffset(layer.CX0 / scale, layer.CY0 / scale)
else
root.Position = UDim2.fromOffset(layer.X / scale, layer.Y / scale)
end end
buffer.writef64(b, o, ox); buffer.writef64(b, o + 8, oy)
buffer.writef64(b, o + 16, draw_list._ContentClipX0 or -1); buffer.writef64(b, o + 24, draw_list._ContentClipY0 or -1)
buffer.writef64(b, o + 32, draw_list._ContentClipX1 or -1); buffer.writef64(b, o + 40, draw_list._ContentClipY1 or -1)
buffer.writef64(b, o + 48, draw_list._ContentClipX0 and draw_list._ScrollOriginX or -1)
buffer.writef64(b, o + 56, draw_list._ContentClipX0 and draw_list._ScrollOriginY or -1)
o += 64
return readstring(draw_list.VtxBuffer.Buf, 0, draw_list.VtxBuffer.Size * VTX_STRIDE) .. readstring(b, 0, o)
end end
-- Overlays: user Instances (e.g. a Frame holding a ViewportFrame) placed inside a window, registered every frame with -- Overlays: user Instances (e.g. a Frame holding a ViewportFrame) placed inside a window, registered every frame with
@@ -690,9 +806,11 @@ local function PE() if profiling then profileend() end end
@native @native
local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_x0, clip_y0, clip_x1, clip_y1) local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_x0, clip_y0, clip_x1, clip_y1)
local scale = bd.Scale local scale = bd.Scale
local deferred = false
-- Pass 2: hash primitives into layer-relative tiles -- Pass 2: hash primitives into layer-relative tiles
PB("ImGui tile hash") PB("ImGui tile hash")
local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched local th1, th2, tn, tl, touched = layer.TH1, layer.TH2, layer.TN, layer.TL, layer.Touched
local tl1, tl2, tlf = layer.TL1, layer.TL2, layer.TLF -- per-tile list of the primitives' hashes (for partial redraws)
for k = 1, layer.NTouched do tn[touched[k]] = 0 end for k = 1, layer.NTouched do tn[touched[k]] = 0 end
local nt = 0 local nt = 0
for pi = 1, np do for pi = 1, np do
@@ -701,23 +819,55 @@ local function DrawLayer(bd, layer, plist, np, lx0, ly0, ox, oy, force, z, clip_
-- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative) -- The clip rect only affects pixels inside the primitive's bounds, so hash the clipped bounds (relative)
-- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved. -- instead of the clip itself: a window clipped by the (fixed) viewport rect stays hash-stable when moved.
local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0 local cx0, cy0, cx1, cy1 = pr_bx0[p] - lx0, pr_by0[p] - ly0, pr_bx1[p] - lx0, pr_by1[p] - ly0
local h1 = ((cx0 * 8191 + cy0) * 8191 + cx1) % P1
local h2 = ((cy1 * 8191 + cx1) * 8191 + cy0 + tex_id) % P2
local vax, vay = lx0 + ox, ly0 + oy
local vbuf = pr_vb[p] local vbuf = pr_vb[p]
h1, h2 = HashVertex(h1, h2, vbuf, pr_a[p], vax, vay) local va, vd = pr_a[p], pr_d[p]
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
@@ -878,20 +1110,27 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
local n = 0 local n = 0
local z = 0 local z = 0
local any_deferred = false
for _, draw_list in draw_data.CmdLists:iter() do for _, draw_list in draw_data.CmdLists:iter() do
-- Unchanged draw list (same vertex bytes, indices, commands, origin and scroll): its layers are already -- Unchanged draw list (same vertices, indices, commands, origin and scroll): its layers are already correct,
-- correct, only restack them. Static windows then cost one byte compare instead of collect + hash. -- only restack them. Moved by whole pixels (a dragged window): only move its layers. Either way no collect + hash.
PB("ImGui unchanged check") PB("ImGui unchanged check")
local sig = DrawListSignature(draw_list, ox, oy)
local set = bd.Layers[draw_list] local set = bd.Layers[draw_list]
if sig and set and not force and set.Sig == sig then local same, sdx, sdy = 0, 0, 0
if not force then same, sdx, sdy = CompareDrawList(set, draw_list, ox, oy, sw, sh) end
if same ~= 0 then
for kind = 1, 3 do for kind = 1, 3 do
local layer = set[kind] local layer = set[kind]
if layer and layer.Visible then if layer and layer.Visible then
if same == 2 then ShiftLayer(layer, sdx, sdy, bd.Scale) end
z += 1 z += 1
if layer.Z ~= z then layer.Z = z; layer.Root.ZIndex = z end if layer.Z ~= z then layer.Z = z; layer.Root.ZIndex = z end
end end
end end
if same == 2 then
Snapshot(set, draw_list, ox, oy, sw, sh)
set.SEX0 += sdx; set.SEY0 += sdy; set.SEX1 += sdx; set.SEY1 += sdy
end
set.LastUsed = frame_no set.LastUsed = frame_no
z = PlaceOverlays(bd, draw_list, z, ox, oy) z = PlaceOverlays(bd, draw_list, z, ox, oy)
PE() PE()
@@ -909,13 +1148,30 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
content_x1, content_y1 = min(floor(ccx1 - ox), lim_x1), min(floor(ccy1 - oy), lim_y1) content_x1, content_y1 = min(floor(ccx1 - ox), lim_x1), min(floor(ccy1 - oy), lim_y1)
end end
local seen_content = false local seen_content = false
-- extents of everything clamped by the clip limits below (lim_free: nothing was), for the move fast path
local ex0, ey0, ex1, ey1 = math.huge, math.huge, -math.huge, -math.huge
local lim_free = true
if ccx0 then
local a, b, c, d = floor(ccx0 - ox), floor(ccy0 - oy), floor(ccx1 - ox), floor(ccy1 - oy)
if a < lim_x0 or b < lim_y0 or c > lim_x1 or d > lim_y1 then lim_free = false end
ex0, ey0, ex1, ey1 = min(ex0, a), min(ey0, b), max(ex1, c), max(ey1, d)
end
local ci = 0
for _, pcmd in draw_list.CmdBuffer:iter() do for _, pcmd in draw_list.CmdBuffer:iter() do
ci += 1
cm_free[ci], cm_kind[ci] = false, 0
if pcmd.UserCallback ~= nil then if pcmd.UserCallback ~= nil then
pcmd.UserCallback(draw_list, pcmd) pcmd.UserCallback(draw_list, pcmd)
elseif pcmd.ElemCount > 0 then elseif pcmd.ElemCount > 0 then
local clip = pcmd.ClipRect local clip = pcmd.ClipRect
local cx0, cy0 = max(floor(clip.x - ox), lim_x0), max(floor(clip.y - oy), lim_y0) local rcx0, rcy0, rcx1, rcy1 = floor(clip.x - ox), floor(clip.y - oy), floor(clip.z - ox), floor(clip.w - oy)
local cx1, cy1 = min(floor(clip.z - ox), lim_x1), min(floor(clip.w - oy), lim_y1) if rcx0 < lim_x0 or rcy0 < lim_y0 or rcx1 > lim_x1 or rcy1 > lim_y1 then lim_free = false end
if rcx0 < ex0 then ex0 = rcx0 end
if rcy0 < ey0 then ey0 = rcy0 end
if rcx1 > ex1 then ex1 = rcx1 end
if rcy1 > ey1 then ey1 = rcy1 end
local cx0, cy0 = max(rcx0, lim_x0), max(rcy0, lim_y0)
local cx1, cy1 = min(rcx1, lim_x1), min(rcy1, lim_y1)
local kind = 1 local kind = 1
if ccx0 and clip.x >= ccx0 and clip.y >= ccy0 and clip.z <= ccx1 and clip.w <= ccy1 then if ccx0 and clip.x >= ccx0 and clip.y >= ccy0 and clip.z <= ccx1 and clip.w <= ccy1 then
kind = 2; seen_content = true kind = 2; seen_content = true
@@ -926,6 +1182,8 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
elseif seen_content then elseif seen_content then
kind = 3 kind = 3
end end
cm_kind[ci] = kind
local cfree, cex0, cey0, cex1, cey1 = cx1 > cx0 and cy1 > cy0, math.huge, math.huge, -math.huge, -math.huge
if cx1 > cx0 and cy1 > cy0 then if cx1 > cx0 and cy1 > cy0 then
local tex_id = pcmd:GetTexID() local tex_id = pcmd:GetTexID()
local t = bd.Textures[tex_id] local t = bd.Textures[tex_id]
@@ -971,6 +1229,16 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
bx1, by1 = ceil(max(pax, pbx, pcx) - ox) + 1, ceil(max(pay, pby, pcy) - oy) + 1 bx1, by1 = ceil(max(pax, pbx, pcx) - ox) + 1, ceil(max(pay, pby, pcy) - oy) + 1
i = i + 3 i = i + 3
end end
if bx0 < cx0 or by0 < cy0 or bx1 > cx1 or by1 > cy1 then cfree = false end
if bx0 < cex0 then cex0 = bx0 end
if by0 < cey0 then cey0 = by0 end
if bx1 > cex1 then cex1 = bx1 end
if by1 > cey1 then cey1 = by1 end
if bx0 < ex0 then ex0 = bx0 end
if by0 < ey0 then ey0 = by0 end
if bx1 > ex1 then ex1 = bx1 end
if by1 > ey1 then ey1 = by1 end
if bx0 < lim_x0 or by0 < lim_y0 or bx1 > lim_x1 or by1 > lim_y1 then lim_free = false end
if bx0 < cx0 then bx0 = cx0 end if bx0 < cx0 then bx0 = cx0 end
if by0 < cy0 then by0 = cy0 end if by0 < cy0 then by0 = cy0 end
if bx1 > cx1 then bx1 = cx1 end if bx1 > cx1 then bx1 = cx1 end
@@ -984,12 +1252,16 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
end end
end end
end end
if cex0 > cex1 then cex0, cey0, cex1, cey1 = 0, 0, 0, 0 end -- (no primitives)
cm_free[ci] = cfree
cm_x0[ci], cm_y0[ci], cm_x1[ci], cm_y1[ci] = cex0, cey0, cex1, cey1
end end
end end
PE() PE()
-- Split into layer kinds and place each one -- Split into layer kinds and place each one
set = bd.Layers[draw_list] set = bd.Layers[draw_list]
local list_deferred = false
local nk1, nk2, nk3 = 0, 0, 0 local nk1, nk2, nk3 = 0, 0, 0
local o1x, o1y, o3x, o3y = math.huge, math.huge, math.huge, math.huge local o1x, o1y, o3x, o3y = math.huge, math.huge, math.huge, math.huge
for p = first, n do for p = first, n do
@@ -1008,19 +1280,28 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
end end
if nk1 > 0 then if nk1 > 0 then
z += 1 z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 1), plist1, nk1, o1x, o1y, ox, oy, force, z) if DrawLayer(bd, GetLayer(bd, draw_list, 1), plist1, nk1, o1x, o1y, ox, oy, force, z) then list_deferred = true end
elseif set and set[1] then HideLayer(set[1]) end elseif set and set[1] then HideLayer(set[1]) end
if nk2 > 0 then if nk2 > 0 then
z += 1 z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 2), plist2, nk2, floor(draw_list._ScrollOriginX - ox), floor(draw_list._ScrollOriginY - oy), ox, oy, force, z, if DrawLayer(bd, GetLayer(bd, draw_list, 2), plist2, nk2, floor(draw_list._ScrollOriginX - ox), floor(draw_list._ScrollOriginY - oy), ox, oy, force, z,
content_x0, content_y0, content_x1, content_y1) content_x0, content_y0, content_x1, content_y1) then list_deferred = true end
elseif set and set[2] then HideLayer(set[2]) end elseif set and set[2] then HideLayer(set[2]) end
if nk3 > 0 then if nk3 > 0 then
z += 1 z += 1
DrawLayer(bd, GetLayer(bd, draw_list, 3), plist3, nk3, o3x, o3y, ox, oy, force, z) if DrawLayer(bd, GetLayer(bd, draw_list, 3), plist3, nk3, o3x, o3y, ox, oy, force, z) then list_deferred = true end
elseif set and set[3] then HideLayer(set[3]) end elseif set and set[3] then HideLayer(set[3]) end
set = bd.Layers[draw_list] set = bd.Layers[draw_list]
if set then set.LastUsed = frame_no; set.Sig = sig end -- (deferred tiles: not "unchanged" next frame even if the draw list is, so they get redrawn)
if set then
set.LastUsed = frame_no
if list_deferred then set.SnapOK = false else
Snapshot(set, draw_list, ox, oy, sw, sh)
set.SLimFree = lim_free
set.SEX0, set.SEY0, set.SEX1, set.SEY1 = ex0, ey0, ex1, ey1
end
end
if list_deferred then any_deferred = true end
z = PlaceOverlays(bd, draw_list, z, ox, oy) z = PlaceOverlays(bd, draw_list, z, ox, oy)
end end
@@ -1033,7 +1314,7 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
-- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips) -- Hide layers not drawn this frame; free long unused ones (closed windows, old tooltips)
for draw_list, set in pairs(bd.Layers) do for draw_list, set in pairs(bd.Layers) do
if set.LastUsed ~= frame_no then if set.LastUsed ~= frame_no then
set.Sig = nil -- hidden now: must go through the full path when shown again set.SnapOK = false -- hidden now: must go through the full path when shown again
for kind = 1, 3 do if set[kind] then HideLayer(set[kind]) end end for kind = 1, 3 do if set[kind] then HideLayer(set[kind]) end end
if frame_no - set.LastUsed > LAYER_KEEP_FRAMES then if frame_no - set.LastUsed > LAYER_KEEP_FRAMES then
DestroyLayerSet(set) DestroyLayerSet(set)
@@ -1045,6 +1326,8 @@ function ImGui_ImplRoblox_RenderDrawData(draw_data)
-- drop references so old vertex tables can be collected -- drop references so old vertex tables can be collected
for k = n + 1, bd.LastPrimCount do pr_t[k], pr_vb[k] = nil, nil end for k = n + 1, bd.LastPrimCount do pr_t[k], pr_vb[k] = nil, nil end
bd.LastPrimCount = n bd.LastPrimCount = n
-- Tiles left for later: if the app doesn't submit a new frame, Heartbeat finishes them from this draw data
if any_deferred then bd.Stale = draw_data; bd.StaleHB = bd.HB end
PE() PE()
end end
@@ -1070,7 +1353,7 @@ 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 = 0, BusyRenderRate = 0, RedrawBudget = 0, HB = 0, NextRender = 0, LastPrimCount = 0, ForceRedraw = true,
} }
io.BackendPlatformUserData = bd io.BackendPlatformUserData = bd
@@ -1137,7 +1420,10 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
if d and os.clock() + 0.002 >= bd.NextRender then if d and os.clock() + 0.002 >= bd.NextRender then
bd.Pending = nil bd.Pending = nil
ImGui_ImplRoblox_RenderDrawData(d) ImGui_ImplRoblox_RenderDrawData(d)
elseif bd.Stale and bd.HB > bd.StaleHB then -- deferred tiles of a frame not followed by a new one
ImGui_ImplRoblox_RenderDrawData(bd.Stale)
end end
bd.HB += 1
end) end)
connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end) connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end)
@@ -1200,6 +1486,7 @@ function ImGui_ImplRoblox_NewFrame()
local io = ImGui.GetIO() local io = ImGui.GetIO()
local bd = ImGui_ImplRoblox_GetBackendData() local bd = ImGui_ImplRoblox_GetBackendData()
if bd.Pending then bd.Pending = nil; table.clear(bd.Overlays) end -- skipped frame superseded (its draw lists get reset) if bd.Pending then bd.Pending = nil; table.clear(bd.Overlays) end -- skipped frame superseded (its draw lists get reset)
bd.Stale = nil
local screen = bd.Gui.AbsoluteSize local screen = bd.Gui.AbsoluteSize
local w, h = floor(screen.X * bd.Scale), floor(screen.Y * bd.Scale) local w, h = floor(screen.X * bd.Scale), floor(screen.Y * bd.Scale)
@@ -1306,7 +1593,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 0 = every frame, limited by the redraw budget instead). imgui logic still runs every frame.
function ImGui_ImplRoblox_SetRenderRate(hz) function ImGui_ImplRoblox_SetRenderRate(hz)
ImGui_ImplRoblox_GetBackendData().RenderRate = hz or 0 ImGui_ImplRoblox_GetBackendData().RenderRate = hz or 0
end end
@@ -1336,6 +1631,7 @@ ImGui_ImplRoblox = {
AddFile = ImGui_ImplRoblox_AddFile, AddFile = ImGui_ImplRoblox_AddFile,
SetRenderScale = ImGui_ImplRoblox_SetRenderScale, SetRenderScale = ImGui_ImplRoblox_SetRenderScale,
SetRenderRate = ImGui_ImplRoblox_SetRenderRate, SetRenderRate = ImGui_ImplRoblox_SetRenderRate,
SetRedrawBudget = ImGui_ImplRoblox_SetRedrawBudget,
SetOverlay = ImGui_ImplRoblox_SetOverlay, SetOverlay = ImGui_ImplRoblox_SetOverlay,
SetTextProxy = ImGui_ImplRoblox_SetTextProxy, SetTextProxy = ImGui_ImplRoblox_SetTextProxy,
CreateTexture = ImGui_ImplRoblox_CreateTexture, CreateTexture = ImGui_ImplRoblox_CreateTexture,
+1 -1
View File
@@ -1 +1 @@
[[12, "imgui.lua"], [21288, "imgui_demo.lua"], [29706, "imgui_draw.lua"], [35476, "imgui_h.lua"], [38142, "imgui_impl_roblox.lua"], [39494, "imgui_impl_roblox_fonts.lua"], [45897, "imgui_internal.lua"], [49343, "imgui_tables.lua"], [54338, "imgui_widgets.lua"], [64948, "imstb_rectpack.lua"], [65612, "imstb_textedit.lua"], [67154, "imstb_truetype.lua"], [69942, "imstd_minstdio.lua"]] [[46, "imgui.lua"], [21389, "imgui_demo.lua"], [29807, "imgui_draw.lua"], [35745, "imgui_h.lua"], [38412, "imgui_impl_roblox.lua"], [40060, "imgui_impl_roblox_fonts.lua"], [46463, "imgui_internal.lua"], [49932, "imgui_tables.lua"], [54991, "imgui_widgets.lua"], [65631, "imstb_rectpack.lua"], [66295, "imstb_textedit.lua"], [67837, "imstb_truetype.lua"], [70625, "imstd_minstdio.lua"]]
+5381 -4695
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
[[149, "imgui.lua"], [21425, "imgui_demo.lua"], [29843, "imgui_draw.lua"], [35613, "imgui_h.lua"], [38279, "imgui_impl_love.lua"], [38819, "imgui_internal.lua"], [42265, "imgui_tables.lua"], [47260, "imgui_widgets.lua"], [57870, "imstb_rectpack.lua"], [58534, "imstb_textedit.lua"], [60076, "imstb_truetype.lua"], [62864, "imstd_minstdio.lua"]] [[183, "imgui.lua"], [21526, "imgui_demo.lua"], [29944, "imgui_draw.lua"], [35882, "imgui_h.lua"], [38549, "imgui_impl_love.lua"], [39089, "imgui_internal.lua"], [42558, "imgui_tables.lua"], [47617, "imgui_widgets.lua"], [58257, "imstb_rectpack.lua"], [58921, "imstb_textedit.lua"], [60463, "imstb_truetype.lua"], [63251, "imstd_minstdio.lua"]]
+5011 -4622
View File
File diff suppressed because it is too large Load Diff
+2661
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -17,7 +17,7 @@ setfenv(1, setmetatable({}, {
local MainScale = 1.0 local MainScale = 1.0
local RenderScale = 1.0 local RenderScale = 1.0
local RenderRate = 60 local RenderRate = 0 -- 0 = every frame (tile redraws are 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" }
+139 -72
View File
@@ -6373,18 +6373,26 @@ function ImGui.ItemAdd(bb, id, nav_bb_arg, extra_flags)
local g = GImGui local g = GImGui
local window = g.CurrentWindow local window = g.CurrentWindow
local last_item_data, next_item_data = g.LastItemData, g.NextItemData -- [Luau] cached field reads
-- Set item data -- Set item data
-- (DisplayRect is left untouched, made valid when ImGuiItemStatusFlags.HasDisplayRect is set) -- (DisplayRect is left untouched, made valid when ImGuiItemStatusFlags.HasDisplayRect is set)
g.LastItemData.ID = id last_item_data.ID = id
ImRect_Copy(g.LastItemData.Rect, bb) -- [Luau] inline ImRect_Copy(g.LastItemData.Rect, bb); ImRect_Copy(g.LastItemData.NavRect, nav_bb_arg or bb)
ImRect_Copy(g.LastItemData.NavRect, nav_bb_arg and nav_bb_arg or bb) local bb_min, bb_max = bb.Min, bb.Max
g.LastItemData.ItemFlags = bit32.bor(g.CurrentItemFlags, g.NextItemData.ItemFlagsSet, extra_flags) local r = last_item_data.Rect
g.LastItemData.StatusFlags = ImGuiItemStatusFlags.None r.Min.x = bb_min.x; r.Min.y = bb_min.y; r.Max.x = bb_max.x; r.Max.y = bb_max.y
local nav_bb = nav_bb_arg and nav_bb_arg or bb
r = last_item_data.NavRect
r.Min.x = nav_bb.Min.x; r.Min.y = nav_bb.Min.y; r.Max.x = nav_bb.Max.x; r.Max.y = nav_bb.Max.y
last_item_data.ItemFlags = bit32.bor(g.CurrentItemFlags, next_item_data.ItemFlagsSet, extra_flags)
last_item_data.StatusFlags = ImGuiItemStatusFlags.None
-- Note: we don't copy 'g.NextItemData.SelectionUserData' to an hypothetical g.LastItemData.SelectionUserData: since the former is not cleared. -- Note: we don't copy 'g.NextItemData.SelectionUserData' to an hypothetical g.LastItemData.SelectionUserData: since the former is not cleared.
if id ~= 0 then if id ~= 0 then
ImGui.KeepAliveID(id) -- [Luau] inline ImGui.KeepAliveID(id)
if g.ActiveId == id then g.ActiveIdIsAlive = id end
if g.DeactivatedItemData.ID == id then g.DeactivatedItemData.IsAlive = true end
-- Directional navigation processing -- Directional navigation processing
-- Runs prior to clipping early-out -- Runs prior to clipping early-out
@@ -6396,7 +6404,7 @@ function ImGui.ItemAdd(bb, id, nav_bb_arg, extra_flags)
-- to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick). -- to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick).
-- We intentionally don't check if g.NavWindow ~= nil because g.NavAnyRequest should only be set when it is non nil. -- We intentionally don't check if g.NavWindow ~= nil because g.NavAnyRequest should only be set when it is non nil.
-- If we crash on a nil g.NavWindow we need to fix the bug elsewhere. -- If we crash on a nil g.NavWindow we need to fix the bug elsewhere.
if bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.NoNav) == 0 then if bit32.band(last_item_data.ItemFlags, ImGuiItemFlags.NoNav) == 0 then
-- FIXME-NAV: investigate changing the window tests into a simple 'if (g.NavFocusScopeId == g.CurrentFocusScopeId)' test. -- FIXME-NAV: investigate changing the window tests into a simple 'if (g.NavFocusScopeId == g.CurrentFocusScopeId)' test.
window.DC.NavLayersActiveMaskNext = bit32.bor(window.DC.NavLayersActiveMaskNext, bit32.lshift(1, window.DC.NavLayerCurrent)) window.DC.NavLayersActiveMaskNext = bit32.bor(window.DC.NavLayersActiveMaskNext, bit32.lshift(1, window.DC.NavLayerCurrent))
@@ -6409,14 +6417,14 @@ function ImGui.ItemAdd(bb, id, nav_bb_arg, extra_flags)
end end
end end
if bit32.band(g.NextItemData.HasFlags, ImGuiNextItemDataFlags.HasShortcut) ~= 0 then if bit32.band(next_item_data.HasFlags, ImGuiNextItemDataFlags.HasShortcut) ~= 0 then
ImGui.ItemHandleShortcut(id) ImGui.ItemHandleShortcut(id)
end end
end end
-- Lightweight clear of SetNextItemXXX data. -- Lightweight clear of SetNextItemXXX data.
g.NextItemData.HasFlags = ImGuiNextItemDataFlags.None next_item_data.HasFlags = ImGuiNextItemDataFlags.None
g.NextItemData.ItemFlagsSet = ImGuiItemFlags.None next_item_data.ItemFlagsSet = ImGuiItemFlags.None
-- Clipping test -- Clipping test
-- (this is an inline copy of IsClippedEx() so we can reuse the is_rect_visible value, otherwise we'd do 'if (IsClippedEx(bb, id)) return false') -- (this is an inline copy of IsClippedEx() so we can reuse the is_rect_visible value, otherwise we'd do 'if (IsClippedEx(bb, id)) return false')
@@ -6435,11 +6443,11 @@ function ImGui.ItemAdd(bb, id, nav_bb_arg, extra_flags)
-- We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) -- We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them)
if is_rect_visible then if is_rect_visible then
g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.Visible) last_item_data.StatusFlags = bit32.bor(last_item_data.StatusFlags, ImGuiItemStatusFlags.Visible)
end end
if ImGui.IsMouseHoveringRect(bb.Min, bb.Max) then if ImGui.IsMouseHoveringRect(bb.Min, bb.Max) then
g.LastItemData.StatusFlags = bit32.bor(g.LastItemData.StatusFlags, ImGuiItemStatusFlags.HoveredRect) last_item_data.StatusFlags = bit32.bor(last_item_data.StatusFlags, ImGuiItemStatusFlags.HoveredRect)
end end
return true return true
@@ -6485,7 +6493,7 @@ function ImGui.ItemSize(size_or_bb, text_baseline_y)
if text_baseline_y == nil then text_baseline_y = -1.0 end if text_baseline_y == nil then text_baseline_y = -1.0 end
local size_x, size_y local size_x, size_y
local bb_min = size_or_bb.Min local bb_min = rawget(size_or_bb, "Min") -- [Luau] rawget: `.Min` on an ImVec2 would go through IM_VEC2.__index
if bb_min then if bb_min then
--- @cast size_or_bb ImRect --- @cast size_or_bb ImRect
size_x = size_or_bb.Max.x - bb_min.x; size_y = size_or_bb.Max.y - bb_min.y size_x = size_or_bb.Max.x - bb_min.x; size_y = size_or_bb.Max.y - bb_min.y
@@ -6566,18 +6574,19 @@ function ImGui.SameLine(offset_from_start_x, spacing_w)
return return
end end
local dc = window.DC -- [Luau] cached field read
if offset_from_start_x ~= 0.0 then if offset_from_start_x ~= 0.0 then
if spacing_w < 0.0 then spacing_w = 0.0 end if spacing_w < 0.0 then spacing_w = 0.0 end
window.DC.CursorPos.x = window.Pos.x - window.Scroll.x + offset_from_start_x + spacing_w + window.DC.GroupOffset.x + window.DC.ColumnsOffset.x dc.CursorPos.x = window.Pos.x - window.Scroll.x + offset_from_start_x + spacing_w + dc.GroupOffset.x + dc.ColumnsOffset.x
window.DC.CursorPos.y = window.DC.CursorPosPrevLine.y dc.CursorPos.y = dc.CursorPosPrevLine.y
else else
if spacing_w < 0.0 then spacing_w = g.Style.ItemSpacing.x end if spacing_w < 0.0 then spacing_w = g.Style.ItemSpacing.x end
window.DC.CursorPos.x = window.DC.CursorPosPrevLine.x + spacing_w dc.CursorPos.x = dc.CursorPosPrevLine.x + spacing_w
window.DC.CursorPos.y = window.DC.CursorPosPrevLine.y dc.CursorPos.y = dc.CursorPosPrevLine.y
end end
ImVec2_Copy(window.DC.CurrLineSize, window.DC.PrevLineSize) dc.CurrLineSize.x = dc.PrevLineSize.x; dc.CurrLineSize.y = dc.PrevLineSize.y -- [Luau] inline ImVec2_Copy()
window.DC.CurrLineTextBaseOffset = window.DC.PrevLineTextBaseOffset dc.CurrLineTextBaseOffset = dc.PrevLineTextBaseOffset
window.DC.IsSameLine = true dc.IsSameLine = true
end end
--- @param indent_w? float --- @param indent_w? float
@@ -6625,7 +6634,10 @@ end
function ImGui.PushItemWidth(item_width) function ImGui.PushItemWidth(item_width)
local g = GImGui local g = GImGui
local window = g.CurrentWindow local window = g.CurrentWindow
window.DC.ItemWidthStack:push_back(window.DC.ItemWidth) -- Backup current width -- [Luau] inline window.DC.ItemWidthStack:push_back(window.DC.ItemWidth) -- Backup current width
local stack = window.DC.ItemWidthStack
local n = stack.Size + 1
if n > stack.Capacity then stack:push_back(window.DC.ItemWidth) else stack.Data[n] = window.DC.ItemWidth; stack.Size = n end
window.DC.ItemWidth = (item_width == 0.0) and window.DC.ItemWidthDefault or item_width window.DC.ItemWidth = (item_width == 0.0) and window.DC.ItemWidthDefault or item_width
g.NextItemData.HasFlags = bit32.band(g.NextItemData.HasFlags, bit32.bnot(ImGuiNextItemDataFlags.HasWidth)) g.NextItemData.HasFlags = bit32.band(g.NextItemData.HasFlags, bit32.bnot(ImGuiNextItemDataFlags.HasWidth))
end end
@@ -6637,8 +6649,9 @@ function ImGui.PopItemWidth()
IM_ASSERT_USER_ERROR(false, "Calling PopItemWidth() too many times!") IM_ASSERT_USER_ERROR(false, "Calling PopItemWidth() too many times!")
return return
end end
window.DC.ItemWidth = window.DC.ItemWidthStack:back() local stack = window.DC.ItemWidthStack
window.DC.ItemWidthStack:pop_back() window.DC.ItemWidth = stack.Data[stack.Size] -- [Luau] inline back()/pop_back()
stack.Size = stack.Size - 1
end end
--- @param components int --- @param components int
@@ -6909,8 +6922,10 @@ function ImGui.CalcItemWidth()
end end
if w < 0.0 then if w < 0.0 then
local region_avail_x = ImGui.GetContentRegionAvail().x -- [Luau] scalar GetContentRegionAvail().x, no temporary ImVec2
w = ImMax(1.0, region_avail_x + w) local mx = (window.DC.CurrentColumns or g.CurrentTable) and window.WorkRect.Max or window.ContentRegionRect.Max
local region_avail_x = mx.x - window.DC.CursorPos.x
w = math.max(1.0, region_avail_x + w)
end end
w = IM_TRUNC(w) w = IM_TRUNC(w)
@@ -6926,21 +6941,25 @@ end
--- @param default_h float --- @param default_h float
--- @return ImVec2 --- @return ImVec2
function ImGui.CalcItemSize(size, default_w, default_h) function ImGui.CalcItemSize(size, default_w, default_h)
local avail local avail_x, avail_y
if size.x < 0.0 or size.y < 0.0 then if size.x < 0.0 or size.y < 0.0 then
avail = ImGui.GetContentRegionAvail() -- [Luau] scalar GetContentRegionAvail(), no temporary ImVec2
local g = GImGui
local window = g.CurrentWindow
local mx = (window.DC.CurrentColumns or g.CurrentTable) and window.WorkRect.Max or window.ContentRegionRect.Max
avail_x = mx.x - window.DC.CursorPos.x; avail_y = mx.y - window.DC.CursorPos.y
end end
if size.x == 0.0 then if size.x == 0.0 then
size.x = default_w size.x = default_w
elseif size.x < 0.0 then elseif size.x < 0.0 then
size.x = ImMax(4.0, avail.x + size.x) -- <-- size.x is negative here so we are subtracting size.x = math.max(4.0, avail_x + size.x) -- <-- size.x is negative here so we are subtracting
end end
if size.y == 0.0 then if size.y == 0.0 then
size.y = default_h size.y = default_h
elseif size.y < 0.0 then elseif size.y < 0.0 then
size.y = ImMax(4.0, avail.y + size.y) -- <-- size.y is negative here so we are subtracting size.y = math.max(4.0, avail_y + size.y) -- <-- size.y is negative here so we are subtracting
end end
return size return size
@@ -7481,7 +7500,10 @@ function ImGui.PushID(str_id)
local g = GImGui local g = GImGui
local window = g.CurrentWindow local window = g.CurrentWindow
local id = window:GetID(str_id) local id = window:GetID(str_id)
window.IDStack:push_back(id) -- [Luau] inline window.IDStack:push_back(id)
local stack = window.IDStack
local n = stack.Size + 1
if n > stack.Capacity then stack:push_back(id) else stack.Data[n] = id; stack.Size = n end
end end
-- Push a given id value ignoring the ID stack as a seed. -- Push a given id value ignoring the ID stack as a seed.
@@ -7492,14 +7514,18 @@ function ImGui.PushOverrideID(id)
if g.DebugHookIdInfoId == id and id ~= 0 then if g.DebugHookIdInfoId == id and id ~= 0 then
ImGui.DebugHookIdInfo(id, ImGuiDataType.ID, nil) ImGui.DebugHookIdInfo(id, ImGuiDataType.ID, nil)
end end
window.IDStack:push_back(id) -- [Luau] inline window.IDStack:push_back(id)
local stack = window.IDStack
local n = stack.Size + 1
if n > stack.Capacity then stack:push_back(id) else stack.Data[n] = id; stack.Size = n end
end end
@native @native
function ImGui.PopID() function ImGui.PopID()
local window = GImGui.CurrentWindow local window = GImGui.CurrentWindow
IM_ASSERT_USER_ERROR_RET(window.IDStack.Size > 1, "Calling PopID() too many times!") local stack = window.IDStack
window.IDStack:pop_back() IM_ASSERT_USER_ERROR_RET(stack.Size > 1, "Calling PopID() too many times!")
stack.Size = stack.Size - 1 -- [Luau] inline stack:pop_back()
end end
-- This is one of the very rare legacy case where we use ImGuiWindow methods, -- This is one of the very rare legacy case where we use ImGuiWindow methods,
@@ -7508,7 +7534,8 @@ end
--- @return ImGuiID --- @return ImGuiID
@native @native
function MT.ImGuiWindow:GetID(id) function MT.ImGuiWindow:GetID(id)
local seed = self.IDStack:back() local stack = self.IDStack
local seed = stack.Data[stack.Size] -- [Luau] inline self.IDStack:back()
local h local h
if type(id) == "string" then if type(id) == "string" then
@@ -7904,7 +7931,9 @@ end
function ImGui.IsClippedEx(bb, id) function ImGui.IsClippedEx(bb, id)
local g = GImGui local g = GImGui
local window = g.CurrentWindow local window = g.CurrentWindow
if not bb:Overlaps(window.ClipRect) then -- [Luau] inline bb:Overlaps(window.ClipRect)
local bmin, bmax, cr = bb.Min, bb.Max, window.ClipRect
if not (bmin.x <= cr.Max.x and bmax.x >= cr.Min.x and bmin.y <= cr.Max.y and bmax.y >= cr.Min.y) then
if id == 0 or (id ~= g.ActiveId and id ~= g.ActiveIdPreviousFrame and id ~= g.NavId and id ~= g.NavActivateId) then if id == 0 or (id ~= g.ActiveId and id ~= g.ActiveIdPreviousFrame and id ~= g.NavId and id ~= g.NavActivateId) then
if not g.ItemUnclipByLog then if not g.ItemUnclipByLog then
return true return true
@@ -10011,7 +10040,7 @@ function ImGui.CalcWrapWidthForPos(pos, wrap_pos_x)
wrap_pos_x = wrap_pos_x + window.Pos.x - window.Scroll.x -- wrap_pos_x is provided is window local space wrap_pos_x = wrap_pos_x + window.Pos.x - window.Scroll.x -- wrap_pos_x is provided is window local space
end end
return ImMax(wrap_pos_x - pos.x, 1.0) return math.max(wrap_pos_x - pos.x, 1.0)
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@@ -10033,7 +10062,7 @@ function ImGui.FindRenderedTextEnd(text, text_end)
end end
text_display_end = ImMemchr(text, "##", 1) or text_end text_display_end = ImMemchr(text, "##", 1) or text_end
text_display_end = ImMin(text_display_end, text_end) text_display_end = math.min(text_display_end, text_end) -- [Luau] scalar ImMin()
return text_display_end return text_display_end
end end
@@ -10104,13 +10133,17 @@ end
--- @param text_size_if_known? ImVec2 --- @param text_size_if_known? ImVec2
--- @param align? ImVec2 --- @param align? ImVec2
--- @param clip_rect? ImRect --- @param clip_rect? ImRect
local RenderTextClipped_align0 = ImVec2(0, 0) -- [Luau] shared read-only default
local RenderTextClippedEx_pos = ImVec2() -- [Luau] scratch: AddText() only reads pos
local RenderTextClippedEx_fine_clip = ImVec4() -- [Luau] scratch: AddText() only reads it
@native @native
function ImGui.RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_begin, text_display_end, text_size_if_known, align, clip_rect) function ImGui.RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_begin, text_display_end, text_size_if_known, align, clip_rect)
if not align then align = ImVec2(0, 0) end if not align then align = RenderTextClipped_align0 end
-- Perform CPU side clipping for single clipped element to avoid using scissor state -- Perform CPU side clipping for single clipped element to avoid using scissor state
local pos = ImVec2() local pos = RenderTextClippedEx_pos
ImVec2_Copy(pos, pos_min) pos.x = pos_min.x; pos.y = pos_min.y
local text_size = text_size_if_known or ImGui.CalcTextSize(text, text_display_end, false, 0.0) local text_size = text_size_if_known or ImGui.CalcTextSize(text, text_display_end, false, 0.0)
@@ -10122,13 +10155,13 @@ function ImGui.RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_begin
end end
-- Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment. -- Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment.
if (align.x > 0.0) then pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x) end if (align.x > 0.0) then pos.x = math.max(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x) end
if (align.y > 0.0) then pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y) end if (align.y > 0.0) then pos.y = math.max(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y) end
-- Render -- Render
local g = GImGui
if (need_clipping) then if (need_clipping) then
local fine_clip_rect = ImVec4(clip_min.x, clip_min.y, clip_max.x, clip_max.y) local fine_clip_rect = RenderTextClippedEx_fine_clip
fine_clip_rect.x = clip_min.x; fine_clip_rect.y = clip_min.y; fine_clip_rect.z = clip_max.x; fine_clip_rect.w = clip_max.y
draw_list:AddText(nil, 0.0, pos, ImGui.GetColorU32(ImGuiCol.Text), text, text_begin, text_display_end, 0.0, fine_clip_rect) draw_list:AddText(nil, 0.0, pos, ImGui.GetColorU32(ImGuiCol.Text), text, text_begin, text_display_end, 0.0, fine_clip_rect)
else else
draw_list:AddText(nil, 0.0, pos, ImGui.GetColorU32(ImGuiCol.Text), text, text_begin, text_display_end, 0.0, nil) draw_list:AddText(nil, 0.0, pos, ImGui.GetColorU32(ImGuiCol.Text), text, text_begin, text_display_end, 0.0, nil)
@@ -10143,7 +10176,7 @@ end
--- @param align? ImVec2 --- @param align? ImVec2
--- @param clip_rect? ImRect --- @param clip_rect? ImRect
function ImGui.RenderTextClipped(pos_min, pos_max, text, text_end, text_size_if_known, align, clip_rect) function ImGui.RenderTextClipped(pos_min, pos_max, text, text_end, text_size_if_known, align, clip_rect)
if not align then align = ImVec2(0, 0) end if not align then align = RenderTextClipped_align0 end
-- Hide anything after a '##' string -- Hide anything after a '##' string
local text_display_end = ImGui.FindRenderedTextEnd(text, text_end) local text_display_end = ImGui.FindRenderedTextEnd(text, text_end)
@@ -13548,15 +13581,21 @@ end
--- @param intersect_with_current_clip_rect? bool --- @param intersect_with_current_clip_rect? bool
@native @native
function ImGui.PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect) function ImGui.PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect)
local window = ImGui.GetCurrentWindow() local window = GImGui.CurrentWindow -- [Luau] inline GetCurrentWindow()
window.DrawList:PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect) window.WriteAccessed = true
ImRect_CopyFromV4(window.ClipRect, window.DrawList._ClipRectStack:back()) local draw_list = window.DrawList
draw_list:PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect)
local stack = draw_list._ClipRectStack
ImRect_CopyFromV4(window.ClipRect, stack.Data[stack.Size]) -- [Luau] inline back()
end end
function ImGui.PopClipRect() function ImGui.PopClipRect()
local window = ImGui.GetCurrentWindow() local window = GImGui.CurrentWindow -- [Luau] inline GetCurrentWindow()
window.DrawList:PopClipRect() window.WriteAccessed = true
ImRect_CopyFromV4(window.ClipRect, window.DrawList._ClipRectStack:back()) local draw_list = window.DrawList
draw_list:PopClipRect()
local stack = draw_list._ClipRectStack
ImRect_CopyFromV4(window.ClipRect, stack.Data[stack.Size]) -- [Luau] inline back()
end end
--- @param viewport ImGuiViewportP --- @param viewport ImGuiViewportP
@@ -14201,12 +14240,12 @@ function ImGui.CalcTextSizeEx(text, text_begin, text_end, hide_text_after_double
if text == "" or (text_end and text_end <= text_begin) then if text == "" or (text_end and text_end <= text_begin) then
return ImVec2(0.0, font_size) return ImVec2(0.0, font_size)
end end
local text_size = font:CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_begin, text_display_end, nil) local text_size = ImFontCalcTextSizeEx(font, font_size, FLT_MAX, wrap_width, text, text_begin, text_display_end, text_display_end, nil, ImDrawTextFlags.None) -- [Luau] inline font:CalcTextSizeA()
-- Round -- Round
-- (see 7b0bf230, 4622fa4b6, #791 for details about this.) -- (see 7b0bf230, 4622fa4b6, #791 for details about this.)
-- FIXME: Add a way to disable this. -- FIXME: Add a way to disable this.
text_size.x = ImCeilFast(text_size.x) text_size.x = math.ceil(text_size.x) -- [Luau] == ImCeilFast() for all finite values, builtin
return text_size return text_size
end end
@@ -14442,7 +14481,13 @@ function ImGui.GetColorU32(col, alpha_mul, col_is_u32)
else --- @cast col ImVec4 else --- @cast col ImVec4
w = col.w * style.Alpha w = col.w * style.Alpha
end end
return bit32.bor(F32ToU8Sat(col.x), bit32.lshift(F32ToU8Sat(col.y), 8), bit32.lshift(F32ToU8Sat(col.z), 16), bit32.lshift(F32ToU8Sat(w), 24)) -- [Luau] inline F32ToU8Sat() x4
local x, y, z = col.x, col.y, col.z
if x < 0.0 then x = 0.0 elseif x > 1.0 then x = 1.0 end
if y < 0.0 then y = 0.0 elseif y > 1.0 then y = 1.0 end
if z < 0.0 then z = 0.0 elseif z > 1.0 then z = 1.0 end
if w < 0.0 then w = 0.0 elseif w > 1.0 then w = 1.0 end
return bit32.bor(math.floor(x * 255.0 + 0.5), bit32.lshift(math.floor(y * 255.0 + 0.5), 8), bit32.lshift(math.floor(z * 255.0 + 0.5), 16), bit32.lshift(math.floor(w * 255.0 + 0.5), 24))
else else
--- @cast col ImU32 --- @cast col ImU32
alpha_mul = alpha_mul * style.Alpha alpha_mul = alpha_mul * style.Alpha
@@ -14463,7 +14508,10 @@ end
function ImGui.PushStyleColor(idx, col) function ImGui.PushStyleColor(idx, col)
local g = GImGui local g = GImGui
local backup = ImGuiColorMod(idx, g.Style.Colors[idx]) local backup = ImGuiColorMod(idx, g.Style.Colors[idx])
g.ColorStack:push_back(backup) -- [Luau] inline g.ColorStack:push_back(backup)
local stack = g.ColorStack
local n = stack.Size + 1
if n > stack.Capacity then stack:push_back(backup) else stack.Data[n] = backup; stack.Size = n end
if g.DebugFlashStyleColorIdx ~= idx then if g.DebugFlashStyleColorIdx ~= idx then
if type(col) == "number" then if type(col) == "number" then
ImGui.ColorConvertU32ToFloat4(col, g.Style.Colors[idx]) ImGui.ColorConvertU32ToFloat4(col, g.Style.Colors[idx])
@@ -14483,10 +14531,11 @@ function ImGui.PopStyleColor(count)
count = g.ColorStack.Size count = g.ColorStack.Size
end end
local stack = g.ColorStack
while count > 0 do while count > 0 do
local backup = g.ColorStack:back() local backup = stack.Data[stack.Size] -- [Luau] inline back()/pop_back()
ImVec4_Copy(g.Style.Colors[backup.Col], backup.BackupValue) ImVec4_Copy(g.Style.Colors[backup.Col], backup.BackupValue)
g.ColorStack:pop_back() stack.Size = stack.Size - 1
count = count - 1 count = count - 1
end end
end end
@@ -14596,8 +14645,9 @@ function ImGui.PopStyleVar(count)
IM_ASSERT_USER_ERROR(0, "Calling PopStyleVar() too many times!") IM_ASSERT_USER_ERROR(0, "Calling PopStyleVar() too many times!")
count = g.StyleVarStack.Size count = g.StyleVarStack.Size
end end
local stack = g.StyleVarStack
while count > 0 do while count > 0 do
local backup = g.StyleVarStack:back() local backup = stack.Data[stack.Size] -- [Luau] inline back()/pop_back()
local var_info = ImGui.GetStyleVarInfo(backup.VarIdx) local var_info = ImGui.GetStyleVarInfo(backup.VarIdx)
local data = g.Style[var_info.Key] local data = g.Style[var_info.Key]
if (var_info.DataType == ImGuiDataType.Float and var_info.Count == 1) then if (var_info.DataType == ImGuiDataType.Float and var_info.Count == 1) then
@@ -14605,7 +14655,7 @@ function ImGui.PopStyleVar(count)
elseif (var_info.DataType == ImGuiDataType.Float and var_info.Count == 2) then elseif (var_info.DataType == ImGuiDataType.Float and var_info.Count == 2) then
data.x = backup.BackupVal[1]; data.y = backup.BackupVal[2] data.x = backup.BackupVal[1]; data.y = backup.BackupVal[2]
end end
g.StyleVarStack:pop_back() stack.Size = stack.Size - 1
count = count - 1 count = count - 1
end end
end end
@@ -14615,16 +14665,21 @@ end
function ImGui.PushTextWrapPos(wrap_local_pos_x) function ImGui.PushTextWrapPos(wrap_local_pos_x)
local g = GImGui local g = GImGui
local window = g.CurrentWindow local window = g.CurrentWindow
window.DC.TextWrapPosStack:push_back(window.DC.TextWrapPos) -- [Luau] inline window.DC.TextWrapPosStack:push_back(window.DC.TextWrapPos)
window.DC.TextWrapPos = wrap_local_pos_x local dc = window.DC
local stack = dc.TextWrapPosStack
local n = stack.Size + 1
if n > stack.Capacity then stack:push_back(dc.TextWrapPos) else stack.Data[n] = dc.TextWrapPos; stack.Size = n end
dc.TextWrapPos = wrap_local_pos_x
end end
function ImGui.PopTextWrapPos() function ImGui.PopTextWrapPos()
local g = GImGui local g = GImGui
local window = g.CurrentWindow local window = g.CurrentWindow
IM_ASSERT_USER_ERROR_RET(window.DC.TextWrapPosStack.Size > 0, "Calling PopTextWrapPos() too many times!") local stack = window.DC.TextWrapPosStack
window.DC.TextWrapPos = window.DC.TextWrapPosStack:back() IM_ASSERT_USER_ERROR_RET(stack.Size > 0, "Calling PopTextWrapPos() too many times!")
window.DC.TextWrapPosStack:pop_back() window.DC.TextWrapPos = stack.Data[stack.Size] -- [Luau] inline back()/pop_back()
stack.Size = stack.Size - 1
end end
--- @param window ImGuiWindow --- @param window ImGuiWindow
@@ -14865,7 +14920,8 @@ function CalcNextScrollFromScrollTargetAndClamp(window)
ImVec2_Copy(scroll, window.Scroll) ImVec2_Copy(scroll, window.Scroll)
local decoration_size = ImVec2(window.DecoOuterSizeX1 + window.DecoInnerSizeX1 + window.DecoOuterSizeX2, window.DecoOuterSizeY1 + window.DecoInnerSizeY1 + window.DecoOuterSizeY2) local decoration_size = ImVec2(window.DecoOuterSizeX1 + window.DecoInnerSizeX1 + window.DecoOuterSizeX2, window.DecoOuterSizeY1 + window.DecoInnerSizeY1 + window.DecoOuterSizeY2)
for axis = ImGuiAxis.X, ImGuiAxis.Y do for axis_n = ImGuiAxis.X, ImGuiAxis.Y do
local axis = (axis_n == ImGuiAxis.X) and "x" or "y" -- [Luau] named key: ImVec2[1]/[2] go through IM_VEC2.__index
if window.ScrollTarget[axis] < FLT_MAX then if window.ScrollTarget[axis] < FLT_MAX then
local center_ratio = window.ScrollTargetCenterRatio[axis] local center_ratio = window.ScrollTargetCenterRatio[axis]
local scroll_target = window.ScrollTarget[axis] local scroll_target = window.ScrollTarget[axis]
@@ -17568,7 +17624,7 @@ end
function ImGui.GetMainViewport() function ImGui.GetMainViewport()
local g = GImGui local g = GImGui
return g.Viewports[1] return g.Viewports.Data[1] -- [Luau] .Data[]: no IM_VECTOR.__index
end end
-- FIXME: This leaks access to viewports not listed in PlatformIO.Viewports[]. Problematic? (#4236) -- FIXME: This leaks access to viewports not listed in PlatformIO.Viewports[]. Problematic? (#4236)
@@ -20026,23 +20082,34 @@ function ImGui.DebugNodeFontGlyphsForSrcMask(font, baked, src_mask)
local base = 0 local base = 0
while base <= max_c do while base <= max_c do
local count = 0 local count = 0
-- [Luau] inline IsGlyphLoaded() + FindGlyphNoFallback() (a loaded glyph never needs loading): 0xFFFF = IM_FONTGLYPH_INDEX_UNUSED, 0xFFFE = IM_FONTGLYPH_INDEX_NOT_FOUND
local lookup, lookup_n, glyphs = baked.IndexLookup.Data, baked.IndexLookup.Size, baked.Glyphs.Data
for n = 0, 255 do for n = 0, 255 do
local c = base + n local c = base + n
if baked:IsGlyphLoaded(c) then if c < lookup_n then
local glyph = baked:FindGlyphNoFallback(c) local i = lookup[c + 1]
if glyph and bit32.band(src_mask, bit32.lshift(1, glyph.SourceIdx)) ~= 0 then count = count + 1 end if i ~= 0xFFFF and i ~= 0xFFFE then
local glyph = glyphs[i]
if glyph and bit32.band(src_mask, bit32.lshift(1, glyph.SourceIdx)) ~= 0 then count = count + 1 end
end
end end
end end
if count > 0 and ImGui.TreeNode(base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, (count > 1) and "glyphs" or "glyph") then if count > 0 and ImGui.TreeNode(base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, (count > 1) and "glyphs" or "glyph") then
-- Draw a 16x16 grid of glyphs -- Draw a 16x16 grid of glyphs
local base_pos = ImGui.GetCursorScreenPos() local base_pos = ImGui.GetCursorScreenPos()
local cell_p1, cell_p2 = ImVec2(), ImVec2()
for n = 0, 255 do for n = 0, 255 do
local c = base + n local c = base + n
-- We use ImFont:RenderChar as a shortcut because we don't have UTF-8 conversion functions -- We use ImFont:RenderChar as a shortcut because we don't have UTF-8 conversion functions
-- available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. -- available here and thus cannot easily generate a zero-terminated UTF-8 encoded string.
local cell_p1 = ImVec2(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + math.floor(n / 16) * (cell_size + cell_spacing)) -- [Luau] reused per cell: AddRect()/RenderChar()/IsMouseHoveringRect() only read them
local cell_p2 = ImVec2(cell_p1.x + cell_size, cell_p1.y + cell_size) cell_p1.x = base_pos.x + (n % 16) * (cell_size + cell_spacing); cell_p1.y = base_pos.y + math.floor(n / 16) * (cell_size + cell_spacing)
local glyph = baked:IsGlyphLoaded(c) and baked:FindGlyphNoFallback(c) or nil cell_p2.x = cell_p1.x + cell_size; cell_p2.y = cell_p1.y + cell_size
local glyph = nil -- [Luau] inline IsGlyphLoaded() + FindGlyphNoFallback(), see above
if c < baked.IndexLookup.Size then
local i = baked.IndexLookup.Data[c + 1]
if i ~= 0xFFFF and i ~= 0xFFFE then glyph = baked.Glyphs.Data[i] end
end
draw_list:AddRect(cell_p1, cell_p2, glyph and IM_COL32(255, 255, 255, 100) or IM_COL32(255, 255, 255, 50)) draw_list:AddRect(cell_p1, cell_p2, glyph and IM_COL32(255, 255, 255, 100) or IM_COL32(255, 255, 255, 50))
if glyph and bit32.band(src_mask, bit32.lshift(1, glyph.SourceIdx)) ~= 0 then if glyph and bit32.band(src_mask, bit32.lshift(1, glyph.SourceIdx)) ~= 0 then
font:RenderChar(draw_list, cell_size, cell_p1, glyph_col, c) font:RenderChar(draw_list, cell_size, cell_p1, glyph_col, c)
+320 -152
View File
@@ -2608,7 +2608,7 @@ function MT.ImFont:GetFontBaked(size, density)
-- Round font size -- Round font size
-- - ImGui.PushFont() will already round, but other paths calling GetFontBaked() directly also needs it (e.g. ImFontAtlasBuildPreloadAllGlyphRanges) -- - ImGui.PushFont() will already round, but other paths calling GetFontBaked() directly also needs it (e.g. ImFontAtlasBuildPreloadAllGlyphRanges)
size = ImGui.GetRoundedFontSize(size) size = math.floor(size + 0.5) -- [Luau] inline ImGui.GetRoundedFontSize() == IM_ROUND()
if density < 0.0 then if density < 0.0 then
density = self.CurrentRasterizerDensity density = self.CurrentRasterizerDensity
@@ -2869,7 +2869,37 @@ end
--- @param wrap_width float --- @param wrap_width float
--- @param flags ImDrawTextFlags --- @param flags ImDrawTextFlags
--- @return int --- @return int
local ImFontCalcWordWrapPositionEx_Uncached
local WRAP_MEMO_MAX = 4096
function ImFontCalcWordWrapPositionEx(font, size, text, pos, text_end, wrap_width, flags) function ImFontCalcWordWrapPositionEx(font, size, text, pos, text_end, wrap_width, flags)
local baked = font:GetFontBaked(size)
if type(text) ~= "string" then
return ImFontCalcWordWrapPositionEx_Uncached(baked, size, text, pos, text_end, wrap_width, flags)
end
-- [Luau] memo per baked font and string: CalcTextSize() and RenderText() of wrapped text ask for the same wrap points
-- (same text/pos/width) every frame. The result only depends on these inputs and the (immutable once loaded) glyph advances.
-- ponytail: whole-memo reset at WRAP_MEMO_MAX strings, LRU if dynamic text churns it
local memo = baked._WrapMemo
if memo == nil or baked._WrapMemoN >= WRAP_MEMO_MAX then
memo = {}; baked._WrapMemo = memo; baked._WrapMemoN = 0
end
local e = memo[text]
if e == nil or e.ww ~= wrap_width or e.size ~= size or e.te ~= text_end or e.fl ~= flags then
if e == nil then baked._WrapMemoN = baked._WrapMemoN + 1 end
e = { ww = wrap_width, size = size, te = text_end, fl = flags }
memo[text] = e
else
local r = e[pos]
if r then return r end
end
local r = ImFontCalcWordWrapPositionEx_Uncached(baked, size, text, pos, text_end, wrap_width, flags)
e[pos] = r
return r
end
@native
function ImFontCalcWordWrapPositionEx_Uncached(baked, size, text, pos, text_end, wrap_width, flags)
-- For references, possible wrap point marked with ^ -- For references, possible wrap point marked with ^
-- "aaa bbb, ccc,ddd. eee fff. ggg!" -- "aaa bbb, ccc,ddd. eee fff. ggg!"
-- ^ ^ ^ ^ ^__ ^ ^ -- ^ ^ ^ ^ ^__ ^ ^
@@ -2880,7 +2910,7 @@ function ImFontCalcWordWrapPositionEx(font, size, text, pos, text_end, wrap_widt
-- Cut words that cannot possibly fit within one line. -- Cut words that cannot possibly fit within one line.
-- e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" -- e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish"
local baked = font:GetFontBaked(size) -- [Luau] baked = font:GetFontBaked(size) is done by the caller
local scale = size / baked.Size local scale = size / baked.Size
local line_width = 0.0 local line_width = 0.0
@@ -2899,10 +2929,15 @@ function ImFontCalcWordWrapPositionEx(font, size, text, pos, text_end, wrap_widt
local span_width = 0.0 local span_width = 0.0
local stop = false local stop = false
local text_is_str = type(text) == "string" -- [Luau] ImStrByte() inlined (once per character)
local adv_data, adv_n = baked.IndexAdvanceX.Data, baked.IndexAdvanceX.Size -- [Luau] hoisted, refreshed after a glyph load
local CLASS_BLANK, CLASS_PUNCT, CLASS_OTHER = ImWcharClass.Blank, ImWcharClass.Punct, ImWcharClass.Other
local classifier_0000_007f = g_CharClassifierIsSeparator_0000_007f
while s < text_end do while s < text_end do
repeat repeat
--- @type unsigned_int --- @type unsigned_int
local c = ImStrByte(text, s) local c
if text_is_str then c = string.byte(text, s) else c = text[s] end
local next_s local next_s
if c < 0x80 then if c < 0x80 then
next_s = s + 1 next_s = s + 1
@@ -2924,24 +2959,25 @@ function ImFontCalcWordWrapPositionEx(font, size, text, pos, text_end, wrap_widt
end end
-- Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' -- Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);'
local char_width = (c < baked.IndexAdvanceX.Size) and baked.IndexAdvanceX.Data[c + 1] or -1.0 local char_width = (c < adv_n) and adv_data[c + 1] or -1.0
if char_width < 0.0 then if char_width < 0.0 then
char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c) char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c)
adv_data, adv_n = baked.IndexAdvanceX.Data, baked.IndexAdvanceX.Size
end end
-- Classify current character -- Classify current character
local curr_type local curr_type
if c < 128 then if c < 128 then -- [Luau] ImTextClassifierGet() inlined
curr_type = ImTextClassifierGet(g_CharClassifierIsSeparator_0000_007f, c) curr_type = bit32.band(bit32.rshift(classifier_0000_007f[bit32.rshift(c, 4) + 1], bit32.lshift(bit32.band(c, 15), 1)), 0x03)
elseif (c >= 0x3000 and c < 0x3010) then elseif (c >= 0x3000 and c < 0x3010) then
curr_type = ImTextClassifierGet(g_CharClassifierIsSeparator_3000_300f, bit32.band(c, 15)) curr_type = ImTextClassifierGet(g_CharClassifierIsSeparator_3000_300f, bit32.band(c, 15))
else else
curr_type = ImWcharClass.Other curr_type = CLASS_OTHER
end end
if curr_type == ImWcharClass.Blank then if curr_type == CLASS_BLANK then
-- End span: 'A ' or '. ' -- End span: 'A ' or '. '
if (prev_type ~= ImWcharClass.Blank and not keep_blanks) then if (prev_type ~= CLASS_BLANK and not keep_blanks) then
span_end = s span_end = s
line_width = line_width + span_width line_width = line_width + span_width
span_width = 0.0 span_width = 0.0
@@ -2950,13 +2986,13 @@ function ImFontCalcWordWrapPositionEx(font, size, text, pos, text_end, wrap_widt
blank_width = blank_width + char_width blank_width = blank_width + char_width
else else
-- End span: '.X' unless X is a digit -- End span: '.X' unless X is a digit
if (prev_type == ImWcharClass.Punct and curr_type ~= ImWcharClass.Punct and not (c >= 48 and c <= 57)) then -- FIXME: Digit checks might be removed if allow custom separators (#8503) if (prev_type == CLASS_PUNCT and curr_type ~= CLASS_PUNCT and not (c >= 48 and c <= 57)) then -- FIXME: Digit checks might be removed if allow custom separators (#8503)
span_end = s span_end = s
line_width = line_width + (span_width + blank_width) line_width = line_width + (span_width + blank_width)
blank_width = 0.0 blank_width = 0.0
span_width = 0.0 span_width = 0.0
-- End span: 'A ' or '. ' -- End span: 'A ' or '. '
elseif (prev_type == ImWcharClass.Blank and keep_blanks) then elseif (prev_type == CLASS_BLANK and keep_blanks) then
span_end = s span_end = s
line_width = line_width + (span_width + blank_width) line_width = line_width + (span_width + blank_width)
blank_width = 0.0 blank_width = 0.0
@@ -3011,11 +3047,15 @@ local TEXT_SIZE_CACHE_MAX = 4096
@native @native
function ImFontCalcTextSizeEx(font, size, max_width, wrap_width, text, text_begin, text_end_display, text_end, out_offset, flags) function ImFontCalcTextSizeEx(font, size, max_width, wrap_width, text, text_begin, text_end_display, text_end, out_offset, flags)
local text_is_str = type(text) == "string"
-- [Luau] cache for the common case (whole string, single pass, no wrap/limit): per baked font, text -> unscaled width, line count. -- [Luau] cache for the common case (whole string, single pass, no wrap/limit): per baked font, text -> unscaled width, line count.
-- Advances of a baked font never change once loaded, so entries stay valid. ponytail: whole-cache reset at TEXT_SIZE_CACHE_MAX entries, LRU if dynamic text churns it -- Advances of a baked font never change once loaded, so entries stay valid. ponytail: whole-cache reset at TEXT_SIZE_CACHE_MAX entries, LRU if dynamic text churns it
local cache_key = nil local cache_key = nil
if text_begin == 1 and out_offset == nil and wrap_width <= 0.0 and max_width >= FLT_MAX and type(text) == "string" local wrap_cache = nil
and (text_end == nil or text_end == #text + 1) and (text_end_display == nil or text_end_display == #text + 1) and (flags == nil or bit32.band(flags, ImDrawTextFlags.StopOnNewLine) == 0) then local text_len1 = text_is_str and #text + 1 or -1
local cacheable = text_is_str and text_begin == 1 and out_offset == nil and max_width >= FLT_MAX
and (text_end == nil or text_end == text_len1) and (text_end_display == nil or text_end_display == text_len1) and (flags == nil or bit32.band(flags, ImDrawTextFlags.StopOnNewLine) == 0)
if cacheable and wrap_width <= 0.0 then
local baked = font:GetFontBaked(size) local baked = font:GetFontBaked(size)
local cache = baked._TextSizeCache local cache = baked._TextSizeCache
if cache == nil or baked._TextSizeCacheN >= TEXT_SIZE_CACHE_MAX then if cache == nil or baked._TextSizeCacheN >= TEXT_SIZE_CACHE_MAX then
@@ -3023,9 +3063,25 @@ function ImFontCalcTextSizeEx(font, size, max_width, wrap_width, text, text_begi
end end
local e = cache[text] local e = cache[text]
if e then if e then
return ImVec2(e[1] * (size / baked.Size), e[2] * size), #text + 1 return ImVec2(e[1] * (size / baked.Size), e[2] * size), text_len1
end end
cache_key = text cache_key = text
elseif cacheable and wrap_width > 0.0 then
-- [Luau] same for wrapped text (TextWrapped() measures every line every frame): exact results per (size, wrap width, text)
local baked = font:GetFontBaked(size)
local cache = baked._TextSizeCache
if cache == nil or baked._TextSizeCacheN >= TEXT_SIZE_CACHE_MAX then
cache = {}; baked._TextSizeCache = cache; baked._TextSizeCacheN = 0
end
local by_size = cache[size]
if by_size == nil then by_size = {}; cache[size] = by_size end
local by_wrap = by_size[wrap_width]
if by_wrap == nil then by_wrap = {}; by_size[wrap_width] = by_wrap end
local e = by_wrap[text]
if e then
return ImVec2(e[1], e[2]), e[3]
end
wrap_cache = by_wrap
end end
if not text_end then if not text_end then
text_end = #text + 1 -- FIXME-OPT: Need to avoid this. text_end = #text + 1 -- FIXME-OPT: Need to avoid this.
@@ -3046,6 +3102,7 @@ function ImFontCalcTextSizeEx(font, size, max_width, wrap_width, text, text_begi
local s = text_begin local s = text_begin
local stop = false local stop = false
local adv_data, adv_n = baked.IndexAdvanceX.Data, baked.IndexAdvanceX.Size -- [Luau] hoisted, refreshed after a glyph load
while s < text_end_display do while s < text_end_display do
repeat repeat
-- Word-wrapping -- Word-wrapping
@@ -3053,6 +3110,7 @@ function ImFontCalcTextSizeEx(font, size, max_width, wrap_width, text, text_begi
-- Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. -- Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature.
if not word_wrap_eol then if not word_wrap_eol then
word_wrap_eol = ImFontCalcWordWrapPositionEx(font, size, text, s, text_end, wrap_width - line_width, flags) word_wrap_eol = ImFontCalcWordWrapPositionEx(font, size, text, s, text_end, wrap_width - line_width, flags)
adv_data, adv_n = baked.IndexAdvanceX.Data, baked.IndexAdvanceX.Size -- [Luau] may have loaded glyphs
end end
if s >= word_wrap_eol then if s >= word_wrap_eol then
@@ -3073,7 +3131,8 @@ function ImFontCalcTextSizeEx(font, size, max_width, wrap_width, text, text_begi
-- Decode and advance source -- Decode and advance source
local prev_s = s local prev_s = s
local c = ImStrByte(text, s) local c
if text_is_str then c = string.byte(text, s) else c = text[s] end -- [Luau] ImStrByte() inlined
if c < 0x80 then if c < 0x80 then
s = s + 1 s = s + 1
else else
@@ -3098,9 +3157,10 @@ function ImFontCalcTextSizeEx(font, size, max_width, wrap_width, text, text_begi
end end
-- Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' -- Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);'
local char_width = (c < baked.IndexAdvanceX.Size) and baked.IndexAdvanceX.Data[c + 1] or -1.0 local char_width = (c < adv_n) and adv_data[c + 1] or -1.0
if (char_width < 0.0) then if (char_width < 0.0) then
char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c) char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c)
adv_data, adv_n = baked.IndexAdvanceX.Data, baked.IndexAdvanceX.Size
end end
char_width = char_width * scale char_width = char_width * scale
@@ -3133,6 +3193,9 @@ function ImFontCalcTextSizeEx(font, size, max_width, wrap_width, text, text_begi
if cache_key then if cache_key then
baked._TextSizeCache[cache_key] = { text_size.x / scale, text_size.y / line_height } baked._TextSizeCache[cache_key] = { text_size.x / scale, text_size.y / line_height }
baked._TextSizeCacheN = baked._TextSizeCacheN + 1 baked._TextSizeCacheN = baked._TextSizeCacheN + 1
elseif wrap_cache then
wrap_cache[text] = { text_size.x, text_size.y, out_remaining }
baked._TextSizeCacheN = baked._TextSizeCacheN + 1
end end
return text_size, out_remaining return text_size, out_remaining
@@ -3155,6 +3218,8 @@ end
--- @param col ImU32 --- @param col ImU32
--- @param c ImWchar --- @param c ImWchar
--- @param cpu_fine_clip ImVec4 --- @param cpu_fine_clip ImVec4
local RenderChar_a, RenderChar_c, RenderChar_uv_a, RenderChar_uv_c = ImVec2(), ImVec2(), ImVec2(), ImVec2()
function MT.ImFont:RenderChar(draw_list, size, pos, col, c, cpu_fine_clip) function MT.ImFont:RenderChar(draw_list, size, pos, col, c, cpu_fine_clip)
local baked = self:GetFontBaked(size) local baked = self:GetFontBaked(size)
local glyph = baked:FindGlyph(c) local glyph = baked:FindGlyph(c)
@@ -3202,7 +3267,10 @@ function MT.ImFont:RenderChar(draw_list, size, pos, col, c, cpu_fine_clip)
if y1 >= y2 then return end if y1 >= y2 then return end
end end
draw_list:PrimReserve(6, 4) draw_list:PrimReserve(6, 4)
draw_list:PrimRectUV(ImVec2(x1, y1), ImVec2(x2, y2), ImVec2(u1, v1), ImVec2(u2, v2), col) -- [Luau] scratch vectors, PrimRectUV() only reads them
local a, c, uv_a, uv_c = RenderChar_a, RenderChar_c, RenderChar_uv_a, RenderChar_uv_c
a.x = x1; a.y = y1; c.x = x2; c.y = y2; uv_a.x = u1; uv_a.y = v1; uv_c.x = u2; uv_c.y = v2
draw_list:PrimRectUV(a, c, uv_a, uv_c, col)
end end
function MT.ImFontAtlas:AddFont(font_cfg_in) function MT.ImFontAtlas:AddFont(font_cfg_in)
@@ -3645,7 +3713,10 @@ function MT.ImDrawList:AddDrawCmd()
draw_cmd.IdxOffset = self.IdxBuffer.Size draw_cmd.IdxOffset = self.IdxBuffer.Size
IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z and draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w) IM_ASSERT(draw_cmd.ClipRect.x <= draw_cmd.ClipRect.z and draw_cmd.ClipRect.y <= draw_cmd.ClipRect.w)
self.CmdBuffer:push_back(draw_cmd) -- [Luau] inline self.CmdBuffer:push_back(draw_cmd)
local cmd_buffer = self.CmdBuffer
local n = cmd_buffer.Size + 1
if n > cmd_buffer.Capacity then cmd_buffer:push_back(draw_cmd) else cmd_buffer.Data[n] = draw_cmd; cmd_buffer.Size = n end
end end
-- Pop trailing draw command (used before merging or presenting to user) -- Pop trailing draw command (used before merging or presenting to user)
@@ -3696,15 +3767,17 @@ end
--- @param CMD_RHS ImDrawCmd|ImDrawCmdHeader --- @param CMD_RHS ImDrawCmd|ImDrawCmdHeader
--- @return bool --- @return bool
local function ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) local function ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS)
if CMD_LHS.ClipRect.x ~= CMD_RHS.ClipRect.x or local lcr, rcr = CMD_LHS.ClipRect, CMD_RHS.ClipRect -- [Luau] cache
CMD_LHS.ClipRect.y ~= CMD_RHS.ClipRect.y or if lcr.x ~= rcr.x or
CMD_LHS.ClipRect.z ~= CMD_RHS.ClipRect.z or lcr.y ~= rcr.y or
CMD_LHS.ClipRect.w ~= CMD_RHS.ClipRect.w then lcr.z ~= rcr.z or
lcr.w ~= rcr.w then
return false return false
end end
if CMD_LHS.TexRef._TexData ~= CMD_RHS.TexRef._TexData or local ltex, rtex = CMD_LHS.TexRef, CMD_RHS.TexRef
CMD_LHS.TexRef._TexID ~= CMD_RHS.TexRef._TexID then if ltex ~= rtex and (ltex._TexData ~= rtex._TexData or
ltex._TexID ~= rtex._TexID) then
return false return false
end end
@@ -3753,10 +3826,14 @@ function MT.ImDrawList:_OnChangedClipRect()
-- If current command is used with different settings we need to add a new command -- If current command is used with different settings we need to add a new command
IM_ASSERT_PARANOID(self.CmdBuffer.Size > 0) IM_ASSERT_PARANOID(self.CmdBuffer.Size > 0)
local curr_cmd = self.CmdBuffer.Data[self.CmdBuffer.Size] local curr_cmd = self.CmdBuffer.Data[self.CmdBuffer.Size]
if (curr_cmd.ElemCount ~= 0 and self._CmdHeader.ClipRect ~= curr_cmd.ClipRect) then if curr_cmd.ElemCount ~= 0 then
self:AddDrawCmd() -- [Luau] inline IM_VEC4.__eq
local hcr, ccr = self._CmdHeader.ClipRect, curr_cmd.ClipRect
if hcr.x ~= ccr.x or hcr.y ~= ccr.y or hcr.z ~= ccr.z or hcr.w ~= ccr.w then
self:AddDrawCmd()
return return
end
end end
IM_ASSERT(curr_cmd.UserCallback == nil) IM_ASSERT(curr_cmd.UserCallback == nil)
@@ -3870,8 +3947,8 @@ function MT.ImDrawListSplitter:Split(draw_list, channels_count)
if i > old_channels_count or channels[i] == nil then if i > old_channels_count or channels[i] == nil then
channels[i] = ImDrawChannel() channels[i] = ImDrawChannel()
else else
channels[i]._CmdBuffer:resize(0) channels[i]._CmdBuffer.Size = 0 -- [Luau] inline resize(0)
channels[i]._IdxBuffer:resize(0) channels[i]._IdxBuffer.Size = 0
end end
end end
end end
@@ -3930,16 +4007,21 @@ function MT.ImDrawListSplitter:Merge(draw_list)
local dst_idx = draw_list.IdxBuffer.Data local dst_idx = draw_list.IdxBuffer.Data
for i = 2, self._Count do for i = 2, self._Count do
local ch = channels[i] local ch = channels[i]
local src_cmds = ch._CmdBuffer.Data -- [Luau] skip empty buffers: their .Data may be nil (reading it goes through IM_VECTOR.__index)
for n = 1, ch._CmdBuffer.Size do if ch._CmdBuffer.Size > 0 then
dst_cmds[cmd_write] = src_cmds[n] local src_cmds = ch._CmdBuffer.Data
src_cmds[n] = nil -- ownership moved to the draw list for n = 1, ch._CmdBuffer.Size do
cmd_write = cmd_write + 1 dst_cmds[cmd_write] = src_cmds[n]
src_cmds[n] = nil -- ownership moved to the draw list
cmd_write = cmd_write + 1
end
end end
local src_idx = ch._IdxBuffer.Data if ch._IdxBuffer.Size > 0 then
for n = 1, ch._IdxBuffer.Size do local src_idx = ch._IdxBuffer.Data
dst_idx[idx_write] = src_idx[n] for n = 1, ch._IdxBuffer.Size do
idx_write = idx_write + 1 dst_idx[idx_write] = src_idx[n]
idx_write = idx_write + 1
end
end end
end end
draw_list._IdxWritePtr = idx_write draw_list._IdxWritePtr = idx_write
@@ -3962,6 +4044,7 @@ end
--- @param draw_list ImDrawList --- @param draw_list ImDrawList
--- @param idx int # 0-based channel index --- @param idx int # 0-based channel index
@native
function MT.ImDrawListSplitter:SetCurrentChannel(draw_list, idx) function MT.ImDrawListSplitter:SetCurrentChannel(draw_list, idx)
IM_ASSERT(idx >= 0 and idx < self._Count) IM_ASSERT(idx >= 0 and idx < self._Count)
if self._Current == idx then if self._Current == idx then
@@ -3975,18 +4058,29 @@ function MT.ImDrawListSplitter:SetCurrentChannel(draw_list, idx)
cur._IdxBuffer = draw_list.IdxBuffer cur._IdxBuffer = draw_list.IdxBuffer
self._Current = idx self._Current = idx
local ch = channels[idx + 1] local ch = channels[idx + 1]
draw_list.CmdBuffer = ch._CmdBuffer local cmd_buffer = ch._CmdBuffer
draw_list.IdxBuffer = ch._IdxBuffer local idx_buffer = ch._IdxBuffer
draw_list._IdxWritePtr = draw_list.IdxBuffer.Size + 1 draw_list.CmdBuffer = cmd_buffer
draw_list.IdxBuffer = idx_buffer
draw_list._IdxWritePtr = idx_buffer.Size + 1
-- If current command is used with different settings we need to add a new command -- If current command is used with different settings we need to add a new command
local curr_cmd = nil -- [Luau] ImDrawCmd_HeaderCopyRef() / ImDrawCmd_HeaderCompare() inlined
if draw_list.CmdBuffer.Size > 0 then curr_cmd = draw_list.CmdBuffer.Data[draw_list.CmdBuffer.Size] end local cmd_count = cmd_buffer.Size
if curr_cmd == nil then if cmd_count == 0 then
draw_list:AddDrawCmd() draw_list:AddDrawCmd()
elseif curr_cmd.ElemCount == 0 then return
ImDrawCmd_HeaderCopyRef(curr_cmd, draw_list._CmdHeader) -- Copy ClipRect, TexRef, VtxOffset end
elseif not ImDrawCmd_HeaderCompare(curr_cmd, draw_list._CmdHeader) then local curr_cmd = cmd_buffer.Data[cmd_count]
local header = draw_list._CmdHeader
local hcr, ccr = header.ClipRect, curr_cmd.ClipRect
if curr_cmd.ElemCount == 0 then
ccr.x = hcr.x; ccr.y = hcr.y; ccr.z = hcr.z; ccr.w = hcr.w -- Copy ClipRect, TexRef, VtxOffset
curr_cmd.TexRef = header.TexRef
curr_cmd.VtxOffset = header.VtxOffset
elseif ccr.x ~= hcr.x or ccr.y ~= hcr.y or ccr.z ~= hcr.z or ccr.w ~= hcr.w
or curr_cmd.TexRef._TexData ~= header.TexRef._TexData or curr_cmd.TexRef._TexID ~= header.TexRef._TexID
or curr_cmd.VtxOffset ~= header.VtxOffset then
draw_list:AddDrawCmd() draw_list:AddDrawCmd()
end end
end end
@@ -4020,14 +4114,15 @@ function MT.ImDrawList:AddConvexPolyFilled(points, points_count, col)
local vtx_inner_idx = self._VtxCurrentIdx local vtx_inner_idx = self._VtxCurrentIdx
local vtx_outer_idx = self._VtxCurrentIdx + 1 local vtx_outer_idx = self._VtxCurrentIdx + 1
local idx_data = self.IdxBuffer.Data local idx_data = self.IdxBuffer.Data
local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops
for i = 2, points_count - 1 do for i = 2, points_count - 1 do
local idx_write_ptr = self._IdxWritePtr
idx_data[idx_write_ptr + 0] = vtx_inner_idx; idx_data[idx_write_ptr + 1] = vtx_inner_idx + ((i - 1) * 2); idx_data[idx_write_ptr + 2] = vtx_inner_idx + (i * 2) idx_data[idx_write_ptr + 0] = vtx_inner_idx; idx_data[idx_write_ptr + 1] = vtx_inner_idx + ((i - 1) * 2); idx_data[idx_write_ptr + 2] = vtx_inner_idx + (i * 2)
self._IdxWritePtr = idx_write_ptr + 3 idx_write_ptr = idx_write_ptr + 3
end end
-- Compute normals -- Compute normals
self._Data.TempBuffer:reserve_discard(points_count) local temp_buffer = self._Data.TempBuffer
if points_count > temp_buffer.Capacity then temp_buffer:reserve_discard(points_count) end -- [Luau] skip the method lookup when no growth is needed
local temp_normals = self._Data.TempBuffer.Data local temp_normals = self._Data.TempBuffer.Data
local i0 = points_count local i0 = points_count
@@ -4044,6 +4139,8 @@ function MT.ImDrawList:AddConvexPolyFilled(points, points_count, col)
end end
local vtx_data = self.VtxBuffer.Buf local vtx_data = self.VtxBuffer.Buf
local vtx_write_ptr = self._VtxWritePtr
local uv_x, uv_y = uv.x, uv.y
i0 = points_count i0 = points_count
for i1 = 1, points_count do for i1 = 1, points_count do
local n0 = temp_normals[i0] local n0 = temp_normals[i0]
@@ -4054,18 +4151,19 @@ function MT.ImDrawList:AddConvexPolyFilled(points, points_count, col)
dm_x = dm_x * AA_SIZE * 0.5 dm_x = dm_x * AA_SIZE * 0.5
dm_y = dm_y * AA_SIZE * 0.5 dm_y = dm_y * AA_SIZE * 0.5
local vtx_write_ptr = self._VtxWritePtr local p1 = points[i1]
WV(vtx_data, vtx_write_ptr + 0, points[i1].x - dm_x, points[i1].y - dm_y, uv.x, uv.y, col) WV(vtx_data, vtx_write_ptr + 0, p1.x - dm_x, p1.y - dm_y, uv_x, uv_y, col)
WV(vtx_data, vtx_write_ptr + 1, points[i1].x + dm_x, points[i1].y + dm_y, uv.x, uv.y, col_trans) WV(vtx_data, vtx_write_ptr + 1, p1.x + dm_x, p1.y + dm_y, uv_x, uv_y, col_trans)
self._VtxWritePtr = vtx_write_ptr + 2 vtx_write_ptr = vtx_write_ptr + 2
local idx_write_ptr = self._IdxWritePtr
idx_data[idx_write_ptr + 0] = vtx_inner_idx + ((i1 - 1) * 2); idx_data[idx_write_ptr + 1] = vtx_inner_idx + ((i0 - 1) * 2); idx_data[idx_write_ptr + 2] = vtx_outer_idx + ((i0 - 1) * 2) idx_data[idx_write_ptr + 0] = vtx_inner_idx + ((i1 - 1) * 2); idx_data[idx_write_ptr + 1] = vtx_inner_idx + ((i0 - 1) * 2); idx_data[idx_write_ptr + 2] = vtx_outer_idx + ((i0 - 1) * 2)
idx_data[idx_write_ptr + 3] = vtx_outer_idx + ((i0 - 1) * 2); idx_data[idx_write_ptr + 4] = vtx_outer_idx + ((i1 - 1) * 2); idx_data[idx_write_ptr + 5] = vtx_inner_idx + ((i1 - 1) * 2) idx_data[idx_write_ptr + 3] = vtx_outer_idx + ((i0 - 1) * 2); idx_data[idx_write_ptr + 4] = vtx_outer_idx + ((i1 - 1) * 2); idx_data[idx_write_ptr + 5] = vtx_inner_idx + ((i1 - 1) * 2)
self._IdxWritePtr = idx_write_ptr + 6 idx_write_ptr = idx_write_ptr + 6
i0 = i1 i0 = i1
end end
self._VtxWritePtr = vtx_write_ptr
self._IdxWritePtr = idx_write_ptr
self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count
else else
local idx_count = (points_count - 2) * 3 local idx_count = (points_count - 2) * 3
@@ -4116,8 +4214,9 @@ end
-- Reserve space for a number of vertices and indices. -- Reserve space for a number of vertices and indices.
-- You must finish filling your reserved data before calling PrimReserve() again, as it may reallocate or -- You must finish filling your reserved data before calling PrimReserve() again, as it may reallocate or
-- submit the intermediate results. PrimUnreserve() can be used to release unused allocations. -- submit the intermediate results. PrimUnreserve() can be used to release unused allocations.
-- [Luau] local function (also MT.ImDrawList.PrimReserve): hot callers in this file skip the method lookup
@native @native
function MT.ImDrawList:PrimReserve(idx_count, vtx_count) local function PrimReserve(self, idx_count, vtx_count)
-- Large mesh support (when enabled) -- Large mesh support (when enabled)
IM_ASSERT_PARANOID(idx_count >= 0 and vtx_count >= 0) IM_ASSERT_PARANOID(idx_count >= 0 and vtx_count >= 0)
@@ -4132,14 +4231,20 @@ function MT.ImDrawList:PrimReserve(idx_count, vtx_count)
local draw_cmd = self.CmdBuffer.Data[self.CmdBuffer.Size] local draw_cmd = self.CmdBuffer.Data[self.CmdBuffer.Size]
draw_cmd.ElemCount = draw_cmd.ElemCount + idx_count draw_cmd.ElemCount = draw_cmd.ElemCount + idx_count
local vtx_buffer_old_size = self.VtxBuffer.Size -- [Luau] inline resize() when no growth is needed (IdxBuffer:resize goes through IM_VECTOR.__index)
self.VtxBuffer:resize(vtx_buffer_old_size + vtx_count) local vtx_buffer = self.VtxBuffer
local vtx_buffer_old_size = vtx_buffer.Size
local vtx_new_size = vtx_buffer_old_size + vtx_count
if vtx_new_size > vtx_buffer.Capacity then vtx_buffer:resize(vtx_new_size) else vtx_buffer.Size = vtx_new_size end
self._VtxWritePtr = vtx_buffer_old_size + 1 self._VtxWritePtr = vtx_buffer_old_size + 1
local idx_buffer_old_size = self.IdxBuffer.Size local idx_buffer = self.IdxBuffer
self.IdxBuffer:resize(idx_buffer_old_size + idx_count) local idx_buffer_old_size = idx_buffer.Size
local idx_new_size = idx_buffer_old_size + idx_count
if idx_new_size > idx_buffer.Capacity then idx_buffer:resize(idx_new_size) else idx_buffer.Size = idx_new_size end
self._IdxWritePtr = idx_buffer_old_size + 1 self._IdxWritePtr = idx_buffer_old_size + 1
end end
MT.ImDrawList.PrimReserve = PrimReserve
-- Release the number of reserved vertices/indices from the end of the last reservation made with PrimReserve(). -- Release the number of reserved vertices/indices from the end of the last reservation made with PrimReserve().
function MT.ImDrawList:PrimUnreserve(idx_count, vtx_count) function MT.ImDrawList:PrimUnreserve(idx_count, vtx_count)
@@ -4159,12 +4264,13 @@ end
--- @param a ImVec2 --- @param a ImVec2
--- @param c ImVec2 --- @param c ImVec2
--- @param col ImU32 --- @param col ImU32
-- [Luau] scalar PrimRect (a = ax,ay; c = cx,cy): no ImVec2 needed by callers, vertex writes inlined
@native @native
function MT.ImDrawList:PrimRect(a, c, col) local function PrimRectXY(self, ax, ay, cx, cy, col)
local uv = self._Data.TexUvWhitePixel local uv = self._Data.TexUvWhitePixel
local idx = self._VtxCurrentIdx local idx = self._VtxCurrentIdx
local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf local idx_data = self.IdxBuffer.Data; local b = self.VtxBuffer.Buf
local idx_write_ptr = self._IdxWritePtr local idx_write_ptr = self._IdxWritePtr
idx_data[idx_write_ptr + 0] = idx idx_data[idx_write_ptr + 0] = idx
@@ -4176,17 +4282,22 @@ function MT.ImDrawList:PrimRect(a, c, col)
idx_data[idx_write_ptr + 5] = idx + 3 idx_data[idx_write_ptr + 5] = idx + 3
local vtx_write_ptr = self._VtxWritePtr local vtx_write_ptr = self._VtxWritePtr
local ax, ay, cx, cy, u, v = a.x, a.y, c.x, c.y, uv.x, uv.y local u, v = uv.x, uv.y
WV(vtx_data, vtx_write_ptr + 0, ax, ay, u, v, col) local o = (vtx_write_ptr - 1) * 36
WV(vtx_data, vtx_write_ptr + 1, cx, ay, u, v, col) writef64(b, o, ax); writef64(b, o + 8, ay); writef64(b, o + 16, u); writef64(b, o + 24, v); writeu32(b, o + 32, col)
WV(vtx_data, vtx_write_ptr + 2, cx, cy, u, v, col) writef64(b, o + 36, cx); writef64(b, o + 44, ay); writef64(b, o + 52, u); writef64(b, o + 60, v); writeu32(b, o + 68, col)
WV(vtx_data, vtx_write_ptr + 3, ax, cy, u, v, col) writef64(b, o + 72, cx); writef64(b, o + 80, cy); writef64(b, o + 88, u); writef64(b, o + 96, v); writeu32(b, o + 104, col)
writef64(b, o + 108, ax); writef64(b, o + 116, cy); writef64(b, o + 124, u); writef64(b, o + 132, v); writeu32(b, o + 140, col)
self._VtxWritePtr = vtx_write_ptr + 4 self._VtxWritePtr = vtx_write_ptr + 4
self._VtxCurrentIdx = idx + 4 self._VtxCurrentIdx = idx + 4
self._IdxWritePtr = idx_write_ptr + 6 self._IdxWritePtr = idx_write_ptr + 6
end end
function MT.ImDrawList:PrimRect(a, c, col)
PrimRectXY(self, a.x, a.y, c.x, c.y, col)
end
--- @param a ImVec2 --- @param a ImVec2
--- @param c ImVec2 --- @param c ImVec2
--- @param uv_a ImVec2 --- @param uv_a ImVec2
@@ -4256,11 +4367,12 @@ function MT.ImDrawList:AddPolyline(points, points_count, col, thickness, flags)
local idx_count = use_texture and (count * 6) or (thick_line and count * 18 or count * 12) local idx_count = use_texture and (count * 6) or (thick_line and count * 18 or count * 12)
local vtx_count = use_texture and (points_count * 2) or (thick_line and points_count * 4 or points_count * 3) local vtx_count = use_texture and (points_count * 2) or (thick_line and points_count * 4 or points_count * 3)
self:PrimReserve(idx_count, vtx_count) PrimReserve(self, idx_count, vtx_count)
-- Temporary buffer -- Temporary buffer
local temp_buffer_size = points_count * ((use_texture or not thick_line) and 3 or 5) local temp_buffer_size = points_count * ((use_texture or not thick_line) and 3 or 5)
self._Data.TempBuffer:reserve_discard(temp_buffer_size) local temp_buffer = self._Data.TempBuffer
if temp_buffer_size > temp_buffer.Capacity then temp_buffer:reserve_discard(temp_buffer_size) end -- [Luau] skip the method lookup when no growth is needed
local temp_normals = self._Data.TempBuffer.Data local temp_normals = self._Data.TempBuffer.Data
local temp_points = self._Data.TempBuffer.Data; local temp_points_start = points_count + 1 local temp_points = self._Data.TempBuffer.Data; local temp_points_start = points_count + 1
@@ -4296,6 +4408,7 @@ function MT.ImDrawList:AddPolyline(points, points_count, col, thickness, flags)
-- Generate indices and vertices -- Generate indices and vertices
local idx1 = self._VtxCurrentIdx local idx1 = self._VtxCurrentIdx
local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf
local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops
for i1 = 1, count do for i1 = 1, count do
local i2 = (i1 == points_count) and 1 or i1 + 1 local i2 = (i1 == points_count) and 1 or i1 + 1
local idx2 = (i1 == points_count) and self._VtxCurrentIdx or (idx1 + (use_texture and 2 or 3)) local idx2 = (i1 == points_count) and self._VtxCurrentIdx or (idx1 + (use_texture and 2 or 3))
@@ -4316,45 +4429,44 @@ function MT.ImDrawList:AddPolyline(points, points_count, col, thickness, flags)
if use_texture then if use_texture then
-- Add indices for two triangles -- Add indices for two triangles
local idx_write_ptr = self._IdxWritePtr
idx_data[idx_write_ptr + 0] = idx2 + 0; idx_data[idx_write_ptr + 1] = idx1 + 0; idx_data[idx_write_ptr + 2] = idx1 + 1 idx_data[idx_write_ptr + 0] = idx2 + 0; idx_data[idx_write_ptr + 1] = idx1 + 0; idx_data[idx_write_ptr + 2] = idx1 + 1
idx_data[idx_write_ptr + 3] = idx2 + 1; idx_data[idx_write_ptr + 4] = idx1 + 1; idx_data[idx_write_ptr + 5] = idx2 + 0 idx_data[idx_write_ptr + 3] = idx2 + 1; idx_data[idx_write_ptr + 4] = idx1 + 1; idx_data[idx_write_ptr + 5] = idx2 + 0
self._IdxWritePtr = idx_write_ptr + 6 idx_write_ptr = idx_write_ptr + 6
else else
-- Add indices for four triangles -- Add indices for four triangles
local idx_write_ptr = self._IdxWritePtr
idx_data[idx_write_ptr + 0] = idx2 + 0; idx_data[idx_write_ptr + 1] = idx1 + 0; idx_data[idx_write_ptr + 2] = idx1 + 2 idx_data[idx_write_ptr + 0] = idx2 + 0; idx_data[idx_write_ptr + 1] = idx1 + 0; idx_data[idx_write_ptr + 2] = idx1 + 2
idx_data[idx_write_ptr + 3] = idx1 + 2; idx_data[idx_write_ptr + 4] = idx2 + 2; idx_data[idx_write_ptr + 5] = idx2 + 0 idx_data[idx_write_ptr + 3] = idx1 + 2; idx_data[idx_write_ptr + 4] = idx2 + 2; idx_data[idx_write_ptr + 5] = idx2 + 0
idx_data[idx_write_ptr + 6] = idx2 + 1; idx_data[idx_write_ptr + 7] = idx1 + 1; idx_data[idx_write_ptr + 8] = idx1 + 0 idx_data[idx_write_ptr + 6] = idx2 + 1; idx_data[idx_write_ptr + 7] = idx1 + 1; idx_data[idx_write_ptr + 8] = idx1 + 0
idx_data[idx_write_ptr + 9] = idx1 + 0; idx_data[idx_write_ptr + 10] = idx2 + 0; idx_data[idx_write_ptr + 11] = idx2 + 1 idx_data[idx_write_ptr + 9] = idx1 + 0; idx_data[idx_write_ptr + 10] = idx2 + 0; idx_data[idx_write_ptr + 11] = idx2 + 1
self._IdxWritePtr = idx_write_ptr + 12 idx_write_ptr = idx_write_ptr + 12
end end
idx1 = idx2 idx1 = idx2
end end
self._IdxWritePtr = idx_write_ptr
-- Add vertices -- Add vertices
local vtx_write_ptr = self._VtxWritePtr
if use_texture then if use_texture then
-- Texture-based: need to implement TexUvLines lookup -- Texture-based: need to implement TexUvLines lookup
local tex_uvs = self._Data.TexUvLines[integer_thickness] local tex_uvs = self._Data.TexUvLines[integer_thickness]
local tex_uv0 = ImVec2(tex_uvs.x, tex_uvs.y) local tex_uv0 = ImVec2(tex_uvs.x, tex_uvs.y)
local tex_uv1 = ImVec2(tex_uvs.z, tex_uvs.w) local tex_uv1 = ImVec2(tex_uvs.z, tex_uvs.w)
for i = 0, points_count - 1 do for i = 0, points_count - 1 do
local vtx_write_ptr = self._VtxWritePtr
WVP(vtx_data, vtx_write_ptr + 0, temp_points[temp_points_start + i * 2 + 0], tex_uv0, col) WVP(vtx_data, vtx_write_ptr + 0, temp_points[temp_points_start + i * 2 + 0], tex_uv0, col)
WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 2 + 1], tex_uv1, col) WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 2 + 1], tex_uv1, col)
self._VtxWritePtr = vtx_write_ptr + 2 vtx_write_ptr = vtx_write_ptr + 2
end end
else else
-- If we're not using a texture, we need the center vertex as well -- If we're not using a texture, we need the center vertex as well
for i = 0, points_count - 1 do for i = 0, points_count - 1 do
local vtx_write_ptr = self._VtxWritePtr
WVP(vtx_data, vtx_write_ptr + 0, points[i + 1], opaque_uv, col) WVP(vtx_data, vtx_write_ptr + 0, points[i + 1], opaque_uv, col)
WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 2 + 0], opaque_uv, col_trans) WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 2 + 0], opaque_uv, col_trans)
WVP(vtx_data, vtx_write_ptr + 2, temp_points[temp_points_start + i * 2 + 1], opaque_uv, col_trans) WVP(vtx_data, vtx_write_ptr + 2, temp_points[temp_points_start + i * 2 + 1], opaque_uv, col_trans)
self._VtxWritePtr = vtx_write_ptr + 3 vtx_write_ptr = vtx_write_ptr + 3
end end
end end
self._VtxWritePtr = vtx_write_ptr
else else
-- [PATH 3] Non texture-based lines (thick) -- [PATH 3] Non texture-based lines (thick)
local half_inner_thickness = (thickness - AA_SIZE) * 0.5 local half_inner_thickness = (thickness - AA_SIZE) * 0.5
@@ -4375,6 +4487,7 @@ function MT.ImDrawList:AddPolyline(points, points_count, col, thickness, flags)
-- Generate indices and vertices -- Generate indices and vertices
local idx1 = self._VtxCurrentIdx local idx1 = self._VtxCurrentIdx
local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf
local idx_write_ptr = self._IdxWritePtr -- [Luau] write pointers kept in locals, stored back after the loops
for i1 = 1, count do for i1 = 1, count do
local i2 = (i1 == points_count) and 1 or i1 + 1 local i2 = (i1 == points_count) and 1 or i1 + 1
local idx2 = (i1 == points_count) and self._VtxCurrentIdx or (idx1 + 4) local idx2 = (i1 == points_count) and self._VtxCurrentIdx or (idx1 + 4)
@@ -4400,34 +4513,35 @@ function MT.ImDrawList:AddPolyline(points, points_count, col, thickness, flags)
temp_points[temp_points_start + out_off + 3].y = points[i2].y - dm_out_y temp_points[temp_points_start + out_off + 3].y = points[i2].y - dm_out_y
-- Add indices -- Add indices
local idx_write_ptr = self._IdxWritePtr
idx_data[idx_write_ptr + 0] = idx2 + 1; idx_data[idx_write_ptr + 1] = idx1 + 1; idx_data[idx_write_ptr + 2] = idx1 + 2 idx_data[idx_write_ptr + 0] = idx2 + 1; idx_data[idx_write_ptr + 1] = idx1 + 1; idx_data[idx_write_ptr + 2] = idx1 + 2
idx_data[idx_write_ptr + 3] = idx1 + 2; idx_data[idx_write_ptr + 4] = idx2 + 2; idx_data[idx_write_ptr + 5] = idx2 + 1 idx_data[idx_write_ptr + 3] = idx1 + 2; idx_data[idx_write_ptr + 4] = idx2 + 2; idx_data[idx_write_ptr + 5] = idx2 + 1
idx_data[idx_write_ptr + 6] = idx2 + 1; idx_data[idx_write_ptr + 7] = idx1 + 1; idx_data[idx_write_ptr + 8] = idx1 + 0 idx_data[idx_write_ptr + 6] = idx2 + 1; idx_data[idx_write_ptr + 7] = idx1 + 1; idx_data[idx_write_ptr + 8] = idx1 + 0
idx_data[idx_write_ptr + 9] = idx1 + 0; idx_data[idx_write_ptr + 10] = idx2 + 0; idx_data[idx_write_ptr + 11] = idx2 + 1 idx_data[idx_write_ptr + 9] = idx1 + 0; idx_data[idx_write_ptr + 10] = idx2 + 0; idx_data[idx_write_ptr + 11] = idx2 + 1
idx_data[idx_write_ptr + 12] = idx2 + 2; idx_data[idx_write_ptr + 13] = idx1 + 2; idx_data[idx_write_ptr + 14] = idx1 + 3 idx_data[idx_write_ptr + 12] = idx2 + 2; idx_data[idx_write_ptr + 13] = idx1 + 2; idx_data[idx_write_ptr + 14] = idx1 + 3
idx_data[idx_write_ptr + 15] = idx1 + 3; idx_data[idx_write_ptr + 16] = idx2 + 3; idx_data[idx_write_ptr + 17] = idx2 + 2 idx_data[idx_write_ptr + 15] = idx1 + 3; idx_data[idx_write_ptr + 16] = idx2 + 3; idx_data[idx_write_ptr + 17] = idx2 + 2
self._IdxWritePtr = idx_write_ptr + 18 idx_write_ptr = idx_write_ptr + 18
idx1 = idx2 idx1 = idx2
end end
self._IdxWritePtr = idx_write_ptr
-- Add vertices -- Add vertices
local vtx_write_ptr = self._VtxWritePtr
for i = 0, points_count - 1 do for i = 0, points_count - 1 do
local vtx_write_ptr = self._VtxWritePtr
WVP(vtx_data, vtx_write_ptr + 0, temp_points[temp_points_start + i * 4 + 0], opaque_uv, col_trans) WVP(vtx_data, vtx_write_ptr + 0, temp_points[temp_points_start + i * 4 + 0], opaque_uv, col_trans)
WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 4 + 1], opaque_uv, col) WVP(vtx_data, vtx_write_ptr + 1, temp_points[temp_points_start + i * 4 + 1], opaque_uv, col)
WVP(vtx_data, vtx_write_ptr + 2, temp_points[temp_points_start + i * 4 + 2], opaque_uv, col) WVP(vtx_data, vtx_write_ptr + 2, temp_points[temp_points_start + i * 4 + 2], opaque_uv, col)
WVP(vtx_data, vtx_write_ptr + 3, temp_points[temp_points_start + i * 4 + 3], opaque_uv, col_trans) WVP(vtx_data, vtx_write_ptr + 3, temp_points[temp_points_start + i * 4 + 3], opaque_uv, col_trans)
self._VtxWritePtr = vtx_write_ptr + 4 vtx_write_ptr = vtx_write_ptr + 4
end end
self._VtxWritePtr = vtx_write_ptr
end end
self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count
else else
-- [PATH 4] Non texture-based, Non anti-aliased lines -- [PATH 4] Non texture-based, Non anti-aliased lines
local idx_count = count * 6 local idx_count = count * 6
local vtx_count = count * 4 local vtx_count = count * 4
self:PrimReserve(idx_count, vtx_count) PrimReserve(self, idx_count, vtx_count)
local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf local idx_data = self.IdxBuffer.Data; local vtx_data = self.VtxBuffer.Buf
for i1 = 1, count do for i1 = 1, count do
@@ -4457,7 +4571,20 @@ function MT.ImDrawList:AddPolyline(points, points_count, col, thickness, flags)
end end
end end
function MT.ImDrawList:PathRect(a, b, rounding, flags) -- [Luau] scalar PathLineTo(): no temporary ImVec2 (the path slot is written in place)
local function PathLineToXY(self, x, y)
local path = self._Path
local n = path.Size + 1
if n > path.Capacity then path:push_back(ImVec2(x, y)) return end
local dest = path.Data[n]
dest.x = x; dest.y = y
path.Size = n
end
local _PathArcToFastExXY -- [Luau] forward declaration, defined below with _PathArcToFastEx()
-- [Luau] scalar PathRect (a = ax,ay; b = bx,by); PathRect() below wraps it
local function PathRectXY(self, ax, ay, bx, by, rounding, flags)
if not rounding then rounding = 0.0 end if not rounding then rounding = 0.0 end
if not flags then flags = 0 end if not flags then flags = 0 end
@@ -4466,26 +4593,31 @@ function MT.ImDrawList:PathRect(a, b, rounding, flags)
if bit32.band(flags, ImDrawFlags.RoundCornersMask_) == 0 then if bit32.band(flags, ImDrawFlags.RoundCornersMask_) == 0 then
flags = bit32.bor(flags, ImDrawFlags.RoundCornersAll) flags = bit32.bor(flags, ImDrawFlags.RoundCornersAll)
end end
rounding = ImMin(rounding, ImAbs(b.x - a.x) * (((bit32.band(flags, ImDrawFlags.RoundCornersTop) == ImDrawFlags.RoundCornersTop) or (bit32.band(flags, ImDrawFlags.RoundCornersBottom) == ImDrawFlags.RoundCornersBottom)) and 0.5 or 1.0) - 1.0) rounding = ImMin(rounding, ImAbs(bx - ax) * (((bit32.band(flags, ImDrawFlags.RoundCornersTop) == ImDrawFlags.RoundCornersTop) or (bit32.band(flags, ImDrawFlags.RoundCornersBottom) == ImDrawFlags.RoundCornersBottom)) and 0.5 or 1.0) - 1.0)
rounding = ImMin(rounding, ImAbs(b.y - a.y) * (((bit32.band(flags, ImDrawFlags.RoundCornersLeft) == ImDrawFlags.RoundCornersLeft) or (bit32.band(flags, ImDrawFlags.RoundCornersRight) == ImDrawFlags.RoundCornersRight)) and 0.5 or 1.0) - 1.0) rounding = ImMin(rounding, ImAbs(by - ay) * (((bit32.band(flags, ImDrawFlags.RoundCornersLeft) == ImDrawFlags.RoundCornersLeft) or (bit32.band(flags, ImDrawFlags.RoundCornersRight) == ImDrawFlags.RoundCornersRight)) and 0.5 or 1.0) - 1.0)
end end
if rounding < 0.5 or (bit32.band(flags, ImDrawFlags.RoundCornersMask_) == ImDrawFlags.RoundCornersNone) then if rounding < 0.5 or (bit32.band(flags, ImDrawFlags.RoundCornersMask_) == ImDrawFlags.RoundCornersNone) then
self:PathLineTo(a) PathLineToXY(self, ax, ay)
self:PathLineTo(ImVec2(b.x, a.y)) PathLineToXY(self, bx, ay)
self:PathLineTo(b) PathLineToXY(self, bx, by)
self:PathLineTo(ImVec2(a.x, b.y)) PathLineToXY(self, ax, by)
else else
local rounding_tl = (bit32.band(flags, ImDrawFlags.RoundCornersTopLeft) ~= 0) and rounding or 0.0 local rounding_tl = (bit32.band(flags, ImDrawFlags.RoundCornersTopLeft) ~= 0) and rounding or 0.0
local rounding_tr = (bit32.band(flags, ImDrawFlags.RoundCornersTopRight) ~= 0) and rounding or 0.0 local rounding_tr = (bit32.band(flags, ImDrawFlags.RoundCornersTopRight) ~= 0) and rounding or 0.0
local rounding_br = (bit32.band(flags, ImDrawFlags.RoundCornersBottomRight) ~= 0) and rounding or 0.0 local rounding_br = (bit32.band(flags, ImDrawFlags.RoundCornersBottomRight) ~= 0) and rounding or 0.0
local rounding_bl = (bit32.band(flags, ImDrawFlags.RoundCornersBottomLeft) ~= 0) and rounding or 0.0 local rounding_bl = (bit32.band(flags, ImDrawFlags.RoundCornersBottomLeft) ~= 0) and rounding or 0.0
self:PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9) -- [Luau] inline PathArcToFast() (radius < 0.5 handled by _PathArcToFastExXY())
self:PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12) _PathArcToFastExXY(self, ax + rounding_tl, ay + rounding_tl, rounding_tl, 6 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 9 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0)
self:PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3) _PathArcToFastExXY(self, bx - rounding_tr, ay + rounding_tr, rounding_tr, 9 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0)
self:PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6) _PathArcToFastExXY(self, bx - rounding_br, by - rounding_br, rounding_br, 0 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 3 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0)
_PathArcToFastExXY(self, ax + rounding_bl, by - rounding_bl, rounding_bl, 3 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 6 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0)
end end
end end
function MT.ImDrawList:PathRect(a, b, rounding, flags)
PathRectXY(self, a.x, a.y, b.x, b.y, rounding, flags)
end
@native @native
function MT.ImDrawList:AddRectFilled(p_min, p_max, col, rounding, flags) function MT.ImDrawList:AddRectFilled(p_min, p_max, col, rounding, flags)
if not rounding then rounding = 0.0 end if not rounding then rounding = 0.0 end
@@ -4494,10 +4626,10 @@ function MT.ImDrawList:AddRectFilled(p_min, p_max, col, rounding, flags)
if bit32.band(col, IM_COL32_A_MASK) == 0 then return end if bit32.band(col, IM_COL32_A_MASK) == 0 then return end
if rounding < 0.5 or (bit32.band(flags, ImDrawFlags.RoundCornersMask_) == ImDrawFlags.RoundCornersNone) then if rounding < 0.5 or (bit32.band(flags, ImDrawFlags.RoundCornersMask_) == ImDrawFlags.RoundCornersNone) then
self:PrimReserve(6, 4) PrimReserve(self, 6, 4) -- [Luau] local calls
self:PrimRect(p_min, p_max, col) PrimRectXY(self, p_min.x, p_min.y, p_max.x, p_max.y, col)
else else
self:PathRect(p_min, p_max, rounding, flags) PathRectXY(self, p_min.x, p_min.y, p_max.x, p_max.y, rounding, flags)
self:PathFillConvex(col) self:PathFillConvex(col)
end end
end end
@@ -4516,13 +4648,19 @@ function MT.ImDrawList:AddRectFilledMultiColor(p_min, p_max, col_upr_left, col_u
end end
local uv = self._Data.TexUvWhitePixel local uv = self._Data.TexUvWhitePixel
self:PrimReserve(6, 4) PrimReserve(self, 6, 4)
self:PrimWriteIdx(self._VtxCurrentIdx); self:PrimWriteIdx(self._VtxCurrentIdx + 1); self:PrimWriteIdx(self._VtxCurrentIdx + 2) -- [Luau] inline PrimWriteIdx()/PrimWriteVtx(): direct writes, no temporary ImVec2
self:PrimWriteIdx(self._VtxCurrentIdx); self:PrimWriteIdx(self._VtxCurrentIdx + 2); self:PrimWriteIdx(self._VtxCurrentIdx + 3) local idx, idx_data, idx_write_ptr = self._VtxCurrentIdx, self.IdxBuffer.Data, self._IdxWritePtr
self:PrimWriteVtx(p_min, uv, col_upr_left) idx_data[idx_write_ptr] = idx; idx_data[idx_write_ptr + 1] = idx + 1; idx_data[idx_write_ptr + 2] = idx + 2
self:PrimWriteVtx(ImVec2(p_max.x, p_min.y), uv, col_upr_right) idx_data[idx_write_ptr + 3] = idx; idx_data[idx_write_ptr + 4] = idx + 2; idx_data[idx_write_ptr + 5] = idx + 3
self:PrimWriteVtx(p_max, uv, col_bot_right) self._IdxWritePtr = idx_write_ptr + 6
self:PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv, col_bot_left) local vtx_data, vtx_write_ptr = self.VtxBuffer.Buf, self._VtxWritePtr
WV(vtx_data, vtx_write_ptr + 0, p_min.x, p_min.y, uv.x, uv.y, col_upr_left)
WV(vtx_data, vtx_write_ptr + 1, p_max.x, p_min.y, uv.x, uv.y, col_upr_right)
WV(vtx_data, vtx_write_ptr + 2, p_max.x, p_max.y, uv.x, uv.y, col_bot_right)
WV(vtx_data, vtx_write_ptr + 3, p_min.x, p_max.y, uv.x, uv.y, col_bot_left)
self._VtxWritePtr = vtx_write_ptr + 4
self._VtxCurrentIdx = idx + 4
end end
-- p_min = upper-left, p_max = lower-right -- p_min = upper-left, p_max = lower-right
@@ -4555,17 +4693,17 @@ function MT.ImDrawList:AddRect(p_min, p_max, col, rounding, thickness, flags)
if (rounding == nil or rounding < 0.5 or bit32.band(flags, ImDrawFlags.RoundCornersMask_) == ImDrawFlags.RoundCornersNone) if (rounding == nil or rounding < 0.5 or bit32.band(flags, ImDrawFlags.RoundCornersMask_) == ImDrawFlags.RoundCornersNone)
and thickness >= 1.0 and thickness % 1 == 0 and p_max.x - p_min.x > 2 * thickness and p_max.y - p_min.y > 2 * thickness then and thickness >= 1.0 and thickness % 1 == 0 and p_max.x - p_min.x > 2 * thickness and p_max.y - p_min.y > 2 * thickness then
local x0, y0, x1, y1, t = p_min.x, p_min.y, p_max.x, p_max.y, thickness local x0, y0, x1, y1, t = p_min.x, p_min.y, p_max.x, p_max.y, thickness
self:PrimReserve(24, 16) PrimReserve(self, 24, 16)
self:PrimRect(ImVec2(x0, y0), ImVec2(x1, y0 + t), col) PrimRectXY(self, x0, y0, x1, y0 + t, col)
self:PrimRect(ImVec2(x0, y1 - t), ImVec2(x1, y1), col) PrimRectXY(self, x0, y1 - t, x1, y1, col)
self:PrimRect(ImVec2(x0, y0 + t), ImVec2(x0 + t, y1 - t), col) PrimRectXY(self, x0, y0 + t, x0 + t, y1 - t, col)
self:PrimRect(ImVec2(x1 - t, y0 + t), ImVec2(x1, y1 - t), col) PrimRectXY(self, x1 - t, y0 + t, x1, y1 - t, col)
return return
end end
if bit32.band(self.Flags, ImDrawListFlags.AntiAliasedLines) ~= 0 then if bit32.band(self.Flags, ImDrawListFlags.AntiAliasedLines) ~= 0 then
self:PathRect(p_min + ImVec2(0.50, 0.50), p_max - ImVec2(0.50, 0.50), rounding, flags) PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.50, p_max.y - 0.50, rounding, flags) -- [Luau] scalar math, no temporary ImVec2
else else
self:PathRect(p_min + ImVec2(0.50, 0.50), p_max - ImVec2(0.49, 0.49), rounding, flags) -- Better looking lower-right corner and rounded non-AA shapes. PathRectXY(self, p_min.x + 0.50, p_min.y + 0.50, p_max.x - 0.49, p_max.y - 0.49, rounding, flags) -- Better looking lower-right corner and rounded non-AA shapes.
end end
self:PathStroke(col, thickness, ImDrawFlags.Closed) self:PathStroke(col, thickness, ImDrawFlags.Closed)
@@ -4574,8 +4712,8 @@ end
function MT.ImDrawList:AddLine(p1, p2, col, thickness) function MT.ImDrawList:AddLine(p1, p2, col, thickness)
if bit32.band(col, IM_COL32_A_MASK) == 0 then return end if bit32.band(col, IM_COL32_A_MASK) == 0 then return end
self:PathLineTo(p1 + ImVec2(0.5, 0.5)) PathLineToXY(self, p1.x + 0.5, p1.y + 0.5) -- [Luau] scalar math, no temporary ImVec2
self:PathLineTo(p2 + ImVec2(0.5, 0.5)) PathLineToXY(self, p2.x + 0.5, p2.y + 0.5)
self:PathStroke(col, thickness) self:PathStroke(col, thickness)
end end
@@ -4590,8 +4728,8 @@ function MT.ImDrawList:AddLineH(min_x, max_x, y, col, thickness)
if bit32.band(col, IM_COL32_A_MASK) == 0 then if bit32.band(col, IM_COL32_A_MASK) == 0 then
return return
end end
self:PathLineTo(ImVec2(min_x + 0.5, y + 0.5)) PathLineToXY(self, min_x + 0.5, y + 0.5)
self:PathLineTo(ImVec2(max_x + 0.5, y + 0.5)) PathLineToXY(self, max_x + 0.5, y + 0.5)
self:PathStroke(col, thickness) self:PathStroke(col, thickness)
end end
@@ -4606,8 +4744,8 @@ function MT.ImDrawList:AddLineV(x, min_y, max_y, col, thickness)
if bit32.band(col, IM_COL32_A_MASK) == 0 then if bit32.band(col, IM_COL32_A_MASK) == 0 then
return return
end end
self:PathLineTo(ImVec2(x + 0.5, min_y + 0.5)) PathLineToXY(self, x + 0.5, min_y + 0.5)
self:PathLineTo(ImVec2(x + 0.5, max_y + 0.5)) PathLineToXY(self, x + 0.5, max_y + 0.5)
self:PathStroke(col, thickness) self:PathStroke(col, thickness)
end end
@@ -4667,7 +4805,7 @@ function MT.ImDrawList:AddCircle(center, radius, col, num_segments, thickness)
if num_segments <= 0 then if num_segments <= 0 then
-- Use arc with automatic segment count -- Use arc with automatic segment count
self:_PathArcToFastEx(center, radius - 0.5, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0) _PathArcToFastExXY(self, center.x, center.y, radius - 0.5, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0)
self._Path.Size = self._Path.Size - 1 self._Path.Size = self._Path.Size - 1
else else
-- Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) -- Explicit segment count (still clamp to avoid drawing insanely tessellated shapes)
@@ -4694,7 +4832,7 @@ function MT.ImDrawList:AddCircleFilled(center, radius, col, num_segments)
if num_segments <= 0 then if num_segments <= 0 then
-- Use arc with automatic segment count -- Use arc with automatic segment count
self:_PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0) _PathArcToFastExXY(self, center.x, center.y, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0)
self._Path.Size = self._Path.Size - 1 self._Path.Size = self._Path.Size - 1
else else
-- Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) -- Explicit segment count (still clamp to avoid drawing insanely tessellated shapes)
@@ -4717,6 +4855,8 @@ end
--- @param text_end int --- @param text_end int
--- @param wrap_width float --- @param wrap_width float
--- @param cpu_fine_clip_rect? ImVec4 --- @param cpu_fine_clip_rect? ImVec4
local AddText_clip_rect = ImVec4()
@native @native
function MT.ImDrawList:AddText(font, font_size, pos, col, text, text_begin, text_end, wrap_width, cpu_fine_clip_rect) function MT.ImDrawList:AddText(font, font_size, pos, col, text, text_begin, text_end, wrap_width, cpu_fine_clip_rect)
-- Overload: AddText(pos, col, text, text_end?) -- Overload: AddText(pos, col, text, text_end?)
@@ -4742,13 +4882,14 @@ function MT.ImDrawList:AddText(font, font_size, pos, col, text, text_begin, text
font_size = self._Data.FontSize font_size = self._Data.FontSize
end end
local clip_rect = ImVec4() -- Don't modify the clip rect! local clip_rect = AddText_clip_rect -- Don't modify the clip rect! -- [Luau] scratch: ImFont:RenderText() only reads it
ImVec4_Copy(clip_rect, self._CmdHeader.ClipRect) local src = self._CmdHeader.ClipRect
clip_rect.x = src.x; clip_rect.y = src.y; clip_rect.z = src.z; clip_rect.w = src.w
if (cpu_fine_clip_rect) then if (cpu_fine_clip_rect) then
clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect.x) clip_rect.x = math.max(clip_rect.x, cpu_fine_clip_rect.x)
clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect.y) clip_rect.y = math.max(clip_rect.y, cpu_fine_clip_rect.y)
clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect.z) clip_rect.z = math.min(clip_rect.z, cpu_fine_clip_rect.z)
clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect.w) clip_rect.w = math.min(clip_rect.w, cpu_fine_clip_rect.w)
end end
font:RenderText(self, font_size, pos, col, clip_rect, text, text_begin, text_end, wrap_width, (cpu_fine_clip_rect ~= nil) and ImDrawTextFlags.CpuFineClip or ImDrawTextFlags.None) font:RenderText(self, font_size, pos, col, clip_rect, text, text_begin, text_end, wrap_width, (cpu_fine_clip_rect ~= nil) and ImDrawTextFlags.CpuFineClip or ImDrawTextFlags.None)
@@ -4846,20 +4987,25 @@ function MT.ImDrawList:PushClipRect(cr_min, cr_max, intersect_with_current_clip_
if cr.w > current.w then cr.w = current.w end if cr.w > current.w then cr.w = current.w end
end end
cr.z = ImMax(cr.x, cr.z) cr.z = math.max(cr.x, cr.z)
cr.w = ImMax(cr.y, cr.w) cr.w = math.max(cr.y, cr.w)
self._ClipRectStack:push_back(cr) -- [Luau] inline self._ClipRectStack:push_back(cr)
local stack = self._ClipRectStack
local n = stack.Size + 1
if n > stack.Capacity then stack:push_back(cr) else stack.Data[n] = cr; stack.Size = n end
ImVec4_Copy(self._CmdHeader.ClipRect, cr) ImVec4_Copy(self._CmdHeader.ClipRect, cr)
self:_OnChangedClipRect() self:_OnChangedClipRect()
end end
function MT.ImDrawList:PopClipRect() function MT.ImDrawList:PopClipRect()
self._ClipRectStack:pop_back() local stack = self._ClipRectStack
if (self._ClipRectStack.Size == 0) then -- LUA: No "Ternary Operator" IM_ASSERT(stack.Size > 0)
stack.Size = stack.Size - 1 -- [Luau] inline pop_back()
if (stack.Size == 0) then -- LUA: No "Ternary Operator"
ImVec4_Copy(self._CmdHeader.ClipRect, self._Data.ClipRectFullscreen) ImVec4_Copy(self._CmdHeader.ClipRect, self._Data.ClipRectFullscreen)
else else
ImVec4_Copy(self._CmdHeader.ClipRect, self._ClipRectStack.Data[self._ClipRectStack.Size]) ImVec4_Copy(self._CmdHeader.ClipRect, stack.Data[stack.Size])
end end
self:_OnChangedClipRect() self:_OnChangedClipRect()
end end
@@ -4869,10 +5015,11 @@ end
--- @param a_min_sample int --- @param a_min_sample int
--- @param a_max_sample int --- @param a_max_sample int
--- @param a_step int --- @param a_step int
-- [Luau] scalar center (cx, cy); assigned to the forward-declared local, _PathArcToFastEx() wraps it
@native @native
function MT.ImDrawList:_PathArcToFastEx(center, radius, a_min_sample, a_max_sample, a_step) function _PathArcToFastExXY(self, cx, cy, radius, a_min_sample, a_max_sample, a_step)
if radius < 0.5 then if radius < 0.5 then
self._Path:push_back(center) PathLineToXY(self, cx, cy)
return return
end end
@@ -4908,6 +5055,7 @@ function MT.ImDrawList:_PathArcToFastEx(center, radius, a_min_sample, a_max_samp
self._Path:resize(self._Path.Size + samples) self._Path:resize(self._Path.Size + samples)
local out_ptr = self._Path.Size - samples + 1 local out_ptr = self._Path.Size - samples + 1
local path_data, arc_fast_vtx = self._Path.Data, self._Data.ArcFastVtx -- [Luau] cache
local sample_index = a_min_sample local sample_index = a_min_sample
if sample_index < 0 or sample_index >= IM_DRAWLIST_ARCFAST_SAMPLE_MAX then if sample_index < 0 or sample_index >= IM_DRAWLIST_ARCFAST_SAMPLE_MAX then
sample_index = sample_index % IM_DRAWLIST_ARCFAST_SAMPLE_MAX sample_index = sample_index % IM_DRAWLIST_ARCFAST_SAMPLE_MAX
@@ -4924,8 +5072,9 @@ function MT.ImDrawList:_PathArcToFastEx(center, radius, a_min_sample, a_max_samp
sample_index = sample_index - IM_DRAWLIST_ARCFAST_SAMPLE_MAX sample_index = sample_index - IM_DRAWLIST_ARCFAST_SAMPLE_MAX
end end
local s = self._Data.ArcFastVtx[sample_index + 1] local s = arc_fast_vtx[sample_index + 1]
ImVec2_CopyV(self._Path.Data[out_ptr], center.x + s.x * radius, center.y + s.y * radius) local d = path_data[out_ptr]
d.x = cx + s.x * radius; d.y = cy + s.y * radius
out_ptr = out_ptr + 1 out_ptr = out_ptr + 1
a = a + a_step a = a + a_step
@@ -4940,8 +5089,9 @@ function MT.ImDrawList:_PathArcToFastEx(center, radius, a_min_sample, a_max_samp
sample_index = sample_index + IM_DRAWLIST_ARCFAST_SAMPLE_MAX sample_index = sample_index + IM_DRAWLIST_ARCFAST_SAMPLE_MAX
end end
local s = self._Data.ArcFastVtx[sample_index + 1] local s = arc_fast_vtx[sample_index + 1]
ImVec2_CopyV(self._Path.Data[out_ptr], center.x + s.x * radius, center.y + s.y * radius) local d = path_data[out_ptr]
d.x = cx + s.x * radius; d.y = cy + s.y * radius
out_ptr = out_ptr + 1 out_ptr = out_ptr + 1
a = a - a_step a = a - a_step
@@ -4956,14 +5106,19 @@ function MT.ImDrawList:_PathArcToFastEx(center, radius, a_min_sample, a_max_samp
normalized_max_sample = normalized_max_sample + IM_DRAWLIST_ARCFAST_SAMPLE_MAX normalized_max_sample = normalized_max_sample + IM_DRAWLIST_ARCFAST_SAMPLE_MAX
end end
local s = self._Data.ArcFastVtx[normalized_max_sample + 1] local s = arc_fast_vtx[normalized_max_sample + 1]
ImVec2_CopyV(self._Path.Data[out_ptr], center.x + s.x * radius, center.y + s.y * radius) local d = path_data[out_ptr]
d.x = cx + s.x * radius; d.y = cy + s.y * radius
out_ptr = out_ptr + 1 out_ptr = out_ptr + 1
end end
IM_ASSERT_PARANOID(self._Path.Size == out_ptr - 1) IM_ASSERT_PARANOID(self._Path.Size == out_ptr - 1)
end end
function MT.ImDrawList:_PathArcToFastEx(center, radius, a_min_sample, a_max_sample, a_step)
_PathArcToFastExXY(self, center.x, center.y, radius, a_min_sample, a_max_sample, a_step)
end
-- 0: East, 3: South, 6: West, 9: North, 12: East -- 0: East, 3: South, 6: West, 9: North, 12: East
@native @native
function MT.ImDrawList:PathArcToFast(center, radius, a_min_of_12, a_max_of_12) function MT.ImDrawList:PathArcToFast(center, radius, a_min_of_12, a_max_of_12)
@@ -4972,7 +5127,7 @@ function MT.ImDrawList:PathArcToFast(center, radius, a_min_of_12, a_max_of_12)
return return
end end
self:_PathArcToFastEx(center, radius, a_min_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, a_max_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0) _PathArcToFastExXY(self, center.x, center.y, radius, a_min_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, a_max_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0)
end end
--- @param center ImVec2 --- @param center ImVec2
@@ -4988,11 +5143,17 @@ function MT.ImDrawList:_PathArcToN(center, radius, a_min, a_max, num_segments)
-- Note that we are adding a point at both a_min and a_max. -- Note that we are adding a point at both a_min and a_max.
-- If you are trying to draw a full closed circle you don't want the overlapping points! -- If you are trying to draw a full closed circle you don't want the overlapping points!
self._Path:reserve(self._Path.Size + (num_segments + 1)) local path = self._Path
path:reserve(path.Size + (num_segments + 1))
-- [Luau] write into the reserved ImVec2 slots: no temporary ImVec2, no push_back() method lookups
local data, size = path.Data, path.Size
for i = 0, num_segments do for i = 0, num_segments do
local a = a_min + (i / num_segments) * (a_max - a_min) local a = a_min + (i / num_segments) * (a_max - a_min)
self._Path:push_back(ImVec2(center.x + ImCos(a) * radius, center.y + ImSin(a) * radius)) size = size + 1
local p = data[size]
p.x = center.x + ImCos(a) * radius; p.y = center.y + ImSin(a) * radius
end end
path.Size = size
end end
--- @param center ImVec2 --- @param center ImVec2
@@ -5033,9 +5194,9 @@ function MT.ImDrawList:PathArcTo(center, radius, a_min, a_max, num_segments)
self._Path:reserve(self._Path.Size + (a_mid_samples + 1 + (a_emit_start and 1 or 0) + (a_emit_end and 1 or 0))) self._Path:reserve(self._Path.Size + (a_mid_samples + 1 + (a_emit_start and 1 or 0) + (a_emit_end and 1 or 0)))
if a_emit_start then self._Path:push_back(ImVec2(center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius)) end if a_emit_start then PathLineToXY(self, center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius) end -- [Luau] no temporary ImVec2
if a_mid_samples > 0 then self:_PathArcToFastEx(center, radius, a_min_sample, a_max_sample, 0) end if a_mid_samples > 0 then _PathArcToFastExXY(self, center.x, center.y, radius, a_min_sample, a_max_sample, 0) end
if a_emit_end then self._Path:push_back(ImVec2(center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius)) end if a_emit_end then PathLineToXY(self, center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius) end
else else
local arc_length = ImAbs(a_max - a_min) local arc_length = ImAbs(a_max - a_min)
local circle_segment_count = self:_CalcCircleAutoSegmentCount(radius) local circle_segment_count = self:_CalcCircleAutoSegmentCount(radius)
@@ -5146,6 +5307,7 @@ function MT.ImFont:RenderText(draw_list, size, pos, col, clip_rect, text, text_b
-- Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. -- Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature.
if not word_wrap_eol then if not word_wrap_eol then
word_wrap_eol = ImFontCalcWordWrapPositionEx(self, size, text, s, text_end, wrap_width - (x - origin_x), flags) word_wrap_eol = ImFontCalcWordWrapPositionEx(self, size, text, s, text_end, wrap_width - (x - origin_x), flags)
lookup, lookup_n, glyphs = baked.IndexLookup.Data, baked.IndexLookup.Size, baked.Glyphs.Data -- [Luau] may have loaded glyphs
end end
if s >= word_wrap_eol then if s >= word_wrap_eol then
@@ -5345,18 +5507,24 @@ end
--- @param pos ImVec2 --- @param pos ImVec2
--- @param col ImU32 --- @param col ImU32
--- @param sz float --- @param sz float
local checkmark_p = ImVec2() -- [Luau] scratch: PathLineTo() copies the point
function ImGui.RenderCheckMark(draw_list, pos, col, sz) function ImGui.RenderCheckMark(draw_list, pos, col, sz)
local thickness = ImMax(sz / 5.0, 1.0) local thickness = math.max(sz / 5.0, 1.0)
sz = sz - thickness * 0.5 sz = sz - thickness * 0.5
pos = pos + ImVec2(thickness * 0.25, thickness * 0.25) local pos_x, pos_y = pos.x + thickness * 0.25, pos.y + thickness * 0.25 -- [Luau] scalar pos
local third = sz / 3.0 local third = sz / 3.0
local bx = pos.x + third local bx = pos_x + third
local by = pos.y + sz - third * 0.5 local by = pos_y + sz - third * 0.5
draw_list:PathLineTo(ImVec2(bx - third, by - third)) local p = checkmark_p
draw_list:PathLineTo(ImVec2(bx, by)) p.x = bx - third; p.y = by - third
draw_list:PathLineTo(ImVec2(bx + third * 2.0, by - third * 2.0)) draw_list:PathLineTo(p)
p.x = bx; p.y = by
draw_list:PathLineTo(p)
p.x = bx + third * 2.0; p.y = by - third * 2.0
draw_list:PathLineTo(p)
draw_list:PathStroke(col, thickness) draw_list:PathStroke(col, thickness)
end end
+3 -2
View File
@@ -30,7 +30,7 @@
--- @meta --- @meta
local type = ImGui._GetTypeFunc() local type = type -- [Luau] builtin (FASTCALL); ImGui._GetTypeFunc() kept for compatibility
local rawget = rawget; local rawset = rawset local rawget = rawget; local rawset = rawset
-- [LuaBitOp](https://bitop.luajit.org/semantics.html) -- [LuaBitOp](https://bitop.luajit.org/semantics.html)
@@ -2130,7 +2130,8 @@ IM_COL32_A_MASK = 0xFF000000
--- @param G ImU32 --- @param G ImU32
--- @param B ImU32 --- @param B ImU32
--- @param A ImU32 --- @param A ImU32
IM_COL32 = function(R, G, B, A) return (bitOr(bitLShift(A, IM_COL32_A_SHIFT), bitLShift(B, IM_COL32_B_SHIFT), bitLShift(G, IM_COL32_G_SHIFT), bitLShift(R, IM_COL32_R_SHIFT))) end --@hoist
function IM_COL32(R, G, B, A) return (bitOr(bitLShift(A, IM_COL32_A_SHIFT), bitLShift(B, IM_COL32_B_SHIFT), bitLShift(G, IM_COL32_G_SHIFT), bitLShift(R, IM_COL32_R_SHIFT))) end
IM_COL32_WHITE = IM_COL32(255, 255, 255, 255) IM_COL32_WHITE = IM_COL32(255, 255, 255, 255)
IM_COL32_BLACK = IM_COL32(0, 0, 0, 255) IM_COL32_BLACK = IM_COL32(0, 0, 0, 255)
IM_COL32_BLACK_TRANS = IM_COL32(0, 0, 0, 0) IM_COL32_BLACK_TRANS = IM_COL32(0, 0, 0, 0)
+27 -4
View File
@@ -30,7 +30,7 @@
--- @meta --- @meta
local type = ImGui._GetTypeFunc() local type = type -- [Luau] builtin (FASTCALL); ImGui._GetTypeFunc() kept for compatibility
local rawget = rawget local rawget = rawget
local mathMin = math.min; local mathMax = math.max local mathMin = math.min; local mathMax = math.max
@@ -189,6 +189,7 @@ end
-- - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support variety of types: signed/unsigned int/long long float/double -- - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support variety of types: signed/unsigned int/long long float/double
--- @overload fun(lhs: number, rhs: number): number --- @overload fun(lhs: number, rhs: number): number
--- @overload fun(lhs: ImVec2, rhs: ImVec2): ImVec2 --- @overload fun(lhs: ImVec2, rhs: ImVec2): ImVec2
--@hoist
@native @native
function ImMin(lhs, rhs) function ImMin(lhs, rhs)
if type(lhs) == "number" then return mathMin(lhs, rhs) end if type(lhs) == "number" then return mathMin(lhs, rhs) end
@@ -197,6 +198,7 @@ end
--- @overload fun(lhs: number, rhs: number): number --- @overload fun(lhs: number, rhs: number): number
--- @overload fun(lhs: ImVec2, rhs: ImVec2): ImVec2 --- @overload fun(lhs: ImVec2, rhs: ImVec2): ImVec2
--@hoist
@native @native
function ImMax(lhs, rhs) function ImMax(lhs, rhs)
if type(lhs) == "number" then return mathMax(lhs, rhs) end if type(lhs) == "number" then return mathMax(lhs, rhs) end
@@ -211,6 +213,7 @@ ImStd.ImQsort = function(base, count, cmp_func) if count > 0 then t_sort(base, c
--- @overload fun(v: number, mn: number, mx: number): number --- @overload fun(v: number, mn: number, mx: number): number
--- @overload fun(v: ImVec2, mn: ImVec2, mx: ImVec2): ImVec2 --- @overload fun(v: ImVec2, mn: ImVec2, mx: ImVec2): ImVec2
--@hoist
@native @native
function ImClamp(v, mn, mx) function ImClamp(v, mn, mx)
if type(v) == "number" and type(mn) == "number" and type(mx) == "number" then return mathMin(mathMax(v, mn), mx) if type(v) == "number" and type(mn) == "number" and type(mx) == "number" then return mathMin(mathMax(v, mn), mx)
@@ -221,6 +224,7 @@ end
--- @overload fun(a: number, b: number, t: number): number --- @overload fun(a: number, b: number, t: number): number
--- @overload fun(a: ImVec2, b: ImVec2, t: number): ImVec2 --- @overload fun(a: ImVec2, b: ImVec2, t: number): ImVec2
--- @overload fun(a: ImVec2, b: ImVec2, t: ImVec2): ImVec2 --- @overload fun(a: ImVec2, b: ImVec2, t: ImVec2): ImVec2
--@hoist
@native @native
function ImLerp(a, b, t) function ImLerp(a, b, t)
if type(a) == "number" and type(b) == "number" and type(t) == "number" then return ((a) + ((b) - (a)) * (t)) if type(a) == "number" and type(b) == "number" and type(t) == "number" then return ((a) + ((b) - (a)) * (t))
@@ -229,10 +233,12 @@ function ImLerp(a, b, t)
end end
end end
--@hoist
local function _Trunc(val) if val >= 0 then return mathFloor(val) else return mathCeil(val) end end local function _Trunc(val) if val >= 0 then return mathFloor(val) else return mathCeil(val) end end
--- @overload fun(v: float): float --- @overload fun(v: float): float
--- @overload fun(v: ImVec2): ImVec2 --- @overload fun(v: ImVec2): ImVec2
--@hoist
function ImTrunc(v) function ImTrunc(v)
if type(v) == "number" then return _Trunc(v) if type(v) == "number" then return _Trunc(v)
elseif type(v) == "table" then return ImVec2(_Trunc(v.x), _Trunc(v.y)) elseif type(v) == "table" then return ImVec2(_Trunc(v.x), _Trunc(v.y))
@@ -256,6 +262,7 @@ function ImRsqrt(x) return (1 / ImSqrt(x)) end
function IM_TRUNC(VAL) return math.floor(VAL) end -- Positive values only! function IM_TRUNC(VAL) return math.floor(VAL) end -- Positive values only!
function IM_ROUND(VAL) return math.floor(VAL + 0.5) end -- Positive values only! function IM_ROUND(VAL) return math.floor(VAL + 0.5) end -- Positive values only!
--@hoist
function ImFloor(f) if f >= 0 or math.floor(f) == f then return math.floor(f) else return math.floor(f) - 1 end end -- Decent replacement for floorf() function ImFloor(f) if f >= 0 or math.floor(f) == f then return math.floor(f) else return math.floor(f) - 1 end end -- Decent replacement for floorf()
-- Helpers: Bit manipulation -- Helpers: Bit manipulation
@@ -376,6 +383,7 @@ function ImStd.strncmp(str1, str2, max_count)
return 0 return 0
end end
--@hoist
function ImSaturate(f) return ((f < 0.0 and 0.0) or (f > 1.0 and 1.0) or f) end function ImSaturate(f) return ((f < 0.0 and 0.0) or (f > 1.0 and 1.0) or f) end
IM_F32_TO_INT8_UNBOUND = function(val) return math.floor(val * 255.0 + (val >= 0 and 0.5 or -0.5)) end -- Unsaturated, for display purpose IM_F32_TO_INT8_UNBOUND = function(val) return math.floor(val * 255.0 + (val >= 0 and 0.5 or -0.5)) end -- Unsaturated, for display purpose
@@ -837,14 +845,21 @@ function IM_RECT:GetBR() return ImVec2(self.Max.x, self.Max.y) end -- Bottom-rig
--- @param r ImRect --- @param r ImRect
@native @native
function IM_RECT:ClipWith(r) function IM_RECT:ClipWith(r)
ImVec2_Copy(self.Min, ImMax(self.Min, r.Min)); ImVec2_Copy(self.Max, ImMin(self.Max, r.Max)) -- [Luau] scalar math, no temporary ImVec2 (= ImMax(Min, r.Min) / ImMin(Max, r.Max))
local smin, smax, rmin, rmax = self.Min, self.Max, r.Min, r.Max
smin.x = mathMax(smin.x, rmin.x); smin.y = mathMax(smin.y, rmin.y)
smax.x = mathMin(smax.x, rmax.x); smax.y = mathMin(smax.y, rmax.y)
end end
-- Full version, ensure both points are fully clipped. -- Full version, ensure both points are fully clipped.
--- @param r ImRect --- @param r ImRect
@native @native
function IM_RECT:ClipWithFull(r) function IM_RECT:ClipWithFull(r)
ImVec2_Copy(self.Min, ImClamp(self.Min, r.Min, r.Max)); ImVec2_Copy(self.Max, ImClamp(self.Max, r.Min, r.Max)) -- [Luau] scalar math, no temporary ImVec2 (= ImClamp(Min/Max, r.Min, r.Max))
local smin, smax, rmin, rmax = self.Min, self.Max, r.Min, r.Max
local min_x, min_y, max_x, max_y = rmin.x, rmin.y, rmax.x, rmax.y
smin.x = mathMax(min_x, mathMin(smin.x, max_x)); smin.y = mathMax(min_y, mathMin(smin.y, max_y))
smax.x = mathMax(min_x, mathMin(smax.x, max_x)); smax.y = mathMax(min_y, mathMin(smax.y, max_y))
end end
--- @param p ImRect|ImVec2 --- @param p ImRect|ImVec2
@@ -983,7 +998,15 @@ function MT.ImDrawList:PathClear() self._Path:clear() end
--- @param pos ImVec2 --- @param pos ImVec2
@native @native
function MT.ImDrawList:PathLineTo(pos) self._Path:push_back(pos) end function MT.ImDrawList:PathLineTo(pos)
-- [Luau] inline self._Path:push_back(pos) (ImVec2_TCopy into the preallocated slot)
local path = self._Path
local n = path.Size + 1
if n > path.Capacity then path:push_back(pos) return end
local dest = path.Data[n]
dest.x = pos.x; dest.y = pos.y
path.Size = n
end
function MT.ImDrawList:PathLineToMergeDuplicate(pos) function MT.ImDrawList:PathLineToMergeDuplicate(pos)
local path_size = self._Path.Size local path_size = self._Path.Size
+185 -121
View File
@@ -200,6 +200,7 @@ end
local bit32 = bit32 local bit32 = bit32
local band, bor, bnot, lshift, rshift = bit32.band, bit32.bor, bit32.bnot, bit32.lshift, bit32.rshift local band, bor, bnot, lshift, rshift = bit32.band, bit32.bor, bit32.bnot, bit32.lshift, bit32.rshift
local mathMax, mathMin = math.max, math.min -- [Luau] scalar ImMax()/ImMin() in table code are written as mathMax()/mathMin() (no call through the vector-aware helper)
IM_COL32_DISABLE = IM_COL32(0, 0, 0, 1) -- Special sentinel code which cannot be used as a regular color. IM_COL32_DISABLE = IM_COL32(0, 0, 0, 1) -- Special sentinel code which cannot be used as a regular color.
IMGUI_TABLE_MAX_COLUMNS = 512 -- Arbitrary "safety" maximum, may be lifted in the future if needed. Must fit in ImGuiTableColumnIdx/ImGuiTableDrawChannelIdx. IMGUI_TABLE_MAX_COLUMNS = 512 -- Arbitrary "safety" maximum, may be lifted in the future if needed. Must fit in ImGuiTableColumnIdx/ImGuiTableDrawChannelIdx.
@@ -482,7 +483,7 @@ function ImGui.BeginTableEx(name, id, columns_count, flags, outer_size, inner_wi
-- - always performing the GetOrAddByKey() O(log N) query in g.Tables.Map[]. -- - always performing the GetOrAddByKey() O(log N) query in g.Tables.Map[].
local use_child_window = band(flags, bor(ImGuiTableFlags.ScrollX, ImGuiTableFlags.ScrollY)) ~= 0 local use_child_window = band(flags, bor(ImGuiTableFlags.ScrollX, ImGuiTableFlags.ScrollY)) ~= 0
local avail_size = ImGui.GetContentRegionAvail() local avail_size = ImGui.GetContentRegionAvail()
local actual_outer_size = ImTrunc(ImGui.CalcItemSize(outer_size, ImMax(avail_size.x, IMGUI_WINDOW_HARD_MIN_SIZE), use_child_window and ImMax(avail_size.y, IMGUI_WINDOW_HARD_MIN_SIZE) or 0.0)) local actual_outer_size = ImTrunc(ImGui.CalcItemSize(outer_size, mathMax(avail_size.x, IMGUI_WINDOW_HARD_MIN_SIZE), use_child_window and mathMax(avail_size.y, IMGUI_WINDOW_HARD_MIN_SIZE) or 0.0))
local outer_rect = ImRect(outer_window.DC.CursorPos, outer_window.DC.CursorPos + actual_outer_size) local outer_rect = ImRect(outer_window.DC.CursorPos, outer_window.DC.CursorPos + actual_outer_size)
local outer_window_is_measuring_size = (outer_window.AutoFitFramesX > 0) or (outer_window.AutoFitFramesY > 0) -- Doesn't apply to AlwaysAutoResize windows! local outer_window_is_measuring_size = (outer_window.AutoFitFramesX > 0) or (outer_window.AutoFitFramesY > 0) -- Doesn't apply to AlwaysAutoResize windows!
if use_child_window and ImGui.IsClippedEx(outer_rect, 0) and not outer_window_is_measuring_size then if use_child_window and ImGui.IsClippedEx(outer_rect, 0) and not outer_window_is_measuring_size then
@@ -638,15 +639,15 @@ function ImGui.BeginTableEx(name, id, columns_count, flags, outer_size, inner_wi
-- different x/y values to BeginChild(). -- different x/y values to BeginChild().
local border_size = TABLE_BORDER_SIZE local border_size = TABLE_BORDER_SIZE
if band(flags, ImGuiTableFlags.BordersOuterV) ~= 0 then if band(flags, ImGuiTableFlags.BordersOuterV) ~= 0 then
table_.HostClipRect.Min.x = ImMin(table_.HostClipRect.Min.x + border_size, table_.HostClipRect.Max.x) table_.HostClipRect.Min.x = mathMin(table_.HostClipRect.Min.x + border_size, table_.HostClipRect.Max.x)
if inner_window.DecoOuterSizeX2 == 0.0 then if inner_window.DecoOuterSizeX2 == 0.0 then
table_.HostClipRect.Max.x = ImMax(table_.HostClipRect.Max.x - border_size, table_.HostClipRect.Min.x) table_.HostClipRect.Max.x = mathMax(table_.HostClipRect.Max.x - border_size, table_.HostClipRect.Min.x)
end end
end end
if band(flags, ImGuiTableFlags.BordersOuterH) ~= 0 then if band(flags, ImGuiTableFlags.BordersOuterH) ~= 0 then
table_.HostClipRect.Min.y = ImMin(table_.HostClipRect.Min.y + border_size, table_.HostClipRect.Max.y) table_.HostClipRect.Min.y = mathMin(table_.HostClipRect.Min.y + border_size, table_.HostClipRect.Max.y)
if inner_window.DecoOuterSizeY2 == 0.0 then if inner_window.DecoOuterSizeY2 == 0.0 then
table_.HostClipRect.Max.y = ImMax(table_.HostClipRect.Max.y - border_size, table_.HostClipRect.Min.y) table_.HostClipRect.Max.y = mathMax(table_.HostClipRect.Max.y - border_size, table_.HostClipRect.Min.y)
end end
end end
end end
@@ -679,7 +680,7 @@ function ImGui.BeginTableEx(name, id, columns_count, flags, outer_size, inner_wi
ImRect_Copy(table_.InnerClipRect, (inner_window == outer_window) and table_.WorkRect or inner_window.ClipRect) ImRect_Copy(table_.InnerClipRect, (inner_window == outer_window) and table_.WorkRect or inner_window.ClipRect)
table_.InnerClipRect:ClipWith(table_.WorkRect) -- We need this to honor inner_width table_.InnerClipRect:ClipWith(table_.WorkRect) -- We need this to honor inner_width
table_.InnerClipRect:ClipWithFull(table_.HostClipRect) table_.InnerClipRect:ClipWithFull(table_.HostClipRect)
table_.InnerClipRect.Max.y = (band(flags, ImGuiTableFlags.NoHostExtendY) ~= 0) and ImMin(table_.InnerClipRect.Max.y, inner_window.WorkRect.Max.y) or table_.HostClipRect.Max.y table_.InnerClipRect.Max.y = (band(flags, ImGuiTableFlags.NoHostExtendY) ~= 0) and mathMin(table_.InnerClipRect.Max.y, inner_window.WorkRect.Max.y) or table_.HostClipRect.Max.y
table_.RowPosY1 = table_.WorkRect.Min.y -- This is needed somehow table_.RowPosY1 = table_.WorkRect.Min.y -- This is needed somehow
table_.RowPosY2 = table_.WorkRect.Min.y table_.RowPosY2 = table_.WorkRect.Min.y
@@ -1050,7 +1051,7 @@ function ImGui.TableUpdateLayout(table_)
BitArrayClearAll(table_.EnabledMaskByIndex) BitArrayClearAll(table_.EnabledMaskByIndex)
BitArrayClearAll(table_.EnabledMaskByDisplayOrder) BitArrayClearAll(table_.EnabledMaskByDisplayOrder)
table_.LeftMostEnabledColumn = -1 table_.LeftMostEnabledColumn = -1
table_.MinColumnWidth = ImMax(1.0, g.Style.FramePadding.x * 1.0) -- g.Style.ColumnsMinSpacing -- FIXME-TABLE table_.MinColumnWidth = mathMax(1.0, g.Style.FramePadding.x * 1.0) -- g.Style.ColumnsMinSpacing -- FIXME-TABLE
-- [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns. -- [Part 1] Apply/lock Enabled and Order states. Calculate auto/ideal width for columns. Count fixed/stretch columns.
-- Process columns in their visible orders as we are building the Prev/Next indices. -- Process columns in their visible orders as we are building the Prev/Next indices.
@@ -1152,7 +1153,7 @@ function ImGui.TableUpdateLayout(table_)
stretch_sum_width_auto = stretch_sum_width_auto + column.WidthAuto stretch_sum_width_auto = stretch_sum_width_auto + column.WidthAuto
count_stretch = count_stretch + 1 count_stretch = count_stretch + 1
else else
fixed_max_width_auto = ImMax(fixed_max_width_auto, column.WidthAuto) fixed_max_width_auto = mathMax(fixed_max_width_auto, column.WidthAuto)
count_fixed = count_fixed + 1 count_fixed = count_fixed + 1
end end
end end
@@ -1204,7 +1205,7 @@ function ImGui.TableUpdateLayout(table_)
-- FIXME: Move this to .WidthGiven to avoid temporary lossyness? -- FIXME: Move this to .WidthGiven to avoid temporary lossyness?
-- FIXME: This break IsPreserveWidthAuto from not flickering if the stored WidthAuto was smaller. -- FIXME: This break IsPreserveWidthAuto from not flickering if the stored WidthAuto was smaller.
if column.AutoFitQueue > 0x01 and table_.IsInitializing and not column.IsPreserveWidthAuto then if column.AutoFitQueue > 0x01 and table_.IsInitializing and not column.IsPreserveWidthAuto then
column.WidthRequest = ImMax(column.WidthRequest, table_.MinColumnWidth * 4.0) -- FIXME-TABLE: Another constant/scale? column.WidthRequest = mathMax(column.WidthRequest, table_.MinColumnWidth * 4.0) -- FIXME-TABLE: Another constant/scale?
end end
sum_width_requests = sum_width_requests + column.WidthRequest sum_width_requests = sum_width_requests + column.WidthRequest
else else
@@ -1238,7 +1239,7 @@ function ImGui.TableUpdateLayout(table_)
local work_rect = Rect(table_.WorkRect) local work_rect = Rect(table_.WorkRect)
local width_spacings = (table_.OuterPaddingX * 2.0) + (table_.CellSpacingX1 + table_.CellSpacingX2) * (table_.ColumnsEnabledCount - 1) local width_spacings = (table_.OuterPaddingX * 2.0) + (table_.CellSpacingX1 + table_.CellSpacingX2) * (table_.ColumnsEnabledCount - 1)
local width_removed = (table_.HasScrollbarYPrev and not table_.InnerWindow.ScrollbarY) and g.Style.ScrollbarSize or 0.0 -- To synchronize decoration width of synced tables with mismatching scrollbar state (#5920) local width_removed = (table_.HasScrollbarYPrev and not table_.InnerWindow.ScrollbarY) and g.Style.ScrollbarSize or 0.0 -- To synchronize decoration width of synced tables with mismatching scrollbar state (#5920)
local width_avail = ImMax(1.0, ((band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0 and table_.InnerWidth == 0.0) and table_.InnerClipRect:GetWidth() or work_rect:GetWidth()) - width_removed) local width_avail = mathMax(1.0, ((band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0 and table_.InnerWidth == 0.0) and table_.InnerClipRect:GetWidth() or work_rect:GetWidth()) - width_removed)
local width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests local width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests
local width_remaining_for_stretched_columns = width_avail_for_stretched_columns local width_remaining_for_stretched_columns = width_avail_for_stretched_columns
table_.ColumnsGivenWidth = width_spacings + (table_.CellPaddingX * 2.0) * table_.ColumnsEnabledCount table_.ColumnsGivenWidth = width_spacings + (table_.CellPaddingX * 2.0) * table_.ColumnsEnabledCount
@@ -1249,7 +1250,7 @@ function ImGui.TableUpdateLayout(table_)
-- Allocate width for stretched/weighted columns (StretchWeight gets converted into WidthRequest) -- Allocate width for stretched/weighted columns (StretchWeight gets converted into WidthRequest)
if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 then if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 then
local weight_ratio = column.StretchWeight / stretch_sum_weights local weight_ratio = column.StretchWeight / stretch_sum_weights
column.WidthRequest = IM_TRUNC(ImMax(width_avail_for_stretched_columns * weight_ratio, table_.MinColumnWidth) + 0.01) column.WidthRequest = IM_TRUNC(mathMax(width_avail_for_stretched_columns * weight_ratio, table_.MinColumnWidth) + 0.01)
width_remaining_for_stretched_columns = width_remaining_for_stretched_columns - column.WidthRequest width_remaining_for_stretched_columns = width_remaining_for_stretched_columns - column.WidthRequest
end end
@@ -1260,7 +1261,7 @@ function ImGui.TableUpdateLayout(table_)
end end
-- Assign final width, record width in case we will need to shrink -- Assign final width, record width in case we will need to shrink
column.WidthGiven = ImTrunc(ImMax(column.WidthRequest, table_.MinColumnWidth)) column.WidthGiven = ImTrunc(mathMax(column.WidthRequest, table_.MinColumnWidth))
table_.ColumnsGivenWidth = table_.ColumnsGivenWidth + column.WidthGiven table_.ColumnsGivenWidth = table_.ColumnsGivenWidth + column.WidthGiven
end end
end end
@@ -1291,7 +1292,7 @@ function ImGui.TableUpdateLayout(table_)
table_instance.HoveredRowLast = table_instance.HoveredRowNext table_instance.HoveredRowLast = table_instance.HoveredRowNext
table_instance.HoveredRowNext = -1 table_instance.HoveredRowNext = -1
table_.HoveredColumnBody = -1; table_.HoveredColumnBorder = -1 table_.HoveredColumnBody = -1; table_.HoveredColumnBorder = -1
local mouse_hit_rect = ImRect(table_.OuterRect.Min.x, table_.OuterRect.Min.y, table_.OuterRect.Max.x, ImMax(table_.OuterRect.Max.y, table_.OuterRect.Min.y + table_instance.LastOuterHeight)) local mouse_hit_rect = ImRect(table_.OuterRect.Min.x, table_.OuterRect.Min.y, table_.OuterRect.Max.x, mathMax(table_.OuterRect.Max.y, table_.OuterRect.Min.y + table_instance.LastOuterHeight))
local backup_active_id = g.ActiveId local backup_active_id = g.ActiveId
g.ActiveId = 0 g.ActiveId = 0
local is_hovering_table = ImGui.ItemHoverable(mouse_hit_rect, 0, ImGuiItemFlags.None) local is_hovering_table = ImGui.ItemHoverable(mouse_hit_rect, 0, ImGuiItemFlags.None)
@@ -1346,8 +1347,8 @@ function ImGui.TableUpdateLayout(table_)
-- Lock width based on start position and minimum/maximum width for this position -- Lock width based on start position and minimum/maximum width for this position
column.WidthMax = ImGui.TableCalcMaxColumnWidth(table_, column_n) column.WidthMax = ImGui.TableCalcMaxColumnWidth(table_, column_n)
column.WidthGiven = ImMin(column.WidthGiven, column.WidthMax) column.WidthGiven = mathMin(column.WidthGiven, column.WidthMax)
column.WidthGiven = ImMax(column.WidthGiven, ImMin(column.WidthRequest, table_.MinColumnWidth)) column.WidthGiven = mathMax(column.WidthGiven, mathMin(column.WidthRequest, table_.MinColumnWidth))
column.MaxX = offset_x + column.WidthGiven + table_.CellSpacingX1 + table_.CellSpacingX2 + table_.CellPaddingX * 2.0 column.MaxX = offset_x + column.WidthGiven + table_.CellSpacingX1 + table_.CellSpacingX2 + table_.CellPaddingX * 2.0
-- Lock other positions -- Lock other positions
@@ -1455,7 +1456,7 @@ function ImGui.TableUpdateLayout(table_)
-- [Part 7] Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it) -- [Part 7] Detect/store when we are hovering the unused space after the right-most column (so e.g. context menus can react on it)
-- Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either -- Clear Resizable flag if none of our column are actually resizable (either via an explicit _NoResize flag, either
-- because of using _WidthAuto/_WidthStretch). This will hide the resizing option from the context menu. -- because of using _WidthAuto/_WidthStretch). This will hide the resizing option from the context menu.
local unused_x1 = ImMax(table_.WorkRect.Min.x, table_.Columns[table_.RightMostEnabledColumn].ClipRect.Max.x) local unused_x1 = mathMax(table_.WorkRect.Min.x, table_.Columns[table_.RightMostEnabledColumn].ClipRect.Max.x)
if is_hovering_table and table_.HoveredColumnBody == -1 then if is_hovering_table and table_.HoveredColumnBody == -1 then
if mouse_skewed_x >= unused_x1 then if mouse_skewed_x >= unused_x1 then
table_.HoveredColumnBody = columns_count table_.HoveredColumnBody = columns_count
@@ -1475,7 +1476,7 @@ function ImGui.TableUpdateLayout(table_)
end end
if band(table_.Flags, ImGuiTableFlags.NoHostExtendX) ~= 0 then if band(table_.Flags, ImGuiTableFlags.NoHostExtendX) ~= 0 then
table_.OuterRect.Max.x = unused_x1; table_.WorkRect.Max.x = unused_x1 table_.OuterRect.Max.x = unused_x1; table_.WorkRect.Max.x = unused_x1
table_.InnerClipRect.Max.x = ImMin(table_.InnerClipRect.Max.x, unused_x1) table_.InnerClipRect.Max.x = mathMin(table_.InnerClipRect.Max.x, unused_x1)
end end
ImRect_Copy(table_.InnerWindow.ParentWorkRect, table_.WorkRect) ImRect_Copy(table_.InnerWindow.ParentWorkRect, table_.WorkRect)
table_.BorderX1 = table_.InnerClipRect.Min.x table_.BorderX1 = table_.InnerClipRect.Min.x
@@ -1486,7 +1487,7 @@ function ImGui.TableUpdateLayout(table_)
if band(table_.Flags, ImGuiTableFlags.NoHostExtendY) ~= 0 then if band(table_.Flags, ImGuiTableFlags.NoHostExtendY) ~= 0 then
window_content_max_y = table_.OuterRect.Max.y window_content_max_y = table_.OuterRect.Max.y
else else
window_content_max_y = ImMax(table_.InnerWindow.ContentRegionRect.Max.y, (band(table_.Flags, ImGuiTableFlags.ScrollY) ~= 0) and 0.0 or table_.OuterRect.Max.y) window_content_max_y = mathMax(table_.InnerWindow.ContentRegionRect.Max.y, (band(table_.Flags, ImGuiTableFlags.ScrollY) ~= 0) and 0.0 or table_.OuterRect.Max.y)
end end
table_.InnerWindow.WorkRect.Max.y = ImClamp(window_content_max_y - g.Style.CellPadding.y, table_.InnerWindow.WorkRect.Min.y, table_.InnerWindow.WorkRect.Max.y) table_.InnerWindow.WorkRect.Max.y = ImClamp(window_content_max_y - g.Style.CellPadding.y, table_.InnerWindow.WorkRect.Min.y, table_.InnerWindow.WorkRect.Max.y)
@@ -1578,7 +1579,7 @@ function ImGui.TableUpdateBorders(table_)
local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent)
local hit_half_width = ImTrunc(TABLE_RESIZE_SEPARATOR_HALF_THICKNESS * g.CurrentDpiScale) local hit_half_width = ImTrunc(TABLE_RESIZE_SEPARATOR_HALF_THICKNESS * g.CurrentDpiScale)
local hit_y1 = ((table_.FreezeRowsCount >= 1) and table_.OuterRect.Min.y or table_.WorkRect.Min.y) + table_.AngledHeadersHeight local hit_y1 = ((table_.FreezeRowsCount >= 1) and table_.OuterRect.Min.y or table_.WorkRect.Min.y) + table_.AngledHeadersHeight
local hit_y2_body = ImMax(table_.OuterRect.Max.y, hit_y1 + table_instance.LastOuterHeight - table_.AngledHeadersHeight) local hit_y2_body = mathMax(table_.OuterRect.Max.y, hit_y1 + table_instance.LastOuterHeight - table_.AngledHeadersHeight)
local hit_y2_head = hit_y1 + table_instance.LastTopHeadersRowHeight local hit_y2_head = hit_y1 + table_instance.LastTopHeadersRowHeight
for order_n = 0, table_.ColumnsCount - 1 do for order_n = 0, table_.ColumnsCount - 1 do
@@ -1672,10 +1673,10 @@ function ImGui.EndTable()
if inner_window ~= outer_window then if inner_window ~= outer_window then
inner_window.DC.CursorMaxPos.y = inner_content_max_y inner_window.DC.CursorMaxPos.y = inner_content_max_y
elseif band(flags, ImGuiTableFlags.NoHostExtendY) == 0 then elseif band(flags, ImGuiTableFlags.NoHostExtendY) == 0 then
local v = ImMax(table_.OuterRect.Max.y, inner_content_max_y) -- Patch OuterRect/InnerRect height local v = mathMax(table_.OuterRect.Max.y, inner_content_max_y) -- Patch OuterRect/InnerRect height
table_.OuterRect.Max.y = v; table_.InnerRect.Max.y = v table_.OuterRect.Max.y = v; table_.InnerRect.Max.y = v
end end
table_.WorkRect.Max.y = ImMax(table_.WorkRect.Max.y, table_.OuterRect.Max.y) table_.WorkRect.Max.y = mathMax(table_.WorkRect.Max.y, table_.OuterRect.Max.y)
table_instance.LastOuterHeight = table_.OuterRect:GetHeight() table_instance.LastOuterHeight = table_.OuterRect:GetHeight()
-- Setup inner scrolling range -- Setup inner scrolling range
@@ -1685,10 +1686,10 @@ function ImGui.EndTable()
local outer_padding_for_border = (band(table_.Flags, ImGuiTableFlags.BordersOuterV) ~= 0) and TABLE_BORDER_SIZE or 0.0 local outer_padding_for_border = (band(table_.Flags, ImGuiTableFlags.BordersOuterV) ~= 0) and TABLE_BORDER_SIZE or 0.0
local max_pos_x = inner_window.DC.CursorMaxPos.x local max_pos_x = inner_window.DC.CursorMaxPos.x
if table_.RightMostEnabledColumn ~= -1 then if table_.RightMostEnabledColumn ~= -1 then
max_pos_x = ImMax(max_pos_x, table_.Columns[table_.RightMostEnabledColumn].WorkMaxX + table_.CellPaddingX + table_.OuterPaddingX - outer_padding_for_border) max_pos_x = mathMax(max_pos_x, table_.Columns[table_.RightMostEnabledColumn].WorkMaxX + table_.CellPaddingX + table_.OuterPaddingX - outer_padding_for_border)
end end
if table_.ResizedColumn ~= -1 then if table_.ResizedColumn ~= -1 then
max_pos_x = ImMax(max_pos_x, table_.ResizeLockMinContentsX2) max_pos_x = mathMax(max_pos_x, table_.ResizeLockMinContentsX2)
end end
inner_window.DC.CursorMaxPos.x = max_pos_x + table_.TempData.AngledHeadersExtraWidth inner_window.DC.CursorMaxPos.x = max_pos_x + table_.TempData.AngledHeadersExtraWidth
end end
@@ -1731,12 +1732,12 @@ function ImGui.EndTable()
auto_fit_width_for_stretched = auto_fit_width_for_stretched + column_width_request auto_fit_width_for_stretched = auto_fit_width_for_stretched + column_width_request
end end
if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoResize) ~= 0 then if band(column.Flags, ImGuiTableColumnFlags.WidthStretch) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoResize) ~= 0 then
auto_fit_width_for_stretched_min = ImMax(auto_fit_width_for_stretched_min, column_width_request / (column.StretchWeight / table_.ColumnsStretchSumWeights)) auto_fit_width_for_stretched_min = mathMax(auto_fit_width_for_stretched_min, column_width_request / (column.StretchWeight / table_.ColumnsStretchSumWeights))
end end
end end
end end
local width_spacings = (table_.OuterPaddingX * 2.0) + (table_.CellSpacingX1 + table_.CellSpacingX2) * (table_.ColumnsEnabledCount - 1) local width_spacings = (table_.OuterPaddingX * 2.0) + (table_.CellSpacingX1 + table_.CellSpacingX2) * (table_.ColumnsEnabledCount - 1)
table_.ColumnsAutoFitWidth = width_spacings + (table_.CellPaddingX * 2.0) * table_.ColumnsEnabledCount + auto_fit_width_for_fixed + ImMax(auto_fit_width_for_stretched, auto_fit_width_for_stretched_min) table_.ColumnsAutoFitWidth = width_spacings + (table_.CellPaddingX * 2.0) * table_.ColumnsEnabledCount + auto_fit_width_for_fixed + mathMax(auto_fit_width_for_stretched, auto_fit_width_for_stretched_min)
-- Update scroll -- Update scroll
if band(table_.Flags, ImGuiTableFlags.ScrollX) == 0 and inner_window ~= outer_window then if band(table_.Flags, ImGuiTableFlags.ScrollX) == 0 and inner_window ~= outer_window then
@@ -1801,27 +1802,27 @@ function ImGui.EndTable()
-- FIXME-TABLE: Could we remove this section? -- FIXME-TABLE: Could we remove this section?
-- ColumnsAutoFitWidth may be one frame ahead here since for Fixed+NoResize is calculated from latest contents -- ColumnsAutoFitWidth may be one frame ahead here since for Fixed+NoResize is calculated from latest contents
IM_ASSERT(band(table_.Flags, ImGuiTableFlags.ScrollX) == 0) IM_ASSERT(band(table_.Flags, ImGuiTableFlags.ScrollX) == 0)
outer_window.DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table_.OuterRect.Min.x + table_.ColumnsAutoFitWidth) outer_window.DC.CursorMaxPos.x = mathMax(backup_outer_max_pos.x, table_.OuterRect.Min.x + table_.ColumnsAutoFitWidth)
elseif temp_data.UserOuterSize.x <= 0.0 then elseif temp_data.UserOuterSize.x <= 0.0 then
-- Some references for this: #7651 + tests "table_reported_size", "table_reported_size_outer" equivalent Y block, #9352 -- Some references for this: #7651 + tests "table_reported_size", "table_reported_size_outer" equivalent Y block, #9352
-- - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback? See broken test in 'table_reported_size_outer' -- - FIXME-TABLE: Would make sense to pre-compute expected scrollbar visibility/sizes to generally save a frame of feedback? See broken test in 'table_reported_size_outer'
local outer_content_max_x = table_.OuterRect.Min.x + table_.ColumnsAutoFitWidth local outer_content_max_x = table_.OuterRect.Min.x + table_.ColumnsAutoFitWidth
local decoration_size = table_.TempData.AngledHeadersExtraWidth + ((inner_window ~= outer_window) and inner_window.ScrollbarSizes.x or 0.0) local decoration_size = table_.TempData.AngledHeadersExtraWidth + ((inner_window ~= outer_window) and inner_window.ScrollbarSizes.x or 0.0)
outer_window.DC.IdealMaxPos.x = ImMax(outer_window.DC.IdealMaxPos.x, outer_content_max_x + decoration_size - temp_data.UserOuterSize.x) outer_window.DC.IdealMaxPos.x = mathMax(outer_window.DC.IdealMaxPos.x, outer_content_max_x + decoration_size - temp_data.UserOuterSize.x)
outer_window.DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table_.OuterRect.Max.x, outer_content_max_x + decoration_size)) outer_window.DC.CursorMaxPos.x = mathMax(backup_outer_max_pos.x, mathMin(table_.OuterRect.Max.x, outer_content_max_x + decoration_size))
else else
outer_window.DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, table_.OuterRect.Max.x) outer_window.DC.CursorMaxPos.x = mathMax(backup_outer_max_pos.x, table_.OuterRect.Max.x)
end end
if temp_data.UserOuterSize.y <= 0.0 then if temp_data.UserOuterSize.y <= 0.0 then
-- (same comment as above) -- (same comment as above)
local outer_content_size_y = (inner_window == outer_window) and (inner_content_max_y - table_.InnerRect.Min.y) or (inner_content_max_y - inner_window.DC.CursorStartPos.y) local outer_content_size_y = (inner_window == outer_window) and (inner_content_max_y - table_.InnerRect.Min.y) or (inner_content_max_y - inner_window.DC.CursorStartPos.y)
local outer_content_max_y = table_.OuterRect.Min.y + outer_content_size_y local outer_content_max_y = table_.OuterRect.Min.y + outer_content_size_y
local decoration_size = (inner_window ~= outer_window) and inner_window.ScrollbarSizes.y or 0.0 local decoration_size = (inner_window ~= outer_window) and inner_window.ScrollbarSizes.y or 0.0
outer_window.DC.IdealMaxPos.y = ImMax(outer_window.DC.IdealMaxPos.y, outer_content_max_y + decoration_size - temp_data.UserOuterSize.y) outer_window.DC.IdealMaxPos.y = mathMax(outer_window.DC.IdealMaxPos.y, outer_content_max_y + decoration_size - temp_data.UserOuterSize.y)
outer_window.DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, ImMin(table_.OuterRect.Max.y, outer_content_max_y + decoration_size)) outer_window.DC.CursorMaxPos.y = mathMax(backup_outer_max_pos.y, mathMin(table_.OuterRect.Max.y, outer_content_max_y + decoration_size))
else else
-- OuterRect.Max.y may already have been pushed downward from the initial value (unless ImGuiTableFlags.NoHostExtendY is set) -- OuterRect.Max.y may already have been pushed downward from the initial value (unless ImGuiTableFlags.NoHostExtendY is set)
outer_window.DC.CursorMaxPos.y = ImMax(backup_outer_max_pos.y, table_.OuterRect.Max.y) outer_window.DC.CursorMaxPos.y = mathMax(backup_outer_max_pos.y, table_.OuterRect.Max.y)
end end
-- Save settings -- Save settings
@@ -2039,7 +2040,7 @@ function ImGui.TableSetupScrollFreeze(columns, rows)
IM_ASSERT(columns >= 0 and columns < IMGUI_TABLE_MAX_COLUMNS) IM_ASSERT(columns >= 0 and columns < IMGUI_TABLE_MAX_COLUMNS)
IM_ASSERT(rows >= 0 and rows < 128) -- Arbitrary limit IM_ASSERT(rows >= 0 and rows < 128) -- Arbitrary limit
table_.FreezeColumnsRequest = (band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0) and ImMin(columns, table_.ColumnsCount) or 0 table_.FreezeColumnsRequest = (band(table_.Flags, ImGuiTableFlags.ScrollX) ~= 0) and mathMin(columns, table_.ColumnsCount) or 0
table_.FreezeColumnsCount = (table_.InnerWindow.Scroll.x ~= 0.0) and table_.FreezeColumnsRequest or 0 table_.FreezeColumnsCount = (table_.InnerWindow.Scroll.x ~= 0.0) and table_.FreezeColumnsRequest or 0
table_.FreezeRowsRequest = (band(table_.Flags, ImGuiTableFlags.ScrollY) ~= 0) and rows or 0 table_.FreezeRowsRequest = (band(table_.Flags, ImGuiTableFlags.ScrollY) ~= 0) and rows or 0
table_.FreezeRowsCount = (table_.InnerWindow.Scroll.y ~= 0.0) and table_.FreezeRowsRequest or 0 table_.FreezeRowsCount = (table_.InnerWindow.Scroll.y ~= 0.0) and table_.FreezeRowsRequest or 0
@@ -2065,11 +2066,23 @@ end
-- they would meddle many times with the underlying ImDrawCmd. -- they would meddle many times with the underlying ImDrawCmd.
-- Instead, we do a preemptive overwrite of clipping rectangle _without_ altering the command-buffer and let -- Instead, we do a preemptive overwrite of clipping rectangle _without_ altering the command-buffer and let
-- the subsequent single call to SetCurrentChannel() does it things once. -- the subsequent single call to SetCurrentChannel() does it things once.
@native
function ImGui.SetWindowClipRectBeforeSetChannel(window, clip_rect) function ImGui.SetWindowClipRectBeforeSetChannel(window, clip_rect)
local clip_rect_vec4 = clip_rect:ToVec4() -- [Luau] scalar copies, no temporary ImVec4: the clip rect stack top is a uniquely owned ImVec4 (PushClipRect always pushes a fresh one), so overwrite it in place
ImRect_Copy(window.ClipRect, clip_rect) local min, max = clip_rect.Min, clip_rect.Max
ImVec4_Copy(window.DrawList._CmdHeader.ClipRect, clip_rect_vec4) local x1, y1, x2, y2 = min.x, min.y, max.x, max.y
window.DrawList._ClipRectStack.Data[window.DrawList._ClipRectStack.Size] = clip_rect_vec4 local wcr = window.ClipRect
wcr.Min.x = x1; wcr.Min.y = y1; wcr.Max.x = x2; wcr.Max.y = y2
local draw_list = window.DrawList
local hcr = draw_list._CmdHeader.ClipRect
hcr.x = x1; hcr.y = y1; hcr.z = x2; hcr.w = y2
local stack = draw_list._ClipRectStack
local top = stack.Data[stack.Size]
if top ~= nil then
top.x = x1; top.y = y1; top.z = x2; top.w = y2
else
stack.Data[stack.Size] = ImVec4(x1, y1, x2, y2)
end
end end
function ImGui.TableGetColumnCount() function ImGui.TableGetColumnCount()
@@ -2154,8 +2167,8 @@ function ImGui.TableGetCellBgRect(table_, column_n)
-- x1 = x1 - table.OuterPaddingX -- x1 = x1 - table.OuterPaddingX
--if column.NextEnabledColumn == -1 then --if column.NextEnabledColumn == -1 then
-- x2 = x2 + table.OuterPaddingX -- x2 = x2 + table.OuterPaddingX
x1 = ImMax(x1, table_.WorkRect.Min.x) x1 = mathMax(x1, table_.WorkRect.Min.x)
x2 = ImMin(x2, table_.WorkRect.Max.x) x2 = mathMin(x2, table_.WorkRect.Max.x)
return ImRect(x1, table_.RowPosY1, x2, table_.RowPosY2) return ImRect(x1, table_.RowPosY1, x2, table_.RowPosY2)
end end
@@ -2274,8 +2287,7 @@ function ImGui.TableNextRow(row_flags, row_min_height)
-- We honor min_row_height requested by user, but cannot guarantee per-row maximum height, -- We honor min_row_height requested by user, but cannot guarantee per-row maximum height,
-- because that would essentially require a unique clipping rectangle per-cell. -- because that would essentially require a unique clipping rectangle per-cell.
table_.RowPosY2 = table_.RowPosY2 + table_.RowCellPaddingY * 2.0 table_.RowPosY2 = mathMax(table_.RowPosY2 + table_.RowCellPaddingY * 2.0, table_.RowPosY1 + row_min_height) -- [Luau] inline ImMax
table_.RowPosY2 = ImMax(table_.RowPosY2, table_.RowPosY1 + row_min_height)
-- Disable output until user calls TableNextColumn() -- Disable output until user calls TableNextColumn()
table_.InnerWindow.SkipItems = true table_.InnerWindow.SkipItems = true
@@ -2302,15 +2314,16 @@ function ImGui.TableBeginRow(table_)
table_.RowPosY1 = next_y1; table_.RowPosY2 = next_y1 table_.RowPosY1 = next_y1; table_.RowPosY2 = next_y1
table_.RowTextBaseline = 0.0 table_.RowTextBaseline = 0.0
table_.RowIndentOffsetX = window.DC.Indent.x - table_.HostIndentX -- Lock indent local dc = window.DC -- [Luau] cache
table_.RowIndentOffsetX = dc.Indent.x - table_.HostIndentX -- Lock indent
window.DC.PrevLineTextBaseOffset = 0.0 dc.PrevLineTextBaseOffset = 0.0
window.DC.CursorPosPrevLine.x = window.DC.CursorPos.x dc.CursorPosPrevLine.x = dc.CursorPos.x
window.DC.CursorPosPrevLine.y = window.DC.CursorPos.y + table_.RowCellPaddingY -- This allows users to call SameLine() to share LineSize between columns. dc.CursorPosPrevLine.y = dc.CursorPos.y + table_.RowCellPaddingY -- This allows users to call SameLine() to share LineSize between columns.
window.DC.PrevLineSize.x = 0.0; window.DC.PrevLineSize.y = 0.0 -- This allows users to call SameLine() to share LineSize between columns, and to call it from first column too. dc.PrevLineSize.x = 0.0; dc.PrevLineSize.y = 0.0 -- This allows users to call SameLine() to share LineSize between columns, and to call it from first column too.
window.DC.CurrLineSize.x = 0.0; window.DC.CurrLineSize.y = 0.0 dc.CurrLineSize.x = 0.0; dc.CurrLineSize.y = 0.0
window.DC.IsSameLine = false; window.DC.IsSetPos = false dc.IsSameLine = false; dc.IsSetPos = false
window.DC.CursorMaxPos.y = next_y1 dc.CursorMaxPos.y = next_y1
-- Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging. -- Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging.
if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 then if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 then
@@ -2321,6 +2334,9 @@ function ImGui.TableBeginRow(table_)
end end
end end
-- [Luau] scratch vectors for TableEndRow() row background (never escape)
local TableEndRow_RowMin, TableEndRow_RowMax = ImVec2(), ImVec2()
-- [Internal] Called by TableNextRow() -- [Internal] Called by TableNextRow()
function ImGui.TableEndRow(table_) function ImGui.TableEndRow(table_)
local g = GImGui local g = GImGui
@@ -2347,7 +2363,7 @@ function ImGui.TableEndRow(table_)
local bg_y2 = table_.RowPosY2 local bg_y2 = table_.RowPosY2
local unfreeze_rows_actual = (table_.CurrentRow + 1 == table_.FreezeRowsCount) local unfreeze_rows_actual = (table_.CurrentRow + 1 == table_.FreezeRowsCount)
local unfreeze_rows_request = (table_.CurrentRow + 1 == table_.FreezeRowsRequest) local unfreeze_rows_request = (table_.CurrentRow + 1 == table_.FreezeRowsRequest)
local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) local table_instance = (table_.InstanceCurrent == 0) and table_.InstanceDataFirst or ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) -- [Luau] inline common case
if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 and (table_.CurrentRow == 0 or band(table_.LastRowFlags, ImGuiTableRowFlags.Headers) ~= 0) then if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 and (table_.CurrentRow == 0 or band(table_.LastRowFlags, ImGuiTableRowFlags.Headers) ~= 0) then
table_instance.LastTopHeadersRowHeight = table_instance.LastTopHeadersRowHeight + bg_y2 - bg_y1 table_instance.LastTopHeadersRowHeight = table_instance.LastTopHeadersRowHeight + bg_y2 - bg_y1
end end
@@ -2384,7 +2400,9 @@ function ImGui.TableEndRow(table_)
-- In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is -- In theory we could call SetWindowClipRectBeforeSetChannel() but since we know TableEndRow() is
-- always followed by a change of clipping rectangle we perform the smallest overwrite possible here. -- always followed by a change of clipping rectangle we perform the smallest overwrite possible here.
if band(table_.Flags, ImGuiTableFlags.NoClip) == 0 then if band(table_.Flags, ImGuiTableFlags.NoClip) == 0 then
ImVec4_Copy(window.DrawList._CmdHeader.ClipRect, table_.Bg0ClipRectForDrawCmd:ToVec4()) -- [Luau] scalar copy, no temporary ImVec4
local src, dst = table_.Bg0ClipRectForDrawCmd, window.DrawList._CmdHeader.ClipRect
dst.x = src.Min.x; dst.y = src.Min.y; dst.z = src.Max.x; dst.w = src.Max.y
end end
table_.DrawSplitter:SetCurrentChannel(window.DrawList, TABLE_DRAW_CHANNEL_BG0) table_.DrawSplitter:SetCurrentChannel(window.DrawList, TABLE_DRAW_CHANNEL_BG0)
end end
@@ -2392,13 +2410,16 @@ function ImGui.TableEndRow(table_)
-- Draw row background -- Draw row background
-- We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle -- We soft/cpu clip this so all backgrounds and borders can share the same clipping rectangle
if bg_col0 ~= 0 or bg_col1 ~= 0 then if bg_col0 ~= 0 or bg_col1 ~= 0 then
local row_rect = ImRect(table_.WorkRect.Min.x, bg_y1, table_.WorkRect.Max.x, bg_y2) -- [Luau] row_rect = ImRect(WorkRect.Min.x, bg_y1, WorkRect.Max.x, bg_y2); row_rect:ClipWith(BgClipRect), in scratch vectors (only read by AddRectFilled)
row_rect:ClipWith(table_.BgClipRect) local bg_clip_rect = table_.BgClipRect
if bg_col0 ~= 0 and row_rect.Min.y < row_rect.Max.y then local row_min, row_max = TableEndRow_RowMin, TableEndRow_RowMax
window.DrawList:AddRectFilled(row_rect.Min, row_rect.Max, bg_col0) row_min.x = mathMax(table_.WorkRect.Min.x, bg_clip_rect.Min.x); row_min.y = mathMax(bg_y1, bg_clip_rect.Min.y)
row_max.x = mathMin(table_.WorkRect.Max.x, bg_clip_rect.Max.x); row_max.y = mathMin(bg_y2, bg_clip_rect.Max.y)
if bg_col0 ~= 0 and row_min.y < row_max.y then
window.DrawList:AddRectFilled(row_min, row_max, bg_col0)
end end
if bg_col1 ~= 0 and row_rect.Min.y < row_rect.Max.y then if bg_col1 ~= 0 and row_min.y < row_max.y then
window.DrawList:AddRectFilled(row_rect.Min, row_rect.Max, bg_col1) window.DrawList:AddRectFilled(row_min, row_max, bg_col1)
end end
end end
@@ -2411,8 +2432,8 @@ function ImGui.TableEndRow(table_)
local column = table_.Columns[cell_data.Column] local column = table_.Columns[cell_data.Column]
local cell_bg_rect = ImGui.TableGetCellBgRect(table_, cell_data.Column) local cell_bg_rect = ImGui.TableGetCellBgRect(table_, cell_data.Column)
cell_bg_rect:ClipWith(table_.BgClipRect) cell_bg_rect:ClipWith(table_.BgClipRect)
cell_bg_rect.Min.x = ImMax(cell_bg_rect.Min.x, column.ClipRect.Min.x) -- So that first column after frozen one gets clipped when scrolling cell_bg_rect.Min.x = mathMax(cell_bg_rect.Min.x, column.ClipRect.Min.x) -- So that first column after frozen one gets clipped when scrolling
cell_bg_rect.Max.x = ImMin(cell_bg_rect.Max.x, column.MaxX) cell_bg_rect.Max.x = mathMin(cell_bg_rect.Max.x, column.MaxX)
if cell_bg_rect.Min.y < cell_bg_rect.Max.y then if cell_bg_rect.Min.y < cell_bg_rect.Max.y then
window.DrawList:AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data.BgColor) window.DrawList:AddRectFilled(cell_bg_rect.Min, cell_bg_rect.Max, cell_data.BgColor)
end end
@@ -2438,7 +2459,7 @@ function ImGui.TableEndRow(table_)
for column_n = 0, table_.ColumnsCount - 1 do for column_n = 0, table_.ColumnsCount - 1 do
table_.Columns[column_n].NavLayerCurrent = table_.NavLayer table_.Columns[column_n].NavLayerCurrent = table_.NavLayer
end end
local y0 = ImMax(table_.RowPosY2 + 1, table_.InnerClipRect.Min.y) local y0 = mathMax(table_.RowPosY2 + 1, table_.InnerClipRect.Min.y)
table_instance.LastFrozenHeight = y0 - table_.OuterRect.Min.y table_instance.LastFrozenHeight = y0 - table_.OuterRect.Min.y
if unfreeze_rows_actual then if unfreeze_rows_actual then
@@ -2446,7 +2467,7 @@ function ImGui.TableEndRow(table_)
table_.IsUnfrozenRows = true table_.IsUnfrozenRows = true
-- BgClipRect starts as table.InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect -- BgClipRect starts as table.InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect
local min_y = ImMin(y0, table_.InnerClipRect.Max.y) local min_y = mathMin(y0, table_.InnerClipRect.Max.y)
table_.BgClipRect.Min.y = min_y; table_.Bg2ClipRectForDrawCmd.Min.y = min_y table_.BgClipRect.Min.y = min_y; table_.Bg2ClipRectForDrawCmd.Min.y = min_y
table_.BgClipRect.Max.y = table_.InnerClipRect.Max.y; table_.Bg2ClipRectForDrawCmd.Max.y = table_.InnerClipRect.Max.y table_.BgClipRect.Max.y = table_.InnerClipRect.Max.y; table_.Bg2ClipRectForDrawCmd.Max.y = table_.InnerClipRect.Max.y
table_.Bg2DrawChannelCurrent = table_.Bg2DrawChannelUnfrozen table_.Bg2DrawChannelCurrent = table_.Bg2DrawChannelUnfrozen
@@ -2518,6 +2539,7 @@ function ImGui.TableSetColumnIndex(column_n)
end end
-- [Public] Append into the next column, wrap and create a new row when already on last column -- [Public] Append into the next column, wrap and create a new row when already on last column
@native
function ImGui.TableNextColumn() function ImGui.TableNextColumn()
local g = GImGui local g = GImGui
local table_ = g.CurrentTable local table_ = g.CurrentTable
@@ -2525,28 +2547,33 @@ function ImGui.TableNextColumn()
return false return false
end end
if table_.IsInsideRow and table_.CurrentColumn + 1 < table_.ColumnsCount then local current_column = table_.CurrentColumn
if table_.CurrentColumn ~= -1 then local next_column
if table_.IsInsideRow and current_column + 1 < table_.ColumnsCount then
if current_column ~= -1 then
ImGui.TableEndCell(table_) ImGui.TableEndCell(table_)
end end
ImGui.TableBeginCell(table_, table_.CurrentColumn + 1) next_column = current_column + 1
else else
ImGui.TableNextRow() ImGui.TableNextRow()
ImGui.TableBeginCell(table_, 0) next_column = 0
end end
ImGui.TableBeginCell(table_, next_column)
-- Return whether the column is visible. User may choose to skip submitting items based on this return value, -- Return whether the column is visible. User may choose to skip submitting items based on this return value,
-- however they shouldn't skip submitting for columns that may have the tallest contribution to row height. -- however they shouldn't skip submitting for columns that may have the tallest contribution to row height.
return table_.Columns[table_.CurrentColumn].IsRequestOutput return table_.Columns[next_column].IsRequestOutput
end end
-- [Internal] Called by TableSetColumnIndex()/TableNextColumn() -- [Internal] Called by TableSetColumnIndex()/TableNextColumn()
-- This is called very frequently, so we need to be mindful of unnecessary overhead. -- This is called very frequently, so we need to be mindful of unnecessary overhead.
-- FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns. -- FIXME-TABLE FIXME-OPT: Could probably shortcut some things for non-active or clipped columns.
@native
function ImGui.TableBeginCell(table_, column_n) function ImGui.TableBeginCell(table_, column_n)
local g = GImGui local g = GImGui
local column = table_.Columns[column_n] local column = table_.Columns[column_n]
local window = table_.InnerWindow local window = table_.InnerWindow
local dc = window.DC -- [Luau] cache
table_.CurrentColumn = column_n table_.CurrentColumn = column_n
-- Start position is roughly ~~ CellRect.Min + CellPadding + Indent -- Start position is roughly ~~ CellRect.Min + CellPadding + Indent
@@ -2555,35 +2582,45 @@ function ImGui.TableBeginCell(table_, column_n)
start_x = start_x + table_.RowIndentOffsetX -- ~~ += window.DC.Indent.x - table.HostIndentX, except we locked it for the row. start_x = start_x + table_.RowIndentOffsetX -- ~~ += window.DC.Indent.x - table.HostIndentX, except we locked it for the row.
end end
window.DC.CursorPos.x = start_x local start_y = table_.RowPosY1 + table_.RowCellPaddingY
window.DC.CursorPos.y = table_.RowPosY1 + table_.RowCellPaddingY dc.CursorPos.x = start_x
window.DC.CursorMaxPos.x = window.DC.CursorPos.x dc.CursorPos.y = start_y
window.DC.ColumnsOffset.x = start_x - window.Pos.x - window.DC.Indent.x -- FIXME-WORKRECT dc.CursorMaxPos.x = start_x
window.DC.CursorPosPrevLine.x = window.DC.CursorPos.x -- PrevLine.y is preserved. This allows users to call SameLine() to share LineSize between columns. dc.ColumnsOffset.x = start_x - window.Pos.x - dc.Indent.x -- FIXME-WORKRECT
window.DC.CurrLineTextBaseOffset = table_.RowTextBaseline dc.CursorPosPrevLine.x = start_x -- PrevLine.y is preserved. This allows users to call SameLine() to share LineSize between columns.
window.DC.NavLayerCurrent = column.NavLayerCurrent dc.CurrLineTextBaseOffset = table_.RowTextBaseline
dc.NavLayerCurrent = column.NavLayerCurrent
-- Note how WorkRect.Max.y is only set once during layout -- Note how WorkRect.Max.y is only set once during layout
window.WorkRect.Min.y = window.DC.CursorPos.y local work_rect = window.WorkRect
window.WorkRect.Min.x = column.WorkMinX work_rect.Min.y = start_y
window.WorkRect.Max.x = column.WorkMaxX work_rect.Min.x = column.WorkMinX
window.DC.ItemWidth = column.ItemWidth work_rect.Max.x = column.WorkMaxX
dc.ItemWidth = column.ItemWidth
window.SkipItems = column.IsSkipItems local skip_items = column.IsSkipItems
if column.IsSkipItems then window.SkipItems = skip_items
if skip_items then
g.LastItemData.ID = 0 g.LastItemData.ID = 0
g.LastItemData.StatusFlags = 0 g.LastItemData.StatusFlags = 0
end end
-- Also see TablePushColumnChannel() -- Also see TablePushColumnChannel()
-- [Luau] inline the SetCurrentChannel() early-out (same channel) to skip the method call
local splitter = table_.DrawSplitter
if band(table_.Flags, ImGuiTableFlags.NoClip) ~= 0 then if band(table_.Flags, ImGuiTableFlags.NoClip) ~= 0 then
-- FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed. -- FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed.
table_.DrawSplitter:SetCurrentChannel(window.DrawList, TABLE_DRAW_CHANNEL_NOCLIP) if splitter._Current ~= TABLE_DRAW_CHANNEL_NOCLIP then
splitter:SetCurrentChannel(window.DrawList, TABLE_DRAW_CHANNEL_NOCLIP)
end
--IM_ASSERT(table.DrawSplitter._Current == TABLE_DRAW_CHANNEL_NOCLIP) --IM_ASSERT(table.DrawSplitter._Current == TABLE_DRAW_CHANNEL_NOCLIP)
else else
-- FIXME-TABLE: Could avoid this if draw channel is dummy channel? -- FIXME-TABLE: Could avoid this if draw channel is dummy channel?
ImGui.SetWindowClipRectBeforeSetChannel(window, column.ClipRect) ImGui.SetWindowClipRectBeforeSetChannel(window, column.ClipRect)
table_.DrawSplitter:SetCurrentChannel(window.DrawList, column.DrawChannelCurrent) local channel = column.DrawChannelCurrent
if splitter._Current ~= channel then
splitter:SetCurrentChannel(window.DrawList, channel)
end
end end
-- Logging -- Logging
@@ -2594,31 +2631,34 @@ function ImGui.TableBeginCell(table_, column_n)
end end
-- [Internal] Called by TableNextRow()/TableSetColumnIndex()/TableNextColumn() -- [Internal] Called by TableNextRow()/TableSetColumnIndex()/TableNextColumn()
@native
function ImGui.TableEndCell(table_) function ImGui.TableEndCell(table_)
local column = table_.Columns[table_.CurrentColumn] local column = table_.Columns[table_.CurrentColumn]
local window = table_.InnerWindow local window = table_.InnerWindow
local dc = window.DC -- [Luau] cache; mathMax() inlined as math.max below
if window.DC.IsSetPos then if dc.IsSetPos then
ImGui.ErrorCheckUsingSetCursorPosToExtendParentBoundaries() ImGui.ErrorCheckUsingSetCursorPosToExtendParentBoundaries()
end end
-- Report maximum position so we can infer content size per column. -- Report maximum position so we can infer content size per column.
local x = window.DC.CursorMaxPos.x local cursor_max_pos = dc.CursorMaxPos
local x = cursor_max_pos.x
if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 then if band(table_.RowFlags, ImGuiTableRowFlags.Headers) ~= 0 then
column.ContentMaxXHeadersUsed = ImMax(column.ContentMaxXHeadersUsed, x) -- Useful in case user submit contents in header row that is not a TableHeader() call column.ContentMaxXHeadersUsed = mathMax(column.ContentMaxXHeadersUsed, x) -- Useful in case user submit contents in header row that is not a TableHeader() call
elseif table_.IsUnfrozenRows then elseif table_.IsUnfrozenRows then
column.ContentMaxXUnfrozen = ImMax(column.ContentMaxXUnfrozen, x) column.ContentMaxXUnfrozen = mathMax(column.ContentMaxXUnfrozen, x)
else else
column.ContentMaxXFrozen = ImMax(column.ContentMaxXFrozen, x) column.ContentMaxXFrozen = mathMax(column.ContentMaxXFrozen, x)
end end
if column.IsEnabled then if column.IsEnabled then
table_.RowPosY2 = ImMax(table_.RowPosY2, window.DC.CursorMaxPos.y + table_.RowCellPaddingY) table_.RowPosY2 = mathMax(table_.RowPosY2, cursor_max_pos.y + table_.RowCellPaddingY)
end end
column.ItemWidth = window.DC.ItemWidth column.ItemWidth = dc.ItemWidth
-- Propagate text baseline for the entire row -- Propagate text baseline for the entire row
-- FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one. -- FIXME-TABLE: Here we propagate text baseline from the last line of the cell.. instead of the first one.
table_.RowTextBaseline = ImMax(table_.RowTextBaseline, window.DC.PrevLineTextBaseOffset) table_.RowTextBaseline = mathMax(table_.RowTextBaseline, dc.PrevLineTextBaseOffset)
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@@ -2663,11 +2703,11 @@ end
-- Note this is meant to be stored in column.WidthAuto, please generally use the WidthAuto field -- Note this is meant to be stored in column.WidthAuto, please generally use the WidthAuto field
function ImGui.TableGetColumnWidthAuto(table_, column) function ImGui.TableGetColumnWidthAuto(table_, column)
local content_width_body = ImMax(column.ContentMaxXFrozen, column.ContentMaxXUnfrozen) - column.WorkMinX local content_width_body = mathMax(column.ContentMaxXFrozen, column.ContentMaxXUnfrozen) - column.WorkMinX
local content_width_headers = column.ContentMaxXHeadersIdeal - column.WorkMinX local content_width_headers = column.ContentMaxXHeadersIdeal - column.WorkMinX
local width_auto = content_width_body local width_auto = content_width_body
if band(column.Flags, ImGuiTableColumnFlags.NoHeaderWidth) == 0 then if band(column.Flags, ImGuiTableColumnFlags.NoHeaderWidth) == 0 then
width_auto = ImMax(width_auto, content_width_headers) width_auto = mathMax(width_auto, content_width_headers)
end end
-- Non-resizable fixed columns preserve their requested width -- Non-resizable fixed columns preserve their requested width
@@ -2677,7 +2717,7 @@ function ImGui.TableGetColumnWidthAuto(table_, column)
end end
end end
return ImMax(width_auto, table_.MinColumnWidth) return mathMax(width_auto, table_.MinColumnWidth)
end end
-- 'width' = inner column width, without padding -- 'width' = inner column width, without padding
@@ -2693,7 +2733,7 @@ function ImGui.TableSetColumnWidth(column_n, width)
-- Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded) -- Compare both requested and actual given width to avoid overwriting requested width when column is stuck (minimum size, bounded)
IM_ASSERT(table_.MinColumnWidth > 0.0) IM_ASSERT(table_.MinColumnWidth > 0.0)
local min_width = table_.MinColumnWidth local min_width = table_.MinColumnWidth
local max_width = ImMax(min_width, column_0.WidthMax) -- Don't use TableCalcMaxColumnWidth() here as it would rely on MinX from last instance (#7933) local max_width = mathMax(min_width, column_0.WidthMax) -- Don't use TableCalcMaxColumnWidth() here as it would rely on MinX from last instance (#7933)
column_0_width = ImClamp(column_0_width, min_width, max_width) column_0_width = ImClamp(column_0_width, min_width, max_width)
if column_0.WidthGiven == column_0_width or column_0.WidthRequest == column_0_width then if column_0.WidthGiven == column_0_width or column_0.WidthRequest == column_0_width then
return return
@@ -2754,7 +2794,7 @@ function ImGui.TableSetColumnWidth(column_n, width)
-- Resizing from right-side of a Stretch column before a Fixed column forward sizing to left-side of fixed column. -- Resizing from right-side of a Stretch column before a Fixed column forward sizing to left-side of fixed column.
-- (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b) -- (old_a + old_b == new_a + new_b) --> (new_a == old_a + old_b - new_b)
local column_1_width = ImMax(column_1.WidthRequest - (column_0_width - column_0.WidthRequest), min_width) local column_1_width = mathMax(column_1.WidthRequest - (column_0_width - column_0.WidthRequest), min_width)
column_0_width = column_0.WidthRequest + column_1.WidthRequest - column_1_width column_0_width = column_0.WidthRequest + column_1.WidthRequest - column_1_width
IM_ASSERT(column_0_width > 0.0 and column_1_width > 0.0) IM_ASSERT(column_0_width > 0.0 and column_1_width > 0.0)
column_0.WidthRequest = column_0_width column_0.WidthRequest = column_0_width
@@ -2970,6 +3010,12 @@ end
-- --
-- This function is particularly tricky to understand.. take a breath. -- This function is particularly tricky to understand.. take a breath.
-- (Lua: splitter channel n lives in splitter._Channels.Data[n + 1]; bit arrays are Lua sets) -- (Lua: splitter channel n lives in splitter._Channels.Data[n + 1]; bit arrays are Lua sets)
local TableMergeDrawChannels_MergeGroups = {}
for n = 0, 3 do
TableMergeDrawChannels_MergeGroups[n] = { ClipRect = ImRect(), ChannelsCount = 0, ChannelsMask = {} }
end
local TableMergeDrawChannels_RemainingMask = {}
local TableMergeDrawChannels_DstTmp = {}
function ImGui.TableMergeDrawChannels(table_) function ImGui.TableMergeDrawChannels(table_)
local splitter = table_.DrawSplitter local splitter = table_.DrawSplitter
local channels = splitter._Channels.Data local channels = splitter._Channels.Data
@@ -2979,12 +3025,18 @@ function ImGui.TableMergeDrawChannels(table_)
-- Track which groups we are going to attempt to merge, and which channels goes into each group. -- Track which groups we are going to attempt to merge, and which channels goes into each group.
local merge_group_mask = 0x00 local merge_group_mask = 0x00
local merge_groups = {} -- [Luau] reuse file-level scratch storage (function is not re-entrant, nothing escapes)
local merge_groups = TableMergeDrawChannels_MergeGroups
for n = 0, 3 do for n = 0, 3 do
merge_groups[n] = { ClipRect = ImRect(), ChannelsCount = 0, ChannelsMask = {} } local merge_group = merge_groups[n]
local cr = merge_group.ClipRect
cr.Min.x = 0.0; cr.Min.y = 0.0; cr.Max.x = 0.0; cr.Max.y = 0.0
merge_group.ChannelsCount = 0
table.clear(merge_group.ChannelsMask)
end end
local max_draw_channels = (4 + table_.ColumnsCount * 2) local max_draw_channels = (4 + table_.ColumnsCount * 2)
local remaining_mask = {} local remaining_mask = TableMergeDrawChannels_RemainingMask
table.clear(remaining_mask)
-- 1. Scan channels and take note of those which can be merged -- 1. Scan channels and take note of those which can be merged
for column_n = 0, table_.ColumnsCount - 1 do for column_n = 0, table_.ColumnsCount - 1 do
@@ -3011,9 +3063,9 @@ function ImGui.TableMergeDrawChannels(table_)
if band(column.Flags, ImGuiTableColumnFlags.NoClip) == 0 then if band(column.Flags, ImGuiTableColumnFlags.NoClip) == 0 then
local content_max_x local content_max_x
if not has_freeze_v then if not has_freeze_v then
content_max_x = ImMax(column.ContentMaxXUnfrozen, column.ContentMaxXHeadersUsed) -- No row freeze content_max_x = mathMax(column.ContentMaxXUnfrozen, column.ContentMaxXHeadersUsed) -- No row freeze
elseif merge_group_sub_n == 0 then elseif merge_group_sub_n == 0 then
content_max_x = ImMax(column.ContentMaxXFrozen, column.ContentMaxXHeadersUsed) -- Row freeze: use width before freeze content_max_x = mathMax(column.ContentMaxXFrozen, column.ContentMaxXHeadersUsed) -- Row freeze: use width before freeze
else else
content_max_x = column.ContentMaxXUnfrozen -- Row freeze: use width after freeze content_max_x = column.ContentMaxXUnfrozen -- Row freeze: use width after freeze
end end
@@ -3026,12 +3078,18 @@ function ImGui.TableMergeDrawChannels(table_)
IM_ASSERT(channel_no < max_draw_channels) IM_ASSERT(channel_no < max_draw_channels)
local merge_group = merge_groups[merge_group_n] local merge_group = merge_groups[merge_group_n]
if merge_group.ChannelsCount == 0 then if merge_group.ChannelsCount == 0 then
merge_group.ClipRect = ImRect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX) local gcr = merge_group.ClipRect -- [Luau] in place (merge_groups are local to this call)
gcr.Min.x = FLT_MAX; gcr.Min.y = FLT_MAX; gcr.Max.x = -FLT_MAX; gcr.Max.y = -FLT_MAX
end end
merge_group.ChannelsMask[channel_no] = true merge_group.ChannelsMask[channel_no] = true
merge_group.ChannelsCount = merge_group.ChannelsCount + 1 merge_group.ChannelsCount = merge_group.ChannelsCount + 1
local cr = cmds.Data[1].ClipRect local cr = cmds.Data[1].ClipRect
merge_group.ClipRect:Add(ImRect(cr.x, cr.y, cr.z, cr.w)) -- [Luau] scalar version of merge_group.ClipRect:Add(ImRect(cr.x, cr.y, cr.z, cr.w))
local gmin, gmax = merge_group.ClipRect.Min, merge_group.ClipRect.Max
if gmin.x > cr.x then gmin.x = cr.x end
if gmin.y > cr.y then gmin.y = cr.y end
if gmax.x < cr.z then gmax.x = cr.z end
if gmax.y < cr.w then gmax.y = cr.w end
merge_group_mask = bor(merge_group_mask, lshift(1, merge_group_n)) merge_group_mask = bor(merge_group_mask, lshift(1, merge_group_n))
until true until true
end end
@@ -3046,7 +3104,8 @@ function ImGui.TableMergeDrawChannels(table_)
if merge_group_mask ~= 0 then if merge_group_mask ~= 0 then
-- We skip channel 0 (Bg0/Bg1) and 1 (Bg2 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels(). -- We skip channel 0 (Bg0/Bg1) and 1 (Bg2 frozen) from the shuffling since they won't move - see channels allocation in TableSetupDrawChannels().
local LEADING_DRAW_CHANNELS = 2 local LEADING_DRAW_CHANNELS = 2
local dst_tmp = {} local dst_tmp = TableMergeDrawChannels_DstTmp -- [Luau] scratch
table.clear(dst_tmp)
for n = LEADING_DRAW_CHANNELS, splitter._Count - 1 do for n = LEADING_DRAW_CHANNELS, splitter._Count - 1 do
remaining_mask[n] = true remaining_mask[n] = true
end end
@@ -3069,16 +3128,16 @@ function ImGui.TableMergeDrawChannels(table_)
-- FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit -- FIXME-TABLE FIXME-WORKRECT: We are wasting a merge opportunity on tables without scrolling if column doesn't fit
-- within host clip rect, solely because of the half-padding difference between window.WorkRect and window.InnerClipRect. -- within host clip rect, solely because of the half-padding difference between window.WorkRect and window.InnerClipRect.
if band(merge_group_n, 1) == 0 or not has_freeze_h then if band(merge_group_n, 1) == 0 or not has_freeze_h then
merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x) merge_clip_rect.Min.x = mathMin(merge_clip_rect.Min.x, host_rect.Min.x)
end end
if band(merge_group_n, 2) == 0 or not has_freeze_v then if band(merge_group_n, 2) == 0 or not has_freeze_v then
merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y) merge_clip_rect.Min.y = mathMin(merge_clip_rect.Min.y, host_rect.Min.y)
end end
if band(merge_group_n, 1) ~= 0 then if band(merge_group_n, 1) ~= 0 then
merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x) merge_clip_rect.Max.x = mathMax(merge_clip_rect.Max.x, host_rect.Max.x)
end end
if band(merge_group_n, 2) ~= 0 and band(table_.Flags, ImGuiTableFlags.NoHostExtendY) == 0 then if band(merge_group_n, 2) ~= 0 and band(table_.Flags, ImGuiTableFlags.NoHostExtendY) == 0 then
merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y) merge_clip_rect.Max.y = mathMax(merge_clip_rect.Max.y, host_rect.Max.y)
end end
--ImGui.GetForegroundDrawList():AddRect(merge_group.ClipRect.Min, merge_group.ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0, 0, 1.0) -- [DEBUG] --ImGui.GetForegroundDrawList():AddRect(merge_group.ClipRect.Min, merge_group.ClipRect.Max, IM_COL32(255, 0, 0, 200), 0.0, 0, 1.0) -- [DEBUG]
--ImGui.GetForegroundDrawList():AddLine(merge_group.ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200)) --ImGui.GetForegroundDrawList():AddLine(merge_group.ClipRect.Min, merge_clip_rect.Min, IM_COL32(255, 100, 0, 200))
@@ -3096,7 +3155,8 @@ function ImGui.TableMergeDrawChannels(table_)
local channel = channels[n + 1] local channel = channels[n + 1]
IM_ASSERT(channel._CmdBuffer.Size == 1) IM_ASSERT(channel._CmdBuffer.Size == 1)
ImVec4_Copy(channel._CmdBuffer.Data[1].ClipRect, merge_clip_rect:ToVec4()) local dst = channel._CmdBuffer.Data[1].ClipRect -- [Luau] scalar copy, no temporary ImVec4
dst.x = merge_clip_rect.Min.x; dst.y = merge_clip_rect.Min.y; dst.z = merge_clip_rect.Max.x; dst.w = merge_clip_rect.Max.y
dst_tmp[#dst_tmp + 1] = channel dst_tmp[#dst_tmp + 1] = channel
end end
n = n + 1 n = n + 1
@@ -3122,6 +3182,7 @@ function ImGui.TableMergeDrawChannels(table_)
for i = 1, #dst_tmp do for i = 1, #dst_tmp do
channels[LEADING_DRAW_CHANNELS + i] = dst_tmp[i] channels[LEADING_DRAW_CHANNELS + i] = dst_tmp[i]
end end
table.clear(dst_tmp) -- [Luau] don't keep channel references alive in the scratch
end end
end end
@@ -3152,9 +3213,9 @@ function ImGui.TableDrawBorders(table_)
-- Draw inner border and resizing feedback -- Draw inner border and resizing feedback
local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent) local table_instance = ImGui.TableGetInstanceData(table_, table_.InstanceCurrent)
local border_size = TABLE_BORDER_SIZE local border_size = TABLE_BORDER_SIZE
local draw_y1 = ImMax(table_.InnerRect.Min.y, ((table_.FreezeRowsCount >= 1) and table_.InnerRect.Min.y or table_.WorkRect.Min.y) + table_.AngledHeadersHeight) + ((band(table_.Flags, ImGuiTableFlags.BordersOuterH) ~= 0) and border_size or 0.0) local draw_y1 = mathMax(table_.InnerRect.Min.y, ((table_.FreezeRowsCount >= 1) and table_.InnerRect.Min.y or table_.WorkRect.Min.y) + table_.AngledHeadersHeight) + ((band(table_.Flags, ImGuiTableFlags.BordersOuterH) ~= 0) and border_size or 0.0)
local draw_y2_body = table_.InnerRect.Max.y local draw_y2_body = table_.InnerRect.Max.y
local draw_y2_head = table_.IsUsingHeaders and ImMin(table_.InnerRect.Max.y, ((table_.FreezeRowsCount >= 1) and table_.InnerRect.Min.y or table_.WorkRect.Min.y) + table_instance.LastTopHeadersRowHeight) or draw_y1 local draw_y2_head = table_.IsUsingHeaders and mathMin(table_.InnerRect.Max.y, ((table_.FreezeRowsCount >= 1) and table_.InnerRect.Min.y or table_.WorkRect.Min.y) + table_instance.LastTopHeadersRowHeight) or draw_y1
if band(table_.Flags, ImGuiTableFlags.BordersInnerV) ~= 0 then if band(table_.Flags, ImGuiTableFlags.BordersInnerV) ~= 0 then
for order_n = 0, table_.ColumnsCount - 1 do for order_n = 0, table_.ColumnsCount - 1 do
repeat repeat
@@ -3310,7 +3371,7 @@ function ImGui.TableSetColumnSortDirection(column_n, sort_direction, append_to_s
local sort_order_max = 0 local sort_order_max = 0
if append_to_sort_specs then if append_to_sort_specs then
for other_column_n = 0, table_.ColumnsCount - 1 do for other_column_n = 0, table_.ColumnsCount - 1 do
sort_order_max = ImMax(sort_order_max, table_.Columns[other_column_n].SortOrder) sort_order_max = mathMax(sort_order_max, table_.Columns[other_column_n].SortOrder)
end end
end end
@@ -3468,7 +3529,7 @@ function ImGui.TableGetHeaderRowHeight()
for column_n = 0, table_.ColumnsCount - 1 do for column_n = 0, table_.ColumnsCount - 1 do
if BitTest(table_.EnabledMaskByIndex, column_n) then if BitTest(table_.EnabledMaskByIndex, column_n) then
if band(table_.Columns[column_n].Flags, ImGuiTableColumnFlags.NoHeaderLabel) == 0 then if band(table_.Columns[column_n].Flags, ImGuiTableColumnFlags.NoHeaderLabel) == 0 then
row_height = ImMax(row_height, ImGui.CalcTextSize(ImGui.TableGetColumnName(table_, column_n)).y) row_height = mathMax(row_height, ImGui.CalcTextSize(ImGui.TableGetColumnName(table_, column_n)).y)
end end
end end
end end
@@ -3482,7 +3543,7 @@ function ImGui.TableGetHeaderAngledMaxLabelWidth()
for column_n = 0, table_.ColumnsCount - 1 do for column_n = 0, table_.ColumnsCount - 1 do
if BitTest(table_.EnabledMaskByIndex, column_n) then if BitTest(table_.EnabledMaskByIndex, column_n) then
if band(table_.Columns[column_n].Flags, ImGuiTableColumnFlags.AngledHeader) ~= 0 then if band(table_.Columns[column_n].Flags, ImGuiTableColumnFlags.AngledHeader) ~= 0 then
width = ImMax(width, ImGui.CalcTextSize(ImGui.TableGetColumnName(table_, column_n), nil, true).x) width = mathMax(width, ImGui.CalcTextSize(ImGui.TableGetColumnName(table_, column_n), nil, true).x)
end end
end end
end end
@@ -3561,8 +3622,11 @@ function ImGui.TableHeader(label)
-- If we already got a row height, there's use that. -- If we already got a row height, there's use that.
-- FIXME-TABLE: Padding problem if the correct outer-padding CellBgRect strays off our ClipRect? -- FIXME-TABLE: Padding problem if the correct outer-padding CellBgRect strays off our ClipRect?
local cell_r = ImGui.TableGetCellBgRect(table_, column_n) -- [Luau] scalar version of: cell_r = TableGetCellBgRect(table_, column_n) (Min.x/Min.y/Max.x/Max.y -> cell_x1/cell_y1/cell_x2/cell_y2)
local label_height = ImMax(label_size.y, table_.RowMinHeight - table_.RowCellPaddingY * 2.0) local cell_x1 = mathMax(column.MinX, table_.WorkRect.Min.x)
local cell_x2 = mathMin(column.MaxX, table_.WorkRect.Max.x)
local cell_y1, cell_y2 = table_.RowPosY1, table_.RowPosY2
local label_height = mathMax(label_size.y, table_.RowMinHeight - table_.RowCellPaddingY * 2.0)
-- Calculate ideal size for sort order arrow -- Calculate ideal size for sort order arrow
local w_arrow = 0.0 local w_arrow = 0.0
@@ -3583,12 +3647,12 @@ function ImGui.TableHeader(label)
-- We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considered for merging. -- We feed our unclipped width to the column without writing on CursorMaxPos, so that column is still considered for merging.
local max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow local max_pos_x = label_pos.x + label_size.x + w_sort_text + w_arrow
column.ContentMaxXHeadersUsed = ImMax(column.ContentMaxXHeadersUsed, sort_arrow and cell_r.Max.x or ImMin(max_pos_x, cell_r.Max.x)) column.ContentMaxXHeadersUsed = mathMax(column.ContentMaxXHeadersUsed, sort_arrow and cell_x2 or mathMin(max_pos_x, cell_x2))
column.ContentMaxXHeadersIdeal = ImMax(column.ContentMaxXHeadersIdeal, max_pos_x) column.ContentMaxXHeadersIdeal = mathMax(column.ContentMaxXHeadersIdeal, max_pos_x)
-- Keep header highlighted when context menu is open. -- Keep header highlighted when context menu is open.
local id = window:GetID(label) local id = window:GetID(label)
local bb = ImRect(cell_r.Min.x, cell_r.Min.y, cell_r.Max.x, ImMax(cell_r.Max.y, cell_r.Min.y + label_height + g.Style.CellPadding.y * 2.0)) local bb = ImRect(cell_x1, cell_y1, cell_x2, mathMax(cell_y2, cell_y1 + label_height + g.Style.CellPadding.y * 2.0))
ImGui.ItemSize(ImVec2(0.0, label_height)) -- Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal ImGui.ItemSize(ImVec2(0.0, label_height)) -- Don't declare unclipped width, it'll be fed ContentMaxPosHeadersIdeal
if not ImGui.ItemAdd(bb, id) then if not ImGui.ItemAdd(bb, id) then
return return
@@ -3626,13 +3690,13 @@ function ImGui.TableHeader(label)
-- ... 2 3 4 ... 2 3 4 (Display order) -- ... 2 3 4 ... 2 3 4 (Display order)
-- - The other constraints are enforced by TableQueueSetColumnDisplayOrder() which might early out. -- - The other constraints are enforced by TableQueueSetColumnDisplayOrder() which might early out.
table_.InstanceInteracted = table_.InstanceCurrent table_.InstanceInteracted = table_.InstanceCurrent
if g.IO.MouseDelta.x < 0.0 and g.IO.MousePos.x < cell_r.Min.x then if g.IO.MouseDelta.x < 0.0 and g.IO.MousePos.x < cell_x1 then
local prev_column = (column.PrevEnabledColumn ~= -1) and table_.Columns[column.PrevEnabledColumn] or nil local prev_column = (column.PrevEnabledColumn ~= -1) and table_.Columns[column.PrevEnabledColumn] or nil
if prev_column then if prev_column then
ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, prev_column.DisplayOrder) ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, prev_column.DisplayOrder)
end end
end end
if g.IO.MouseDelta.x > 0.0 and g.IO.MousePos.x > cell_r.Max.x then if g.IO.MouseDelta.x > 0.0 and g.IO.MousePos.x > cell_x2 then
local next_column = (column.NextEnabledColumn ~= -1) and table_.Columns[column.NextEnabledColumn] or nil local next_column = (column.NextEnabledColumn ~= -1) and table_.Columns[column.NextEnabledColumn] or nil
if next_column then if next_column then
ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, next_column.DisplayOrder) ImGui.TableQueueSetColumnDisplayOrder(table_, column_n, next_column.DisplayOrder)
@@ -3643,10 +3707,10 @@ function ImGui.TableHeader(label)
-- Sort order arrow -- Sort order arrow
-- - Only clip label for a visible arrow. Auto-fit still accounts for a possible arrow, but when -- - Only clip label for a visible arrow. Auto-fit still accounts for a possible arrow, but when
-- manually sized smaller we don't clip the label for the sake of an arrow that isn't displayed. -- manually sized smaller we don't clip the label for the sake of an arrow that isn't displayed.
local ellipsis_max = ImMax(cell_r.Max.x - (sort_arrow and (w_arrow + w_sort_text) or 0.0), label_pos.x) local ellipsis_max = mathMax(cell_x2 - (sort_arrow and (w_arrow + w_sort_text) or 0.0), label_pos.x)
if band(table_.Flags, ImGuiTableFlags.Sortable) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoSort) == 0 then if band(table_.Flags, ImGuiTableFlags.Sortable) ~= 0 and band(column.Flags, ImGuiTableColumnFlags.NoSort) == 0 then
if column.SortOrder ~= -1 then if column.SortOrder ~= -1 then
local x = ImMax(cell_r.Min.x, cell_r.Max.x - w_arrow - w_sort_text) local x = mathMax(cell_x1, cell_x2 - w_arrow - w_sort_text)
local y = label_pos.y local y = label_pos.y
if column.SortOrder > 0 then if column.SortOrder > 0 then
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.Text, 0.70)) ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.Text, 0.70))
+100 -70
View File
@@ -73,6 +73,8 @@ local IM_U32_MAX = UINT_MAX -- (0xFFFFFFFF)
--- @param text string --- @param text string
--- @param text_end? int --- @param text_end? int
--- @param flags? ImGuiTextFlags --- @param flags? ImGuiTextFlags
local TextEx_bb = ImRect() -- [Luau] scratch, never escapes TextEx()
@native @native
function ImGui.TextEx(text, text_end, flags) function ImGui.TextEx(text, text_end, flags)
if not flags then flags = 0 end if not flags then flags = 0 end
@@ -94,23 +96,29 @@ function ImGui.TextEx(text, text_end, flags)
text_end = #text + 1 text_end = #text + 1
end end
local text_pos = ImVec2(window.DC.CursorPos.x, window.DC.CursorPos.y + window.DC.CurrLineTextBaseOffset) local dc = window.DC
local wrap_pos_x = window.DC.TextWrapPos local wrap_pos_x = dc.TextWrapPos
local wrap_enabled = (wrap_pos_x >= 0.0) local wrap_enabled = (wrap_pos_x >= 0.0)
if (text_end - 1 <= 2000) or wrap_enabled then if (text_end - 1 <= 2000) or wrap_enabled then
-- Common case -- Common case
local wrap_width = wrap_enabled and ImGui.CalcWrapWidthForPos(window.DC.CursorPos, wrap_pos_x) or 0.0 -- [Luau] scalar text_pos, scratch bb (ItemAdd() copies it, RenderTextWrapped() only reads it)
local text_size = ImGui.CalcTextSize(text, text_end, false, wrap_width) local text_pos_x, text_pos_y = dc.CursorPos.x, dc.CursorPos.y + dc.CurrLineTextBaseOffset
local wrap_width = wrap_enabled and ImGui.CalcWrapWidthForPos(dc.CursorPos, wrap_pos_x) or 0.0
local text_size = ImGui.CalcTextSizeEx(text, 1, text_end, false, wrap_width)
local bb = ImRect(text_pos, text_pos + text_size) local bb = TextEx_bb
local bb_min, bb_max = bb.Min, bb.Max
bb_min.x = text_pos_x; bb_min.y = text_pos_y
bb_max.x = text_pos_x + text_size.x; bb_max.y = text_pos_y + text_size.y
ImGui.ItemSize(text_size, 0.0) ImGui.ItemSize(text_size, 0.0)
if not ImGui.ItemAdd(bb, 0) then if not ImGui.ItemAdd(bb, 0) then
return return
end end
-- Render (we don't hide text after ## in this end-user function) -- Render (we don't hide text after ## in this end-user function)
ImGui.RenderTextWrapped(bb.Min, text, text_end, wrap_width) ImGui.RenderTextWrapped(bb_min, text, text_end, wrap_width)
else else
local text_pos = ImVec2(dc.CursorPos.x, dc.CursorPos.y + dc.CurrLineTextBaseOffset)
-- Long text! -- Long text!
-- Perform manual coarse clipping to optimize for long multi-line text -- Perform manual coarse clipping to optimize for long multi-line text
-- - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled. -- - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
@@ -197,7 +205,8 @@ end
--- @param ... any --- @param ... any
@native @native
function ImGui.TextV(fmt, ...) function ImGui.TextV(fmt, ...)
local window = ImGui.GetCurrentWindow() local window = GImGui.CurrentWindow -- [Luau] inline GetCurrentWindow()
window.WriteAccessed = true
if window.SkipItems then if window.SkipItems then
return return
end end
@@ -340,19 +349,18 @@ function ImGui.BulletText(fmt, ...)
local text_end = #text + 1 local text_end = #text + 1
local label_size = ImGui.CalcTextSize(text, text_end, false) local label_size = ImGui.CalcTextSize(text, text_end, false)
local total_size = ImVec2(g.FontSize + ((label_size.x > 0.0) and (label_size.x + style.FramePadding.x * 2) or 0.0), label_size.y) -- Empty text doesn't add padding local total_size = ImVec2(g.FontSize + ((label_size.x > 0.0) and (label_size.x + style.FramePadding.x * 2) or 0.0), label_size.y) -- Empty text doesn't add padding
local pos = ImVec2() -- [Luau] scalar pos, no temporary ImVec2
ImVec2_Copy(pos, window.DC.CursorPos) local pos_x, pos_y = window.DC.CursorPos.x, window.DC.CursorPos.y + window.DC.CurrLineTextBaseOffset
pos.y = pos.y + window.DC.CurrLineTextBaseOffset
ImGui.ItemSize(total_size, 0.0) ImGui.ItemSize(total_size, 0.0)
local bb = ImRect(pos, pos + total_size) local bb = ImRect(pos_x, pos_y, pos_x + total_size.x, pos_y + total_size.y)
if not ImGui.ItemAdd(bb, 0) then if not ImGui.ItemAdd(bb, 0) then
return return
end end
-- Render -- Render
local text_col = ImGui.GetColorU32(ImGuiCol.Text) local text_col = ImGui.GetColorU32(ImGuiCol.Text)
ImGui.RenderBullet(window.DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5, g.FontSize * 0.5), text_col) ImGui.RenderBullet(window.DrawList, ImVec2(bb.Min.x + (style.FramePadding.x + g.FontSize * 0.5), bb.Min.y + g.FontSize * 0.5), text_col)
ImGui.RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0), text, 1, text_end, false) ImGui.RenderText(ImVec2(bb.Min.x + (g.FontSize + style.FramePadding.x * 2), bb.Min.y + 0.0), text, 1, text_end, false)
end end
-- va_list variants: identical to the varargs versions in Lua -- va_list variants: identical to the varargs versions in Lua
@@ -730,6 +738,8 @@ end
--- @param size_arg? ImVec2 --- @param size_arg? ImVec2
--- @param flags? ImGuiButtonFlags --- @param flags? ImGuiButtonFlags
--- @return bool --- @return bool
local ButtonEx_text_min, ButtonEx_text_max = ImVec2(), ImVec2()
@native @native
function ImGui.ButtonEx(label, size_arg, flags) function ImGui.ButtonEx(label, size_arg, flags)
if size_arg == nil then size_arg = ImVec2(0, 0) end if size_arg == nil then size_arg = ImVec2(0, 0) end
@@ -746,15 +756,16 @@ function ImGui.ButtonEx(label, size_arg, flags)
local label_end = ImGui.FindRenderedTextEnd(label) local label_end = ImGui.FindRenderedTextEnd(label)
local label_size = ImGui.CalcTextSize(label, label_end, false) local label_size = ImGui.CalcTextSize(label, label_end, false)
local pos = ImVec2() -- Don't modify the cursor! -- [Luau] scalar pos, no temporary ImVec2
ImVec2_Copy(pos, window.DC.CursorPos) local pos_x, pos_y = window.DC.CursorPos.x, window.DC.CursorPos.y -- Don't modify the cursor!
if bit32.band(flags, ImGuiButtonFlags.AlignTextBaseLine) ~= 0 and style.FramePadding.y < window.DC.CurrLineTextBaseOffset then -- Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) local frame_padding = style.FramePadding
pos.y = pos.y + window.DC.CurrLineTextBaseOffset - style.FramePadding.y if bit32.band(flags, ImGuiButtonFlags.AlignTextBaseLine) ~= 0 and frame_padding.y < window.DC.CurrLineTextBaseOffset then -- Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)
pos_y = pos_y + window.DC.CurrLineTextBaseOffset - frame_padding.y
end end
local size = ImGui.CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0, label_size.y + style.FramePadding.y * 2.0) local size = ImGui.CalcItemSize(size_arg, label_size.x + frame_padding.x * 2.0, label_size.y + frame_padding.y * 2.0)
local bb = ImRect(pos, pos + size) local bb = ImRect(pos_x, pos_y, pos_x + size.x, pos_y + size.y)
ImGui.ItemSize(size, style.FramePadding.y) ImGui.ItemSize(size, frame_padding.y)
if not ImGui.ItemAdd(bb, id) then if not ImGui.ItemAdd(bb, id) then
return false return false
end end
@@ -776,7 +787,10 @@ function ImGui.ButtonEx(label, size_arg, flags)
-- if (g.LogEnabled) -- if (g.LogEnabled)
-- LogSetNextTextDecoration("[", "]"); -- LogSetNextTextDecoration("[", "]");
ImGui.RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, label_end, label_size, style.ButtonTextAlign, bb) local text_min, text_max = ButtonEx_text_min, ButtonEx_text_max -- [Luau] scratch: RenderTextClipped() only reads them
text_min.x = bb.Min.x + frame_padding.x; text_min.y = bb.Min.y + frame_padding.y
text_max.x = bb.Max.x - frame_padding.x; text_max.y = bb.Max.y - frame_padding.y
ImGui.RenderTextClipped(text_min, text_max, label, label_end, label_size, style.ButtonTextAlign, bb)
-- Automatically close popups -- Automatically close popups
--if (pressed && !(flags & ImGuiButtonFlags.DontClosePopups) && (window->Flags & ImGuiWindowFlags.Popup)) --if (pressed && !(flags & ImGuiButtonFlags.DontClosePopups) && (window->Flags & ImGuiWindowFlags.Popup))
@@ -1179,12 +1193,13 @@ function ImGui.Scrollbar(axis)
-- Calculate scrollbar bounding box -- Calculate scrollbar bounding box
local bb = ImGui.GetWindowScrollbarRect(window, axis) local bb = ImGui.GetWindowScrollbarRect(window, axis)
local rounding_corners = ImGui.CalcRoundingFlagsForRectInRect(bb, window:Rect(), g.Style.WindowBorderSize) local rounding_corners = ImGui.CalcRoundingFlagsForRectInRect(bb, window:Rect(), g.Style.WindowBorderSize)
local size_visible = window.InnerRect.Max[axis] - window.InnerRect.Min[axis] local k = (axis == ImGuiAxis.X) and "x" or "y" -- [Luau] named key: ImVec2[axis] goes through IM_VEC2.__index
local size_contents = window.ContentSize[axis] + window.WindowPadding[axis] * 2.0 local size_visible = window.InnerRect.Max[k] - window.InnerRect.Min[k]
local scroll = window.Scroll[axis] local size_contents = window.ContentSize[k] + window.WindowPadding[k] * 2.0
local scroll = window.Scroll[k]
local held local held
held, scroll = ImGui.ScrollbarEx(bb, id, axis, scroll, size_visible, size_contents, rounding_corners) held, scroll = ImGui.ScrollbarEx(bb, id, axis, scroll, size_visible, size_contents, rounding_corners)
window.Scroll[axis] = scroll window.Scroll[k] = scroll
end end
-- - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples -- - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
@@ -1316,9 +1331,8 @@ function ImGui.Checkbox(label, v)
local label_end = ImGui.FindRenderedTextEnd(label) local label_end = ImGui.FindRenderedTextEnd(label)
local label_size = ImGui.CalcTextSize(label, label_end, false) local label_size = ImGui.CalcTextSize(label, label_end, false)
local square_sz = ImGui.GetFrameHeight() local square_sz = g.FontSize + style.FramePadding.y * 2.0 -- [Luau] inline GetFrameHeight()
local pos = ImVec2() local pos_x, pos_y = window.DC.CursorPos.x, window.DC.CursorPos.y -- [Luau] scalar pos, no temporary ImVec2
ImVec2_Copy(pos, window.DC.CursorPos)
local total_width local total_width
if label_size.x > 0.0 then if label_size.x > 0.0 then
@@ -1326,7 +1340,7 @@ function ImGui.Checkbox(label, v)
else else
total_width = square_sz total_width = square_sz
end end
local total_bb = ImRect(pos, pos + ImVec2(total_width, label_size.y + style.FramePadding.y * 2.0)) local total_bb = ImRect(pos_x, pos_y, pos_x + total_width, pos_y + (label_size.y + style.FramePadding.y * 2.0))
ImGui.ItemSize(total_bb, style.FramePadding.y) ImGui.ItemSize(total_bb, style.FramePadding.y)
local is_visible = ImGui.ItemAdd(total_bb, id) local is_visible = ImGui.ItemAdd(total_bb, id)
local is_multi_select = (bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.IsMultiSelect) ~= 0) local is_multi_select = (bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.IsMultiSelect) ~= 0)
@@ -1358,7 +1372,7 @@ function ImGui.Checkbox(label, v)
ImGui.MarkItemEdited(id) ImGui.MarkItemEdited(id)
end end
local check_bb = ImRect(pos, pos + ImVec2(square_sz, square_sz)) local check_bb = ImRect(pos_x, pos_y, pos_x + square_sz, pos_y + square_sz)
local mixed_value = (bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.MixedValue) ~= 0) local mixed_value = (bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.MixedValue) ~= 0)
if is_visible then if is_visible then
ImGui.RenderNavCursor(total_bb, id) ImGui.RenderNavCursor(total_bb, id)
@@ -1379,12 +1393,12 @@ function ImGui.Checkbox(label, v)
end end
end end
local label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y)
if g.LogEnabled then if g.LogEnabled then
-- ImGui.LogRenderedText(label_pos, mixed_value and "[~]" or (v and "[x]" or "[ ]")) -- ImGui.LogRenderedText(label_pos, mixed_value and "[~]" or (v and "[x]" or "[ ]"))
end end
if is_visible and label_size.x > 0.0 then if is_visible and label_size.x > 0.0 then
local label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y) -- [Luau] only built when used
ImGui.RenderText(label_pos, label, 1, label_end, false) ImGui.RenderText(label_pos, label, 1, label_end, false)
end end
@@ -1435,11 +1449,10 @@ function ImGui.RadioButtonEx(label, active)
local label_end = ImGui.FindRenderedTextEnd(label) local label_end = ImGui.FindRenderedTextEnd(label)
local label_size = ImGui.CalcTextSize(label, label_end, false) local label_size = ImGui.CalcTextSize(label, label_end, false)
local square_sz = ImGui.GetFrameHeight() local square_sz = g.FontSize + style.FramePadding.y * 2.0 -- [Luau] inline GetFrameHeight()
local pos = ImVec2() local pos_x, pos_y = window.DC.CursorPos.x, window.DC.CursorPos.y -- [Luau] scalar pos, no temporary ImVec2
ImVec2_Copy(pos, window.DC.CursorPos) local check_bb = ImRect(pos_x, pos_y, pos_x + square_sz, pos_y + square_sz)
local check_bb = ImRect(pos, pos + ImVec2(square_sz, square_sz)) local total_bb = ImRect(pos_x, pos_y, pos_x + (square_sz + (label_size.x > 0.0 and style.ItemInnerSpacing.x + label_size.x or 0.0)), pos_y + (label_size.y + style.FramePadding.y * 2.0))
local total_bb = ImRect(pos, pos + ImVec2(square_sz + (label_size.x > 0.0 and style.ItemInnerSpacing.x + label_size.x or 0.0), label_size.y + style.FramePadding.y * 2.0))
ImGui.ItemSize(total_bb, style.FramePadding.y) ImGui.ItemSize(total_bb, style.FramePadding.y)
if not ImGui.ItemAdd(total_bb, id) then if not ImGui.ItemAdd(total_bb, id) then
return false return false
@@ -1683,12 +1696,15 @@ end
-- - Separator() -- - Separator()
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
local LAYOUT_ZERO_SIZE = ImVec2(0, 0) -- [Luau] shared read-only: ItemSize() only reads it
local LayoutHelpers_size = ImVec2() -- [Luau] scratch for ItemSize() arguments
function ImGui.Spacing() function ImGui.Spacing()
local window = ImGui.GetCurrentWindow() local window = ImGui.GetCurrentWindow()
if window.SkipItems then if window.SkipItems then
return return
end end
ImGui.ItemSize(ImVec2(0, 0)) ImGui.ItemSize(LAYOUT_ZERO_SIZE)
end end
--- @param size ImVec2 --- @param size ImVec2
@@ -1698,7 +1714,8 @@ function ImGui.Dummy(size)
return return
end end
local bb = ImRect(window.DC.CursorPos, window.DC.CursorPos + size) local cursor = window.DC.CursorPos
local bb = ImRect(cursor.x, cursor.y, cursor.x + size.x, cursor.y + size.y) -- [Luau] no temporary ImVec2
ImGui.ItemSize(size) ImGui.ItemSize(size)
ImGui.ItemAdd(bb, 0) ImGui.ItemAdd(bb, 0)
end end
@@ -1755,8 +1772,9 @@ function ImGui.SeparatorEx(flags, thickness)
-- Vertical separator, for menu bars (use current line height). -- Vertical separator, for menu bars (use current line height).
local y1 = window.DC.CursorPos.y local y1 = window.DC.CursorPos.y
local y2 = window.DC.CursorPos.y + window.DC.CurrLineSize.y local y2 = window.DC.CursorPos.y + window.DC.CurrLineSize.y
local bb = ImRect(ImVec2(window.DC.CursorPos.x, y1), ImVec2(window.DC.CursorPos.x + thickness, y2)) local bb = ImRect(window.DC.CursorPos.x, y1, window.DC.CursorPos.x + thickness, y2) -- [Luau] no temporary ImVec2
ImGui.ItemSize(ImVec2(thickness, 0.0)) local item_size = LayoutHelpers_size; item_size.x = thickness; item_size.y = 0.0
ImGui.ItemSize(item_size)
if not ImGui.ItemAdd(bb, 0) then if not ImGui.ItemAdd(bb, 0) then
return return
end end
@@ -1788,8 +1806,9 @@ function ImGui.SeparatorEx(flags, thickness)
-- This was mostly chosen to allow backward compatibility with user's code assuming zero-height when calculating height for layout (e.g. bottom alignment of a status bar). -- This was mostly chosen to allow backward compatibility with user's code assuming zero-height when calculating height for layout (e.g. bottom alignment of a status bar).
-- In order to handle scaling we need to scale separator thickness and it would not makes sense to have a disparity depending on height. -- In order to handle scaling we need to scale separator thickness and it would not makes sense to have a disparity depending on height.
----float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change. ----float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change.
local bb = ImRect(ImVec2(x1, window.DC.CursorPos.y), ImVec2(x2, window.DC.CursorPos.y + thickness)) local bb = ImRect(x1, window.DC.CursorPos.y, x2, window.DC.CursorPos.y + thickness) -- [Luau] no temporary ImVec2
ImGui.ItemSize(ImVec2(0.0, thickness)) local item_size = LayoutHelpers_size; item_size.x = 0.0; item_size.y = thickness
ImGui.ItemSize(item_size)
if ImGui.ItemAdd(bb, 0) then if ImGui.ItemAdd(bb, 0) then
-- Draw -- Draw
@@ -3380,6 +3399,7 @@ end
-- [Luau] memo of string.format(format, value) for drag/slider value text (same values are redrawn every frame). -- [Luau] memo of string.format(format, value) for drag/slider value text (same values are redrawn every frame).
-- ponytail: whole-memo reset at 4096 entries, LRU if values churn -- ponytail: whole-memo reset at 4096 entries, LRU if values churn
local fmt_memo, fmt_memo_n = {}, 0 local fmt_memo, fmt_memo_n = {}, 0
local SliderScalar_total_bb, SliderScalar_grab_bb = ImRect(), ImRect() -- [Luau] scratch for SliderScalar()/DragScalar(), never escape
local DRAG_TEXT_ALIGN = ImVec2(0.5, 0.5) local DRAG_TEXT_ALIGN = ImVec2(0.5, 0.5)
function FormatCached(format, v) function FormatCached(format, v)
local m = fmt_memo[format] local m = fmt_memo[format]
@@ -3411,8 +3431,11 @@ function ImGui.DragScalar(label, data_type, data, v_speed, min, max, format, fla
local label_end = ImGui.FindRenderedTextEnd(label) local label_end = ImGui.FindRenderedTextEnd(label)
local label_size = ImGui.CalcTextSize(label, label_end, false) local label_size = ImGui.CalcTextSize(label, label_end, false)
local frame_bb = ImRect(window.DC.CursorPos, window.DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0)) local cursor = window.DC.CursorPos
local total_bb = ImRect(frame_bb.Min, frame_bb.Max + ImVec2((label_size.x > 0.0) and (style.ItemInnerSpacing.x + label_size.x) or 0.0, 0.0)) local frame_bb = ImRect(cursor.x, cursor.y, cursor.x + w, cursor.y + (label_size.y + style.FramePadding.y * 2.0)) -- [Luau] no temporary ImVec2
local total_bb = SliderScalar_total_bb -- [Luau] scratch: ItemSize()/ItemAdd() only read it
total_bb.Min.x = frame_bb.Min.x; total_bb.Min.y = frame_bb.Min.y
total_bb.Max.x = frame_bb.Max.x + ((label_size.x > 0.0) and (style.ItemInnerSpacing.x + label_size.x) or 0.0); total_bb.Max.y = frame_bb.Max.y + 0.0
local temp_input_allowed = (bit32.band(flags, ImGuiSliderFlags.NoInput) == 0) local temp_input_allowed = (bit32.band(flags, ImGuiSliderFlags.NoInput) == 0)
ImGui.ItemSize(total_bb, style.FramePadding.y) ImGui.ItemSize(total_bb, style.FramePadding.y)
@@ -3840,21 +3863,22 @@ function ImGui.SliderBehaviorT(TYPE, SIGNEDTYPE, bb, id, data_type, v, v_min, v_
local style = g.Style local style = g.Style
local axis = (bit32.band(flags, ImGuiSliderFlags.Vertical) ~= 0) and ImGuiAxis.Y or ImGuiAxis.X local axis = (bit32.band(flags, ImGuiSliderFlags.Vertical) ~= 0) and ImGuiAxis.Y or ImGuiAxis.X
local axis_k = (axis == ImGuiAxis.X) and "x" or "y" -- [Luau] named key: ImVec2[axis] goes through IM_VEC2.__index
local is_logarithmic = bit32.band(flags, ImGuiSliderFlags.Logarithmic) ~= 0 local is_logarithmic = bit32.band(flags, ImGuiSliderFlags.Logarithmic) ~= 0
local is_floating_point = (data_type == ImGuiDataType.Float) or (data_type == ImGuiDataType.Double) local is_floating_point = (data_type == ImGuiDataType.Float) or (data_type == ImGuiDataType.Double)
local v_range_f = (v_min < v_max) and (v_max - v_min) or (v_min - v_max) -- We don't need high precision for what we do with it. local v_range_f = (v_min < v_max) and (v_max - v_min) or (v_min - v_max) -- We don't need high precision for what we do with it.
-- Calculate bounds -- Calculate bounds
local grab_padding = 2.0 -- FIXME: Should be part of style. local grab_padding = 2.0 -- FIXME: Should be part of style.
local slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0 local slider_sz = (bb.Max[axis_k] - bb.Min[axis_k]) - grab_padding * 2.0
local grab_sz = style.GrabMinSize local grab_sz = style.GrabMinSize
if not is_floating_point and v_range_f >= 0.0 then -- v_range_f < 0 may happen on integer overflows if not is_floating_point and v_range_f >= 0.0 then -- v_range_f < 0 may happen on integer overflows
grab_sz = ImMax(slider_sz / (v_range_f + 1), style.GrabMinSize) -- For integer sliders: if possible have the grab size represent 1 unit grab_sz = ImMax(slider_sz / (v_range_f + 1), style.GrabMinSize) -- For integer sliders: if possible have the grab size represent 1 unit
end end
grab_sz = ImMin(grab_sz, slider_sz) grab_sz = ImMin(grab_sz, slider_sz)
local slider_usable_sz = slider_sz - grab_sz local slider_usable_sz = slider_sz - grab_sz
local slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5 local slider_usable_pos_min = bb.Min[axis_k] + grab_padding + grab_sz * 0.5
local slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5 local slider_usable_pos_max = bb.Max[axis_k] - grab_padding - grab_sz * 0.5
local logarithmic_zero_epsilon = 0.0 -- Only valid when is_logarithmic is true local logarithmic_zero_epsilon = 0.0 -- Only valid when is_logarithmic is true
local zero_deadzone_halfsize = 0.0 -- Only valid when is_logarithmic is true local zero_deadzone_halfsize = 0.0 -- Only valid when is_logarithmic is true
@@ -3874,7 +3898,7 @@ function ImGui.SliderBehaviorT(TYPE, SIGNEDTYPE, bb, id, data_type, v, v_min, v_
if not g.IO.MouseDown[0] then if not g.IO.MouseDown[0] then
ImGui.ClearActiveID() ImGui.ClearActiveID()
else else
local mouse_abs_pos = g.IO.MousePos[axis] local mouse_abs_pos = g.IO.MousePos[axis_k]
if g.ActiveIdIsJustActivated then if g.ActiveIdIsJustActivated then
local grab_t = ImGui.ScaleRatioFromValueT(data_type, v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize) local grab_t = ImGui.ScaleRatioFromValueT(data_type, v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize)
if axis == ImGuiAxis.Y then if axis == ImGuiAxis.Y then
@@ -3977,8 +4001,10 @@ function ImGui.SliderBehaviorT(TYPE, SIGNEDTYPE, bb, id, data_type, v, v_min, v_
end end
end end
-- [Luau] write out_grab_bb fields directly, no temporary ImRect
local og_min, og_max = out_grab_bb.Min, out_grab_bb.Max
if slider_sz < 1.0 then if slider_sz < 1.0 then
ImRect_Copy(out_grab_bb, ImRect(bb.Min, bb.Min)) og_min.x = bb.Min.x; og_min.y = bb.Min.y; og_max.x = bb.Min.x; og_max.y = bb.Min.y
else else
-- Output grab position so it can be displayed by the caller -- Output grab position so it can be displayed by the caller
local grab_t = ImGui.ScaleRatioFromValueT(data_type, v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize) local grab_t = ImGui.ScaleRatioFromValueT(data_type, v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize)
@@ -3987,9 +4013,9 @@ function ImGui.SliderBehaviorT(TYPE, SIGNEDTYPE, bb, id, data_type, v, v_min, v_
end end
local grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t) local grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t)
if axis == ImGuiAxis.X then if axis == ImGuiAxis.X then
ImRect_Copy(out_grab_bb, ImRect(grab_pos - grab_sz * 0.5, bb.Min.y + grab_padding, grab_pos + grab_sz * 0.5, bb.Max.y - grab_padding)) og_min.x = grab_pos - grab_sz * 0.5; og_min.y = bb.Min.y + grab_padding; og_max.x = grab_pos + grab_sz * 0.5; og_max.y = bb.Max.y - grab_padding
else else
ImRect_Copy(out_grab_bb, ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz * 0.5, bb.Max.x - grab_padding, grab_pos + grab_sz * 0.5)) og_min.x = bb.Min.x + grab_padding; og_min.y = grab_pos - grab_sz * 0.5; og_max.x = bb.Max.x - grab_padding; og_max.y = grab_pos + grab_sz * 0.5
end end
end end
@@ -4080,9 +4106,11 @@ function ImGui.SliderScalar(label, data_type, data, min, max, format, flags)
local label_end = ImGui.FindRenderedTextEnd(label) local label_end = ImGui.FindRenderedTextEnd(label)
local label_size = ImGui.CalcTextSize(label, label_end, false) local label_size = ImGui.CalcTextSize(label, label_end, false)
local frame_bb = ImRect(window.DC.CursorPos, window.DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0)) local cursor = window.DC.CursorPos
local frame_bb = ImRect(cursor.x, cursor.y, cursor.x + w, cursor.y + (label_size.y + style.FramePadding.y * 2.0)) -- [Luau] no temporary ImVec2
local total_bb_x = frame_bb.Max.x + ((label_size.x > 0.0) and (style.ItemInnerSpacing.x + label_size.x) or 0.0) local total_bb_x = frame_bb.Max.x + ((label_size.x > 0.0) and (style.ItemInnerSpacing.x + label_size.x) or 0.0)
local total_bb = ImRect(frame_bb.Min, ImVec2(total_bb_x, frame_bb.Max.y)) local total_bb = SliderScalar_total_bb -- [Luau] scratch: ItemSize()/ItemAdd() only read it
total_bb.Min.x = frame_bb.Min.x; total_bb.Min.y = frame_bb.Min.y; total_bb.Max.x = total_bb_x; total_bb.Max.y = frame_bb.Max.y
local temp_input_allowed = bit32.band(flags, ImGuiSliderFlags.NoInput) == 0 local temp_input_allowed = bit32.band(flags, ImGuiSliderFlags.NoInput) == 0
ImGui.ItemSize(total_bb, style.FramePadding.y) ImGui.ItemSize(total_bb, style.FramePadding.y)
@@ -4140,7 +4168,7 @@ function ImGui.SliderScalar(label, data_type, data, min, max, format, flags)
ImGui.RenderFrameBorder(frame_bb.Min, frame_bb.Max, g.Style.FrameRounding) ImGui.RenderFrameBorder(frame_bb.Min, frame_bb.Max, g.Style.FrameRounding)
-- Slider behavior -- Slider behavior
local grab_bb = ImRect() local grab_bb = SliderScalar_grab_bb -- [Luau] scratch: fully written by SliderBehavior(), only read below
local value_changed local value_changed
data, value_changed = ImGui.SliderBehavior(frame_bb, id, data_type, data, min, max, format, flags, grab_bb) data, value_changed = ImGui.SliderBehavior(frame_bb, id, data_type, data, min, max, format, flags, grab_bb)
if value_changed then if value_changed then
@@ -7554,11 +7582,12 @@ function ImGui.TreeNodeBehavior(id, flags, label, label_end)
-- When not framed, we vertically increase height up to typical framed widget height -- When not framed, we vertically increase height up to typical framed widget height
local display_frame = bit32.band(flags, ImGuiTreeNodeFlags.Framed) ~= 0 local display_frame = bit32.band(flags, ImGuiTreeNodeFlags.Framed) ~= 0
local use_frame_padding = display_frame or (bit32.band(flags, ImGuiTreeNodeFlags.FramePadding) ~= 0) local use_frame_padding = display_frame or (bit32.band(flags, ImGuiTreeNodeFlags.FramePadding) ~= 0)
local padding -- [Luau] scalar padding, no temporary ImVec2
local padding_x, padding_y
if use_frame_padding then if use_frame_padding then
padding = style.FramePadding padding_x, padding_y = style.FramePadding.x, style.FramePadding.y
else else
padding = ImVec2(style.FramePadding.x, ImMin(window.DC.CurrLineTextBaseOffset, style.FramePadding.y)) padding_x, padding_y = style.FramePadding.x, math.min(window.DC.CurrLineTextBaseOffset, style.FramePadding.y)
end end
if label_end == nil then if label_end == nil then
@@ -7566,18 +7595,18 @@ function ImGui.TreeNodeBehavior(id, flags, label, label_end)
end end
local label_size = ImGui.CalcTextSize(label, label_end, false) local label_size = ImGui.CalcTextSize(label, label_end, false)
local text_offset_x = g.FontSize + (display_frame and (padding.x * 3) or (padding.x * 2)) -- Collapsing arrow width + Spacing local text_offset_x = g.FontSize + (display_frame and (padding_x * 3) or (padding_x * 2)) -- Collapsing arrow width + Spacing
local text_offset_y = use_frame_padding and ImMax(style.FramePadding.y, window.DC.CurrLineTextBaseOffset) or window.DC.CurrLineTextBaseOffset -- Latch before ItemSize changes it local text_offset_y = use_frame_padding and ImMax(style.FramePadding.y, window.DC.CurrLineTextBaseOffset) or window.DC.CurrLineTextBaseOffset -- Latch before ItemSize changes it
local text_width = g.FontSize + label_size.x + padding.x * 2 -- Include collapsing arrow local text_width = g.FontSize + label_size.x + padding_x * 2 -- Include collapsing arrow
local frame_height = label_size.y + padding.y * 2 local frame_height = label_size.y + padding_y * 2
local span_all_columns = bit32.band(flags, ImGuiTreeNodeFlags.SpanAllColumns) ~= 0 and (g.CurrentTable ~= nil) local span_all_columns = bit32.band(flags, ImGuiTreeNodeFlags.SpanAllColumns) ~= 0 and (g.CurrentTable ~= nil)
local span_all_columns_label = bit32.band(flags, ImGuiTreeNodeFlags.LabelSpanAllColumns) ~= 0 and (g.CurrentTable ~= nil) local span_all_columns_label = bit32.band(flags, ImGuiTreeNodeFlags.LabelSpanAllColumns) ~= 0 and (g.CurrentTable ~= nil)
local frame_bb = ImRect() local frame_bb = ImRect()
frame_bb.Min.x = span_all_columns and window.ParentWorkRect.Min.x or (bit32.band(flags, ImGuiTreeNodeFlags.SpanFullWidth) ~= 0 and window.WorkRect.Min.x or window.DC.CursorPos.x) frame_bb.Min.x = span_all_columns and window.ParentWorkRect.Min.x or (bit32.band(flags, ImGuiTreeNodeFlags.SpanFullWidth) ~= 0 and window.WorkRect.Min.x or window.DC.CursorPos.x)
frame_bb.Min.y = window.DC.CursorPos.y + (text_offset_y - padding.y) frame_bb.Min.y = window.DC.CursorPos.y + (text_offset_y - padding_y)
frame_bb.Max.x = span_all_columns and window.ParentWorkRect.Max.x or (bit32.band(flags, ImGuiTreeNodeFlags.SpanLabelWidth) ~= 0 and window.DC.CursorPos.x + text_width + padding.x or window.WorkRect.Max.x) frame_bb.Max.x = span_all_columns and window.ParentWorkRect.Max.x or (bit32.band(flags, ImGuiTreeNodeFlags.SpanLabelWidth) ~= 0 and window.DC.CursorPos.x + text_width + padding_x or window.WorkRect.Max.x)
frame_bb.Max.y = window.DC.CursorPos.y + (text_offset_y - padding.y) + frame_height frame_bb.Max.y = window.DC.CursorPos.y + (text_offset_y - padding_y) + frame_height
if display_frame then if display_frame then
local outer_extend = IM_TRUNC(window.WindowPadding.x * 0.5) -- Framed header expand a little outside of current limits local outer_extend = IM_TRUNC(window.WindowPadding.x * 0.5) -- Framed header expand a little outside of current limits
frame_bb.Min.x = frame_bb.Min.x - outer_extend frame_bb.Min.x = frame_bb.Min.x - outer_extend
@@ -7585,7 +7614,8 @@ function ImGui.TreeNodeBehavior(id, flags, label, label_end)
end end
local text_pos = ImVec2(window.DC.CursorPos.x + text_offset_x, window.DC.CursorPos.y + text_offset_y) local text_pos = ImVec2(window.DC.CursorPos.x + text_offset_x, window.DC.CursorPos.y + text_offset_y)
ImGui.ItemSize(ImVec2(text_width, frame_height), padding.y) local item_size = LayoutHelpers_size; item_size.x = text_width; item_size.y = frame_height -- [Luau] scratch, ItemSize() only reads it
ImGui.ItemSize(item_size, padding_y)
-- For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing -- For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
local interact_bb = ImRect() local interact_bb = ImRect()
@@ -7669,7 +7699,7 @@ function ImGui.TreeNodeBehavior(id, flags, label, label_end)
-- allow browsing a tree while preserving selection with code implementing multi-selection patterns. -- allow browsing a tree while preserving selection with code implementing multi-selection patterns.
-- When clicking on the rest of the tree node we always disallow keyboard modifiers. -- When clicking on the rest of the tree node we always disallow keyboard modifiers.
local arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x local arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x
local arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0) + style.TouchExtraPadding.x local arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding_x * 2.0) + style.TouchExtraPadding.x
local is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 and g.IO.MousePos.x < arrow_hit_x2) local is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 and g.IO.MousePos.x < arrow_hit_x2)
local is_multi_select = bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.IsMultiSelect) ~= 0 local is_multi_select = bit32.band(g.LastItemData.ItemFlags, ImGuiItemFlags.IsMultiSelect) ~= 0
@@ -7784,9 +7814,9 @@ function ImGui.TreeNodeBehavior(id, flags, label, label_end)
if bit32.band(flags, ImGuiTreeNodeFlags.Bullet) ~= 0 then if bit32.band(flags, ImGuiTreeNodeFlags.Bullet) ~= 0 then
ImGui.RenderBullet(window.DrawList, ImVec2(text_pos.x - text_offset_x * 0.60, text_pos.y + g.FontSize * 0.5), text_col) ImGui.RenderBullet(window.DrawList, ImVec2(text_pos.x - text_offset_x * 0.60, text_pos.y + g.FontSize * 0.5), text_col)
elseif not is_leaf then elseif not is_leaf then
ImGui.RenderArrow(window.DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open and ((bit32.band(flags, ImGuiTreeNodeFlags.UpsideDownArrow) ~= 0) and ImGuiDir.Up or ImGuiDir.Down) or ImGuiDir.Right, 1.0) ImGui.RenderArrow(window.DrawList, ImVec2(text_pos.x - text_offset_x + padding_x, text_pos.y), text_col, is_open and ((bit32.band(flags, ImGuiTreeNodeFlags.UpsideDownArrow) ~= 0) and ImGuiDir.Up or ImGuiDir.Down) or ImGuiDir.Right, 1.0)
else -- Leaf without bullet, left-adjusted text else -- Leaf without bullet, left-adjusted text
text_pos.x = text_pos.x - (text_offset_x - padding.x) text_pos.x = text_pos.x - (text_offset_x - padding_x)
end end
if bit32.band(flags, ImGuiTreeNodeFlags.ClipLabelForTrailingButton) ~= 0 then if bit32.band(flags, ImGuiTreeNodeFlags.ClipLabelForTrailingButton) ~= 0 then
frame_bb.Max.x = frame_bb.Max.x - (g.FontSize + style.FramePadding.x) frame_bb.Max.x = frame_bb.Max.x - (g.FontSize + style.FramePadding.x)
@@ -7807,7 +7837,7 @@ function ImGui.TreeNodeBehavior(id, flags, label, label_end)
if bit32.band(flags, ImGuiTreeNodeFlags.Bullet) ~= 0 then if bit32.band(flags, ImGuiTreeNodeFlags.Bullet) ~= 0 then
ImGui.RenderBullet(window.DrawList, ImVec2(text_pos.x - text_offset_x * 0.5, text_pos.y + g.FontSize * 0.5), text_col) ImGui.RenderBullet(window.DrawList, ImVec2(text_pos.x - text_offset_x * 0.5, text_pos.y + g.FontSize * 0.5), text_col)
elseif not is_leaf then elseif not is_leaf then
ImGui.RenderArrow(window.DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15), text_col, is_open and ((bit32.band(flags, ImGuiTreeNodeFlags.UpsideDownArrow) ~= 0) and ImGuiDir.Up or ImGuiDir.Down) or ImGuiDir.Right, 0.70) ImGui.RenderArrow(window.DrawList, ImVec2(text_pos.x - text_offset_x + padding_x, text_pos.y + g.FontSize * 0.15), text_col, is_open and ((bit32.band(flags, ImGuiTreeNodeFlags.UpsideDownArrow) ~= 0) and ImGuiDir.Up or ImGuiDir.Down) or ImGuiDir.Right, 0.70)
end end
-- if g.LogEnabled then -- if g.LogEnabled then
-- ImGui.LogSetNextTextDecoration(">", nil) -- ImGui.LogSetNextTextDecoration(">", nil)
@@ -7815,7 +7845,7 @@ function ImGui.TreeNodeBehavior(id, flags, label, label_end)
end end
if draw_tree_lines then if draw_tree_lines then
ImGui.TreeNodeDrawLineToChildNode(ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.5)) ImGui.TreeNodeDrawLineToChildNode(ImVec2(text_pos.x - text_offset_x + padding_x, text_pos.y + g.FontSize * 0.5))
end end
-- Label -- Label
@@ -8058,7 +8088,7 @@ end
function ImGui.Selectable(label, selected, flags, size_arg) function ImGui.Selectable(label, selected, flags, size_arg)
if selected == nil then selected = false end if selected == nil then selected = false end
if flags == nil then flags = 0 end if flags == nil then flags = 0 end
if size_arg == nil then size_arg = ImVec2(0, 0) end if size_arg == nil then size_arg = LAYOUT_ZERO_SIZE end -- [Luau] shared read-only default (only read below)
local window = ImGui.GetCurrentWindow() local window = ImGui.GetCurrentWindow()
if (window.SkipItems) then if (window.SkipItems) then
+222 -14
View File
@@ -7,15 +7,34 @@
# Each source file becomes a function in __FILES; IM_INCLUDE(name) runs it once and caches the result. # Each source file becomes a function in __FILES; IM_INCLUDE(name) runs it once and caches the result.
# The module returns a table with every global the library defines (ImGui, ImVec2, ImGuiConfigFlags, ImGui_ImplRoblox, ...). # The module returns a table with every global the library defines (ImGui, ImVec2, ImGuiConfigFlags, ImGui_ImplRoblox, ...).
# Roblox target also writes ImGuiTyped.luau (types + require(script.Impl), see tools/gen_types.py). # Roblox target also writes ImGuiTyped.luau (types + require(script.Impl), see tools/gen_types.py).
import os, re #
# Build-time optimizations (token rewrites with minify.py's lexer: strings/comments are never touched, every source line
# stays on its line so linemap.json still maps errors). IMGUI_BUNDLE_NOOPT=1 turns all off, IMGUI_BUNDLE_NO_<X>=1 one:
# A hot library globals (most referenced names assigned at a file's top level) become bundle-level locals declared
# before __FILES: every file's assignment/reference then hits an upvalue instead of GETGLOBAL. Fewer names for
# LuaJIT (60 upvalues per function). Library globals can't be patched from outside (use the returned table).
# B enum folding: the bundle is loaded once headless (Luau CLI) to dump enum-like tables (all values numbers, never
# written inside a function) and numeric constants assigned once at top level; reads `ImGuiCol.Text` / `FLT_MAX`
# inside function bodies become `0 --[[ImGuiCol.Text]]`. Load-time (top-level) code is left alone.
# C `--@hoist` functions in the sources (pure numeric helpers: ImMin, ImMax, ImClamp, ...) are moved to the bundle top
# as `local function`s (their source lines become blank) so Luau -O2 can inline them everywhere.
# E statement-form `IM_ASSERT(...)` becomes `assert(...)` (fast builtin); expression uses are left alone.
import os, re, sys, json, shutil, subprocess, tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
sys.path.insert(0, HERE)
from minify import TOKEN
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
import sys
NO_DEMO = "--no-demo" in sys.argv NO_DEMO = "--no-demo" in sys.argv
LOVE = False LOVE = False
if "--target" in sys.argv: if "--target" in sys.argv:
i = sys.argv.index("--target"); LOVE = sys.argv[i + 1] == "love"; del sys.argv[i:i + 2] i = sys.argv.index("--target"); LOVE = sys.argv[i + 1] == "love"; del sys.argv[i:i + 2]
sys.argv = [a for a in sys.argv if a != "--no-demo"] sys.argv = [a for a in sys.argv if a != "--no-demo"]
OPT = {x: os.environ.get("IMGUI_BUNDLE_NOOPT") != "1" and os.environ.get("IMGUI_BUNDLE_NO_" + x) != "1" for x in "ABCE"}
# A limits: Luau 200 locals in the main chunk (+ helpers, __FILES, ...), 200 upvalues per function; LuaJIT 60 upvalues
# per function (margin left for nested functions that also capture file locals)
MAX_LOCALS, MAX_UPVALUES = 160, (45 if LOVE else 150)
BACKEND = "imgui_impl_love.lua" if LOVE else "imgui_impl_roblox.lua" BACKEND = "imgui_impl_love.lua" if LOVE else "imgui_impl_roblox.lua"
SKIP = {"imgui_impl_roblox.lua", "imgui_impl_roblox_fonts.lua"} if LOVE else {"imgui_impl_love.lua"} SKIP = {"imgui_impl_roblox.lua", "imgui_impl_roblox_fonts.lua"} if LOVE else {"imgui_impl_love.lua"}
FILES = {} FILES = {}
@@ -26,6 +45,7 @@ for d in (ROOT, os.path.join(ROOT, "backends"), os.path.join(ROOT, "fonts")):
if LOVE: src = re.sub(r"^@native$", "", src, flags=re.M) # keep line numbers if LOVE: src = re.sub(r"^@native$", "", src, flags=re.M) # keep line numbers
FILES[f] = src FILES[f] = src
ORIG = dict(FILES) # for gen_types
ENTRY = ["imgui.lua", BACKEND] + ([] if NO_DEMO else ["imgui_demo.lua"]) # run order ENTRY = ["imgui.lua", BACKEND] + ([] if NO_DEMO else ["imgui_demo.lua"]) # run order
names = set() names = set()
@@ -33,16 +53,207 @@ for src in FILES.values():
names.update(re.findall(r"^function ([A-Za-z_]\w*)\s*\(", src, re.M)) names.update(re.findall(r"^function ([A-Za-z_]\w*)\s*\(", src, re.M))
names.update(re.findall(r"^([A-Za-z_]\w*)\s*=[^=]", src, re.M)) names.update(re.findall(r"^([A-Za-z_]\w*)\s*=[^=]", src, re.M))
# ---- 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)))
+559
View File
@@ -0,0 +1,559 @@
#!/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
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"}
# ---------------------------------------------------------------------------------------------------------------------
# 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
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:
names = [x for x in pre if re.match(r"[A-Za-z_]\w*$", x) and x not in ("struct", "class", "union", "IMGUI_API", "typedef", "final", "public", "private", "alignas")]
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:]
if txt and ns == "ImGui" and cls is None and "(" in txt and txt[0] not in ("typedef", "struct", "enum", "using"):
p = txt.index("(")
if p > 0 and st[p - 1][0] == "name" and txt[p - 1] not in CTRL:
decls.setdefault(txt[p - 1], (st[p - 1][2], st[p - 1][3], section_at(st[p - 1][2])))
elif txt and cls and txt[0] not in ("typedef", "friend", "static", "using", "enum", "struct", "class", "template", "IM_MSVC_RUNTIME_CHECKS_OFF", "IM_MSVC_RUNTIME_CHECKS_RESTORE"):
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
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}
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: return 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
tot = {"n": 0, "have": 0, "stub": 0, "size": 0, "size_have": 0, "simw": 0.0, "size_sim": 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}
for u in sorted(all_up[f], key=lambda u: u.line):
un = norm_cpp(u.body)
if u.excl:
rows.append((u.section, "[-]", "", u, None, u.excl)); continue
st["n"] += 1; st["size"] += len(un)
p = find(u)
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 = [], 0, 0
for nm, (fl, line, sec) in sorted(structs_all.items()):
fl = [x for x in fl if not x[2]]
if not fl: continue
missing = [x[0] for x in fl if x[0] not in lua_fields]
field_n += len(fl); field_have += len(fl) - len(missing)
field_rows.append((nm, len(fl) - len(missing), len(fl), missing))
api_rows = []
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]}
have = [k for k in d if k in port_names]
api_rows.append((label, len(have), len(d), sorted((k for k in d if k not in port_names), key=lambda k: d[k][0]), d))
# ------------------------------------------------------------------ summary (stdout + file)
head = []
head.append("%-22s %9s %9s %9s %11s %9s" % ("upstream file", "funcs", "ported", "by size", "similarity", "stubs"))
for f, s in summary:
head.append("%-22s %9d %9s %9s %11s %9d" % (f, s["n"], pct(s["have"], s["n"]), pct(s["size_have"], s["size"]),
pct(s["simw"], s["size_sim"]), s["stub"]))
head.append("%-22s %9d %9s %9s %11s %9d" % ("TOTAL", tot["n"], pct(tot["have"], tot["n"]), pct(tot["size_have"], tot["size"]),
pct(tot["simw"], tot["size_sim"]), tot["stub"]))
head.append("")
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)))
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("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 [-] 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)")
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). Field names are matched globally, so common names may over-count.")
L.append("=" * 100)
for nm, h, n, missing in field_rows:
L.append(" %-40s %4d / %4d%s" % (nm, h, n, (" missing: " + ", ".join(missing)) if missing else ""))
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()
+1 -1
View File
@@ -68,7 +68,7 @@ def main():
ap.add_argument("--quiet", action="store_true", help="no random input (scripted tests drive io themselves)") ap.add_argument("--quiet", action="store_true", help="no random input (scripted tests drive io themselves)")
args = ap.parse_args() args = ap.parse_args()
code, out = run(args, "") code, out = run(args, os.environ.get("RUN_FLAGS", ""))
print(out if os.environ.get("FULL_OUTPUT") else out[-4000:]) print(out if os.environ.get("FULL_OUTPUT") else out[-4000:])
sys.exit(code) sys.exit(code)
+133
View File
@@ -0,0 +1,133 @@
-- Backend pixel oracle: composites the ScreenGui (like t_composite) at checkpoints and prints "PIX <frame> <hash>".
-- Phases: resize (bottom-right, then top-left), move, scroll, hover, typing, new font sizes (glyph baking), colour drag.
-- Each phase ends with a few still frames, then a checkpoint. Run with RUN_FLAGS="ARG_FORCE=true" for a full redraw
-- every frame: incremental output must match it exactly (and match the previous backend version).
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4
ImGui.CreateContext()
G.ImGui_ImplRoblox.Init()
G.ImGui_ImplRoblox.SetRenderRate(0)
if ARG_TILELIMIT then ImGui.GetIO().BackendPlatformUserData.TileLimit = ARG_TILELIMIT end
local io = ImGui.GetIO()
local W, H = 1280, 760
local col = ImVec4(0.4, 0.7, 0.2, 0.5)
local buf = ImGui.NewTextBuffer("type here", 128)
local slider = 0.3
local function composite(gui)
local screen = buffer.create(W * H * 3)
local function walk(inst, ox, oy, kx0, ky0, kx1, ky1)
local kids = inst:GetChildren()
table.sort(kids, function(a, b) return (a.ZIndex or 1) < (b.ZIndex or 1) end)
for _, c in ipairs(kids) do
if c.Visible ~= false then
local x, y = ox + (c.Position and c.Position.X or 0), oy + (c.Position and c.Position.Y or 0)
local cx0, cy0, cx1, cy1 = kx0, ky0, kx1, ky1
if c.ClipsDescendants then
cx0, cy0 = math.max(cx0, x), math.max(cy0, y)
cx1, cy1 = math.min(cx1, x + c.Size.X), math.min(cy1, y + c.Size.Y)
end
if c.ClassName == "ImageLabel" then
local img = c.ImageContent.Object
local px, sz = img.Pixels, img.Size.X
for ty = 0, sz - 1 do
local sy = y + ty
if sy >= cy0 and sy < cy1 then
for tx = 0, sz - 1 do
local sx = x + tx
if sx >= cx0 and sx < cx1 then
local o = (ty * sz + tx) * 4
local a = buffer.readu8(px, o + 3)
if a > 0 then
local d = (sy * W + sx) * 3
local fa = a / 255
for ch = 0, 2 do
local v = buffer.readu8(px, o + ch) * fa + buffer.readu8(screen, d + ch) * (1 - fa)
buffer.writeu8(screen, d + ch, math.floor(v + 0.5))
end
end
end
end
end
end
end
walk(c, x, y, cx0, cy0, cx1, cy1)
end
end
end
walk(gui, 0, 0, 0, 0, W, H)
local h = 0
for i = 0, W * H * 3 - 4, 4 do h = (h * 31 + buffer.readu32(screen, i)) % 2147483647 end
return h
end
-- phase list: name, frames of action (then 6 still frames + checkpoint)
local PHASES = { { "start", 4 }, { "resize_br", 24 }, { "resize_tl", 24 }, { "move", 24 }, { "scroll", 30 }, { "hover", 30 },
{ "type", 24 }, { "fonts", 24 }, { "color", 20 } }
local STILL = ARG_STILL or (ARG_TILELIMIT and 40 or 6) -- tile limit: deferred tiles need more frames to settle
local PT = {}
local pi, pf = 1, 0
local pos, size, scroll, fsize = ImVec2(40, 40), ImVec2(460, 520), 0, 13
local frame = 0
local typing = "Hello, glyphs! 0123 xyz"
game:GetService("RunService").Stepped:Connect(function()
local ph = PHASES[pi]
if not ph then return end
frame += 1; pf += 1
local name, n = ph[1], ph[2]
local act = pf <= n
G.ImGui_ImplRoblox.NewFrame()
io.DeltaTime = 1 / 60
io:AddMousePosEvent(1200, 740)
if act then
if name == "resize_br" then size = ImVec2(460 + pf * 7, 520 + pf * 5)
elseif name == "resize_tl" then pos = ImVec2(40 + pf * 3, 40 + pf * 2); size = ImVec2(size.x - 3, size.y - 2)
elseif name == "move" then pos = ImVec2(pos.x + 5, pos.y + 3)
elseif name == "scroll" then scroll += 9
elseif name == "hover" then io:AddMousePosEvent(pos.x + 30 + (pf * 17) % 300, pos.y + 40 + (pf * 23) % 400)
elseif name == "type" then
if pf == 1 then io:AddMousePosEvent(pos.x + 120, pos.y + 37); io:AddMouseButtonEvent(0, true)
elseif pf == 2 then io:AddMouseButtonEvent(0, false)
else io:AddInputCharacter(string.byte(typing, (pf - 3) % #typing + 1)) end
elseif name == "fonts" then fsize = 13 + pf * 0.75
elseif name == "color" then
local p = ImVec2(700 + pf * 4, 120 + pf * 3)
io:AddMousePosEvent(p.x, p.y); io:AddMouseButtonEvent(0, pf < n)
end
end
ImGui.NewFrame()
ImGui.SetNextWindowPos(pos, G.ImGuiCond.Always); ImGui.SetNextWindowSize(size, G.ImGuiCond.Always)
ImGui.Begin("Stress")
ImGui.InputText("##in", buf, 128)
if name == "scroll" or scroll > 0 then ImGui.SetScrollY(scroll) end
for i = 1, 12 do ImGui.Button("Button " .. i); ImGui.SameLine(); slider = ImGui.SliderFloat("##s" .. i, slider, 0, 1) end
if ImGui.BeginTable("tbl", 4, bit32.bor(G.ImGuiTableFlags.Borders, G.ImGuiTableFlags.RowBg)) then
for r = 1, 30 do ImGui.TableNextRow(); for c = 1, 4 do ImGui.TableNextColumn(); ImGui.Text("r%d c%d", r, c) end end
ImGui.EndTable()
end
ImGui.PushFont(nil, fsize); ImGui.Text("Font size %.2f AaBbCc", fsize); ImGui.PopFont()
for i = 1, 30 do ImGui.TextWrapped("Line %d: the quick brown fox jumps over the lazy dog.", i) end
ImGui.End()
ImGui.SetNextWindowPos(ImVec2(660, 60), G.ImGuiCond.Always); ImGui.SetNextWindowSize(ImVec2(500, 620), G.ImGuiCond.Always)
ImGui.Begin("Pickers")
ImGui.ColorPicker4("bar", col, G.ImGuiColorEditFlags.PickerHueBar)
ImGui.End()
ImGui.Render()
local clk = os.realclock or os.clock
local t0 = clk()
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
if act then PT[name] = (PT[name] or 0) + (clk() - t0) * 1000 end
if pf == n then -- mid-action (not settled): only exact without a redraw budget
print("PIXA", name, frame, composite(game:GetService("Players").LocalPlayer.PlayerGui:FindFirstChild("ImGui")))
end
if pf == n + STILL then
local gui = game:GetService("Players").LocalPlayer.PlayerGui:FindFirstChild("ImGui")
print("PIX", name, frame, composite(gui))
pi += 1; pf = 0
if not PHASES[pi] then
for _, q in PHASES do print(string.format("PIXT %-10s %7.2f ms/frame", q[1], PT[q[1]] / q[2])) end
print("PIXELS DONE")
end
end
end)
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "4dbug/imgui-luau" name = "4dbug/imgui-luau"
version = "0.1.4" version = "0.2.0"
registry = "https://github.com/UpliftGames/wally-index" registry = "https://github.com/UpliftGames/wally-index"
realm = "shared" realm = "shared"
license = "MIT" license = "MIT"