README modelled on upstream with rendered screenshots/GIF, docs/TODO.txt, screenshot tooling, t_readme snippet test, ImGui.NewTextBuffer helpers
@@ -0,0 +1,242 @@
|
||||
Dear ImGui for Roblox
|
||||
=====
|
||||
|
||||
<center><b><i>"Give someone state and they'll have a bug one day, but teach them how to represent state in two separate locations that have to be kept in sync and they'll have bugs for a lifetime."</i></b></center> <a href="https://twitter.com/rygorous/status/1507178315886444544">-ryg</a>
|
||||
|
||||
----
|
||||
|
||||
A Luau port of [Dear ImGui](https://github.com/ocornut/imgui) (docking branch, 1.93 WIP) for Roblox, rendered with EditableImages. It follows the C++ source function by function, so upstream documentation, examples and the demo apply almost directly.
|
||||
|
||||
| [The Pitch](#the-pitch) - [Usage](#usage) - [How it works](#how-it-works) - [Demo](#demo) - [Getting Started & Integration](#getting-started--integration) |
|
||||
:----------------------------------------------------------: |
|
||||
| [Differences from C++](#differences-from-c-dear-imgui) - [Performance](#performance) - [Testing](#testing) - [Not ported yet](#not-ported-yet) |
|
||||
| [FAQ](#faq) - [Credits](#credits) - [License](#license) |
|
||||
|
||||
### The Pitch
|
||||
|
||||
Dear ImGui is a **bloat-free graphical user interface library**. It outputs optimized vertex buffers that you can render anytime in your application. It is fast, portable, renderer agnostic, and self-contained.
|
||||
|
||||
Dear ImGui is designed to **enable fast iterations** and to **empower programmers** to create **content creation tools and visualization / debug tools** (as opposed to UI for the average end-user). It favors simplicity and productivity toward this goal and lacks certain features commonly found in more high-level libraries.
|
||||
|
||||
This port brings that to Roblox: debug overlays, admin panels, in-game editors, profilers and dev tools, written as plain Luau code with no Instances to create, parent or clean up. It includes docking, tables, multiple fonts, the full Style Editor, the Metrics/Debugger window and the complete demo.
|
||||
|
||||
### Usage
|
||||
|
||||
**The core of Dear ImGui is a set of files with no dependencies**, bundled by `tools/bundle.py` into one ModuleScript (`build/ImGui.luau`). The Roblox backend (`backends/imgui_impl_roblox.lua`) is included in the bundle.
|
||||
|
||||
Dear ImGui is **immediate mode**: you call widget functions every frame, and their return values tell you what the user did. There is no widget object to keep in sync with your state.
|
||||
|
||||
Code:
|
||||
```lua
|
||||
ImGui.Text("Hello, world %d", 123)
|
||||
if ImGui.Button("Save") then
|
||||
MySaveFunction()
|
||||
end
|
||||
ImGui.InputText("string", buf, 256) -- buf = ImGui.NewTextBuffer("...", 256)
|
||||
f = ImGui.SliderFloat("float", f, 0.0, 1.0)
|
||||
```
|
||||
Result:
|
||||
|
||||

|
||||
<br>_(settings: Dark style (left), Light style (right) / Font: ProggyClean, 13px)_
|
||||
|
||||
Code:
|
||||
```lua
|
||||
-- Create a window called "My First Tool", with a menu bar.
|
||||
my_tool_active = ImGui.Begin("My First Tool", my_tool_active, ImGuiWindowFlags.MenuBar)
|
||||
if ImGui.BeginMenuBar() then
|
||||
if ImGui.BeginMenu("File") then
|
||||
if ImGui.MenuItem("Open..", "Ctrl+O") then --[[ Do stuff ]] end
|
||||
if ImGui.MenuItem("Save", "Ctrl+S") then --[[ Do stuff ]] end
|
||||
if ImGui.MenuItem("Close", "Ctrl+W") then my_tool_active = false end
|
||||
ImGui.EndMenu()
|
||||
end
|
||||
ImGui.EndMenuBar()
|
||||
end
|
||||
|
||||
-- Edit a color stored as 4 floats
|
||||
ImGui.ColorEdit4("Color", my_color)
|
||||
|
||||
-- Generate samples and plot them
|
||||
for n = 1, 100 do samples[n] = math.sin((n - 1) * 0.2 + ImGui.GetTime() * 1.5) end
|
||||
ImGui.PlotLines("Samples", samples, nil, 100)
|
||||
|
||||
-- Display contents in a scrolling region
|
||||
ImGui.TextColored(ImVec4(1, 1, 0, 1), "Important Stuff")
|
||||
ImGui.BeginChild("Scrolling")
|
||||
for n = 0, 49 do ImGui.Text("%04d: Some text", n) end
|
||||
ImGui.EndChild()
|
||||
ImGui.End()
|
||||
```
|
||||
Result:
|
||||
|
||||

|
||||
|
||||
Dear ImGui allows you to **create elaborate tools** as well as very short-lived ones. On the extreme side of short-livedness: using the Edit&Continue-friendly nature of immediate mode, you can add a slider to tweak a value in one line and remove it again a minute later.
|
||||
|
||||
Every snippet in this README is run by [`tools/test/scripts/t_readme.luau`](tools/test/scripts/t_readme.luau), and every image was rendered by this port with [`tools/screenshots/make.py`](tools/screenshots/make.py).
|
||||
|
||||
### How it works
|
||||
|
||||
Dear ImGui builds a list of textured triangles (`ImDrawData`) every frame. The Roblox backend turns it into pixels:
|
||||
|
||||
- Every window gets its own **layer** (a Frame) made of 128×128 **EditableImage tiles**. Moving a window only moves its Frame: nothing is redrawn.
|
||||
- Scrolled window content sits in a `ClipsDescendants` frame positioned at the scroll offset, so scrolling moves already drawn pixels and only rows entering view are drawn.
|
||||
- Tiles are hashed; only tiles whose content changed are rasterized (in Luau, with `@native`) and uploaded, and only their dirty region. A static UI costs almost nothing to render.
|
||||
- While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the mouse wheel don't reach the game (camera zoom etc.).
|
||||
|
||||
### Demo
|
||||
|
||||
Calling `ImGui.ShowDemoWindow()` creates a demo window that showcases a variety of features and examples. The code is in [imgui_demo.lua](imgui_demo.lua), like upstream's `imgui_demo.cpp`. **It's strongly recommended to keep it at hand as a reference**: you can find the code for any widget you see by searching for its label.
|
||||
|
||||

|
||||
|
||||
Tables, with sorting, resizing, reordering, borders and row backgrounds:
|
||||
|
||||

|
||||
|
||||
Docking (`io.ConfigFlags |= ImGuiConfigFlags.DockingEnable`):
|
||||
|
||||

|
||||
|
||||
Plotting:
|
||||
|
||||

|
||||
|
||||
Custom drawing with `ImDrawList`:
|
||||
|
||||
```lua
|
||||
local draw_list = ImGui.GetWindowDrawList()
|
||||
local p = ImGui.GetCursorScreenPos()
|
||||
draw_list:AddRectFilled(p, p + ImVec2(100, 100), IM_COL32(255, 120, 60, 255), 12.0)
|
||||
draw_list:AddCircleFilled(p + ImVec2(170, 50), 45, IM_COL32(80, 160, 255, 255))
|
||||
draw_list:AddLine(p + ImVec2(0, 120), p + ImVec2(220, 120), IM_COL32(255, 255, 0, 255), 2.0)
|
||||
ImGui.Dummy(ImVec2(220, 130)) -- reserve the space so the window sizes/scrolls correctly
|
||||
```
|
||||
|
||||

|
||||
|
||||
The Style Editor (`ImGui.ShowStyleEditor()`) and the Metrics/Debugger (`ImGui.ShowMetricsWindow()`):
|
||||
|
||||
 
|
||||
|
||||
### Getting Started & Integration
|
||||
|
||||
1. Build the module: `python3 tools/bundle.py` writes `build/ImGui.luau` (`--no-demo` leaves the demo out).
|
||||
2. Sync with [Rojo](https://rojo.space): [`default.project.json`](default.project.json) puts the module in `ReplicatedStorage.ImGui` and the example LocalScript in `StarterPlayerScripts`.
|
||||
3. A minimal LocalScript (full version: [examples/example_roblox/main.client.luau](examples/example_roblox/main.client.luau)):
|
||||
|
||||
```lua
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local RunService = game:GetService("RunService")
|
||||
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
|
||||
local ImGui, ImGui_ImplRoblox = G.ImGui, G.ImGui_ImplRoblox
|
||||
|
||||
ImGui.CreateContext()
|
||||
local io = ImGui.GetIO()
|
||||
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.NavEnableKeyboard, G.ImGuiConfigFlags.DockingEnable)
|
||||
ImGui_ImplRoblox.Init() -- Init(parent, render_scale): parent defaults to PlayerGui
|
||||
|
||||
RunService.Stepped:Connect(function()
|
||||
ImGui_ImplRoblox.NewFrame()
|
||||
ImGui.NewFrame()
|
||||
|
||||
ImGui.ShowDemoWindow()
|
||||
-- ... your windows ...
|
||||
|
||||
ImGui.Render()
|
||||
ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||
end)
|
||||
```
|
||||
|
||||
Backend options:
|
||||
|
||||
| Call | Effect |
|
||||
| --- | --- |
|
||||
| `ImGui_ImplRoblox.Init(parent, render_scale)` | `render_scale` = your OS display scale renders at physical-pixel resolution (crisper, same size as desktop imgui). |
|
||||
| `ImGui_ImplRoblox.SetRenderScale(scale)` | Change it at runtime. |
|
||||
| `ImGui_ImplRoblox.SetRenderRate(hz)` | Max tile redraws per second (default 60, `0` = every frame). Your code still runs every frame. |
|
||||
| `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.AddFile(path, bytes)` | Register a file (e.g. a `.ttf`) so `io.Fonts:AddFontFromFileTTF(path, size)` can load it. |
|
||||
|
||||
Fonts: ProggyClean (default), ProggyTiny, ProggyForever, Cousine, DroidSans, Karla and Roboto are embedded from [fonts/](fonts/):
|
||||
|
||||
```lua
|
||||
io.Fonts:AddFontDefault()
|
||||
io.Fonts:AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0)
|
||||
```
|
||||
|
||||
### Differences from C++ Dear ImGui
|
||||
|
||||
The API is the same function-for-function; these are the Luau-specific conventions:
|
||||
|
||||
| C++ | Luau |
|
||||
| --- | --- |
|
||||
| `bool* p_open`, `float* v`, `int* v` | Passed by value, the new value is returned. |
|
||||
| `bool Checkbox(label, &v)` | `pressed, v = ImGui.Checkbox(label, v)` |
|
||||
| `bool SliderFloat(label, &v, ...)` | `v, changed = ImGui.SliderFloat(label, v, ...)` (same for Drag/Input/Slider scalars) |
|
||||
| `bool Combo(label, &idx, items)` | `idx, changed = ImGui.Combo(label, idx, { "a", "b" })`, **0-based** index |
|
||||
| `bool Begin(name, &open)` | `open, visible = ImGui.Begin(name, open)` |
|
||||
| `float v[3]`, `ImVec4 col` | Arrays / `ImVec4` are edited in place (`DragFloat3`, `ColorEdit4`). |
|
||||
| `char buf[256]` | `buf = ImGui.NewTextBuffer("text", 256)`, edited in place; `ImGui.TextBufferToString(buf)` |
|
||||
| `ImGuiWindowFlags_MenuBar` | `ImGuiWindowFlags.MenuBar` (combine with `bit32.bor`) |
|
||||
| `ImVec2 a + b` | Works (`ImVec2` has arithmetic metamethods); fields are `.x`, `.y` |
|
||||
| `draw_list->AddRect(a, b, col, rounding, flags, thickness)` | `draw_list:AddRect(a, b, col, rounding, thickness, flags)` |
|
||||
| `ImDrawList::VtxBuffer` (`ImDrawVert[]`) | One flat `buffer` (`VtxBuffer.Buf`, 36 bytes per vertex); read with `ImDrawVtx_Pos/ImDrawVtx_Col`. |
|
||||
|
||||
All exported names (`ImGui`, `ImVec2`, `ImVec4`, `IM_COL32`, every `ImGui*Flags` enum, …) are fields of the table returned by `require`.
|
||||
|
||||
### Performance
|
||||
|
||||
Measured headless (Luau CLI, Studio-like native code generation), demo window open:
|
||||
|
||||
| Scenario | Frame time |
|
||||
| --- | --- |
|
||||
| Demo idle, Widgets > Basic open | ~1.3 ms |
|
||||
| Scrolling the same | ~1.9 ms |
|
||||
| Dragging a window | ~0.4 ms, no tile uploads |
|
||||
| Whole demo open (every section) | ~22 ms |
|
||||
|
||||
Most of the remaining cost is widget logic (your UI code and Dear ImGui's per-widget work), which runs every frame by design. Use `ImGuiListClipper` for long lists.
|
||||
|
||||
### Testing
|
||||
|
||||
`tools/test/run.py` runs any LocalScript headless against a mocked Roblox (`tools/test/roblox_mock.luau`):
|
||||
|
||||
```
|
||||
python3 tools/bundle.py
|
||||
python3 tools/test/run.py --main tools/test/scripts/t_tables.luau --frames 120 --quiet --roblox
|
||||
```
|
||||
|
||||
- `t_drawhash` / `t_colors_hash`: checksums of all draw data; internal rewrites must keep them identical.
|
||||
- `t_composite`: composites the backend's tiles like Roblox does, to compare pixels between versions.
|
||||
- `t_click_fuzz`, `t_apps`, `t_dock*`, `t_tables`, …: behaviour tests. `--profile` + `tools/test/prof.py` for profiling.
|
||||
|
||||
See [docs/PORTING.md](docs/PORTING.md) for the porting rules.
|
||||
|
||||
### Not ported yet
|
||||
|
||||
Multi-select / box-select, typing-select, the Assets Browser example, the Item Picker and a few smaller items. The full list is in [docs/TODO.txt](docs/TODO.txt).
|
||||
|
||||
### FAQ
|
||||
|
||||
**Does it work on mobile / console?** Rendering does. The backend currently handles mouse and keyboard input only; touch and gamepad are not wired up yet.
|
||||
|
||||
**Does my UI code run when nothing changes?** Yes, every frame, like Dear ImGui. Only rendering is skipped for unchanged content.
|
||||
|
||||
**Can I use it for player-facing UI?** You can, but like upstream it is designed for tools: programmer-oriented, dense, and not themed for end users.
|
||||
|
||||
**Where is the documentation?** Upstream's docs apply: the [wiki](https://github.com/ocornut/imgui/wiki), the [FAQ](https://github.com/ocornut/imgui/blob/master/docs/FAQ.md) and the comments in `imgui.h`. The demo is the best reference.
|
||||
|
||||
### Credits
|
||||
|
||||
Dear ImGui is developed by [Omar Cornut](https://www.miracleworld.net) and [every direct or indirect contributor](https://github.com/ocornut/imgui/graphs/contributors). This repository is an unofficial port and is not affiliated with the Dear ImGui project.
|
||||
|
||||
Embeds [stb_truetype, stb_textedit, stb_rectpack](https://github.com/nothings/stb) by Sean Barrett (public domain / MIT), ported to Luau.
|
||||
|
||||
Fonts: ProggyClean, ProggyTiny, ProggyForever by Tristan Grimmer; Cousine, DroidSans, Karla, Roboto under their respective licenses (Apache 2.0 / SIL OFL), as distributed in upstream's `misc/fonts/`.
|
||||
|
||||
### License
|
||||
|
||||
Dear ImGui is licensed under the MIT License, see [LICENSE.txt](https://github.com/ocornut/imgui/blob/master/LICENSE.txt) for more information. This port is distributed under the same license.
|
||||
@@ -1 +1 @@
|
||||
[[12, "imgui.lua"], [18472, "imgui_demo.lua"], [25757, "imgui_draw.lua"], [30995, "imgui_h.lua"], [33193, "imgui_impl_roblox.lua"], [34319, "imgui_impl_roblox_fonts.lua"], [40722, "imgui_internal.lua"], [43894, "imgui_tables.lua"], [48241, "imgui_widgets.lua"], [57982, "imstb_rectpack.lua"], [58428, "imstb_textedit.lua"], [59669, "imstb_truetype.lua"], [62137, "imstd_minstdio.lua"]]
|
||||
[[12, "imgui.lua"], [18472, "imgui_demo.lua"], [25757, "imgui_draw.lua"], [30995, "imgui_h.lua"], [33193, "imgui_impl_roblox.lua"], [34319, "imgui_impl_roblox_fonts.lua"], [40722, "imgui_internal.lua"], [43894, "imgui_tables.lua"], [48241, "imgui_widgets.lua"], [57996, "imstb_rectpack.lua"], [58442, "imstb_textedit.lua"], [59683, "imstb_truetype.lua"], [62151, "imstd_minstdio.lua"]]
|
||||
@@ -52294,6 +52294,20 @@ function ImGui.InputText(label, buf, buf_size, flags, callback, user_data)
|
||||
return ImGui.InputTextEx(label, nil, buf, buf_size, ImVec2(0, 0), flags, callback, user_data)
|
||||
end
|
||||
|
||||
-- [Luau] InputText edits a byte table in place (C++ char buf[N]). Helpers to make/read one:
|
||||
-- local buf = ImGui.NewTextBuffer("Hello", 256); ImGui.InputText("label", buf, 256); print(ImGui.TextBufferToString(buf))
|
||||
function ImGui.NewTextBuffer(str, size)
|
||||
local t = {}
|
||||
for i = 1, #str do t[i] = string.byte(str, i) end
|
||||
for i = #str + 1, math.max(size or 0, #str + 1) do t[i] = 0 end
|
||||
return t
|
||||
end
|
||||
function ImGui.TextBufferToString(buf)
|
||||
local n = 0
|
||||
while buf[n + 1] ~= nil and buf[n + 1] ~= 0 do n += 1 end
|
||||
return n == 0 and "" or ImGui._ByteArrayToString(buf, 1, n + 1)
|
||||
end
|
||||
|
||||
--- @param size? ImVec2
|
||||
function ImGui.InputTextMultiline(label, buf, buf_size, size, flags, callback, user_data)
|
||||
return ImGui.InputTextEx(label, nil, buf, buf_size, size or ImVec2(0, 0), bit32.bor(flags or 0, ImGuiInputTextFlags.Multiline), callback, user_data)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
Dear ImGui for Roblox - not yet ported / known gaps
|
||||
====================================================
|
||||
Upstream reference: tools/test/upstream/ (ocornut/imgui docking branch, 1.93 WIP).
|
||||
Re-check with: python3 tools/test/missing.py [imgui|widgets|tables|draw] and python3 tools/test/undefined.py --no-demo
|
||||
(missing.py also lists names the port defines differently, e.g. the *V printf variants and DragFloat2..4 / DragInt2..4,
|
||||
which exist as ImGui.DragFloat2 = DragFloatN(2) etc. Those are NOT gaps.)
|
||||
|
||||
|
||||
FEATURES NOT PORTED
|
||||
-------------------
|
||||
- Multi-select / box-select: BeginMultiSelect, EndMultiSelect, MultiSelectItemHeader/Footer, MultiSelectAddSetAll,
|
||||
MultiSelectAddSetRange, SetNextItemSelectionUserData, BeginBoxSelect/EndBoxSelect, DebugNodeMultiSelectState.
|
||||
(Selectable/TreeNode call sites are stubbed with TODOs; the demo "Selection State & Multi-Select" section is not ported.)
|
||||
- Typing-select (type to jump in lists): GetTypingSelectRequest, TypingSelectFind*, DebugNodeTypingSelectState.
|
||||
- Examples > Assets Browser (needs multi-select + box-select).
|
||||
- Metrics > Tools > Item Picker (UpdateDebugToolItemPicker).
|
||||
- DebugFlashStyleColor / UpdateDebugToolFlashStyleColor (Style Editor "?" flash button).
|
||||
- DebugNodeInputTextState (Metrics > Internal state > InputText).
|
||||
- BringWindowToDisplayBehind: stub, does nothing.
|
||||
- ColorEdit4 / ColorPicker4: drag & drop target (dropping a color onto a color widget).
|
||||
- ImFontAtlas:AddFontFromMemoryCompressedTTF: stub (binary_to_compressed_c fonts can't be loaded).
|
||||
- ImGuiSizeCallbackData: SetNextWindowSizeConstraints custom callbacks only partially supported
|
||||
(Examples > Constrained Resize).
|
||||
- LogSetNextTextDecoration (Selectable "[ ]" decoration in log/clipboard output).
|
||||
- stb_truetype: cmap high-byte mapping (some CJK fonts assert).
|
||||
- Context hooks removal (RemoveContextHook), SetContextName.
|
||||
- RegisterUserTexture / UnregisterUserTexture.
|
||||
- ErrorCheckNewFrameSanityChecks / ErrorCheckEndFrameSanityChecks (fewer usage-error asserts than upstream).
|
||||
- GetAllocatorFunctions (n/a in Luau).
|
||||
|
||||
WON'T DO (platform)
|
||||
-------------------
|
||||
- Multi-viewports / platform windows: Roblox has one window. Viewport API exists for the main viewport only.
|
||||
- Gamepad navigation via a real gamepad backend (keyboard navigation works).
|
||||
- IME / non-ASCII keyboard text input (backend sends printable ASCII only).
|
||||
|
||||
NOT VERIFIED LINE BY LINE AGAINST UPSTREAM
|
||||
------------------------------------------
|
||||
- Demo: Layout & Scrolling, Popups & Modal windows, Inputs & Focus.
|
||||
|
||||
KNOWN ISSUES
|
||||
------------
|
||||
- Tables > "Columns flags": disabling every column asserts in TableUpdateLayout (upstream asserts too).
|
||||
- Style Editor > Colors uses a ListClipper (port-only change, upstream submits every row).
|
||||
|
||||
PERFORMANCE IDEAS (not done)
|
||||
----------------------------
|
||||
- Opt-in cached windows (skip user code for idle windows).
|
||||
- Cheaper visible ColorEdit4 rows; cheaper Begin() for child windows.
|
||||
|
||||
PLANNED EXTENSIONS
|
||||
------------------
|
||||
- ImGuizmo, ImNodes, ImGuiColorTextEdit, ImPlot, ImPlot3D (as separate extensions/, built with a --with flag).
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 44 KiB |
@@ -4054,6 +4054,20 @@ function ImGui.InputText(label, buf, buf_size, flags, callback, user_data)
|
||||
return ImGui.InputTextEx(label, nil, buf, buf_size, ImVec2(0, 0), flags, callback, user_data)
|
||||
end
|
||||
|
||||
-- [Luau] InputText edits a byte table in place (C++ char buf[N]). Helpers to make/read one:
|
||||
-- local buf = ImGui.NewTextBuffer("Hello", 256); ImGui.InputText("label", buf, 256); print(ImGui.TextBufferToString(buf))
|
||||
function ImGui.NewTextBuffer(str, size)
|
||||
local t = {}
|
||||
for i = 1, #str do t[i] = string.byte(str, i) end
|
||||
for i = #str + 1, math.max(size or 0, #str + 1) do t[i] = 0 end
|
||||
return t
|
||||
end
|
||||
function ImGui.TextBufferToString(buf)
|
||||
local n = 0
|
||||
while buf[n + 1] ~= nil and buf[n + 1] ~= 0 do n += 1 end
|
||||
return n == 0 and "" or ImGui._ByteArrayToString(buf, 1, n + 1)
|
||||
end
|
||||
|
||||
--- @param size? ImVec2
|
||||
function ImGui.InputTextMultiline(label, buf, buf_size, size, flags, callback, user_data)
|
||||
return ImGui.InputTextEx(label, nil, buf, buf_size, size or ImVec2(0, 0), bit32.bor(flags or 0, ImGuiInputTextFlags.Multiline), callback, user_data)
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
-- Renders README screenshots headless: runs one SCENE (prepended by tools/screenshots/make.py), composites the
|
||||
-- backend's tile ImageLabels exactly like Roblox stacks them (ZIndex, nested offsets, ClipsDescendants) over a
|
||||
-- background colour, and prints frames as "PX <frame> <y> <hex rgb row>" for the crop rect.
|
||||
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||
local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4
|
||||
local Cond = G.ImGuiCond
|
||||
ImGui.CreateContext()
|
||||
local io = ImGui.GetIO()
|
||||
io.ConfigFlags = bit32.bor(io.ConfigFlags, G.ImGuiConfigFlags.DockingEnable)
|
||||
io.IniFilename = nil
|
||||
G.ImGui_ImplRoblox.Init()
|
||||
G.ImGui_ImplRoblox.SetRenderRate(0)
|
||||
local BG = { 115, 140, 153 } -- examples/example_roblox clear_color
|
||||
|
||||
-- force tree nodes / headers / tabs open by label
|
||||
local OPEN = {}
|
||||
for _, fname in ipairs({ "TreeNode", "CollapsingHeader" }) do
|
||||
local real = ImGui[fname]
|
||||
ImGui[fname] = function(label, ...)
|
||||
if OPEN[label] then ImGui.SetNextItemOpen(true, Cond.Always) end
|
||||
return real(label, ...)
|
||||
end
|
||||
end
|
||||
local SELECT_TAB = nil
|
||||
local realBTI = ImGui.BeginTabItem
|
||||
ImGui.BeginTabItem = function(label, p_open, flags)
|
||||
if label == SELECT_TAB then flags = bit32.bor(flags or 0, G.ImGuiTabItemFlags.SetSelected) end
|
||||
return realBTI(label, p_open, flags)
|
||||
end
|
||||
|
||||
-- the demo sets its own pos/size (FirstUseEver) right before Begin: override it there
|
||||
local DEMO_RECT = nil
|
||||
local realBegin = ImGui.Begin
|
||||
ImGui.Begin = function(name, ...)
|
||||
if name == "Dear ImGui Demo" and DEMO_RECT then
|
||||
ImGui.SetNextWindowPos(ImVec2(DEMO_RECT[1], DEMO_RECT[2]), Cond.Always); ImGui.SetNextWindowSize(ImVec2(DEMO_RECT[3], DEMO_RECT[4]), Cond.Always)
|
||||
end
|
||||
return realBegin(name, ...)
|
||||
end
|
||||
|
||||
local function Pos(x, y, w, h) ImGui.SetNextWindowPos(ImVec2(x, y), Cond.Always); if w then ImGui.SetNextWindowSize(ImVec2(w, h), Cond.Always) end end
|
||||
|
||||
-- ---------------------------------------------------------------- scenes
|
||||
-- Each: crop {x, y, w, h}, frames to capture (list), mouse position, draw(frame)
|
||||
local scenes = {}
|
||||
|
||||
-- README "Usage" example, verbatim
|
||||
local f, counter = 0.6, 0
|
||||
local buf = ImGui.NewTextBuffer("Hello, world!", 256)
|
||||
local my_color = ImVec4(1.0, 0.4, 0.2, 1.0)
|
||||
local function HelloWorld()
|
||||
ImGui.Begin("My First Tool")
|
||||
ImGui.Text("Hello, world %d", 123)
|
||||
if ImGui.Button("Save") then counter += 1 end
|
||||
ImGui.InputText("string", buf, 256)
|
||||
f = ImGui.SliderFloat("float", f, 0.0, 1.0)
|
||||
ImGui.ColorEdit4("Color", my_color)
|
||||
ImGui.End()
|
||||
end
|
||||
|
||||
scenes.hello = { crop = { 0, 0, 420, 200 }, shots = { 30 }, mouse = { 1900, 1060 }, draw = function()
|
||||
Pos(10, 10, 400, 180); HelloWorld()
|
||||
end }
|
||||
|
||||
scenes.demo = { crop = { 0, 0, 1000, 740 }, shots = { 40 }, mouse = { 640, 330 }, draw = function()
|
||||
OPEN["Widgets"] = true; OPEN["Basic"] = true
|
||||
Pos(10, 10, 380, 200)
|
||||
ImGui.Begin("Hello, world!")
|
||||
ImGui.Text("This is some useful text.")
|
||||
ImGui.Checkbox("Demo Window", true)
|
||||
ImGui.SliderFloat("float", 0.42, 0.0, 1.0)
|
||||
ImGui.Button("Button"); ImGui.SameLine(); ImGui.Text("counter = %d", 3)
|
||||
ImGui.Text("Application average %.3f ms/frame (%.1f FPS)", 16.667, 60.0)
|
||||
ImGui.End()
|
||||
DEMO_RECT = { 410, 10, 580, 720 }; ImGui.ShowDemoWindow()
|
||||
end }
|
||||
|
||||
scenes.tables = { crop = { 0, 0, 700, 740 }, shots = { 40 }, mouse = { 1900, 1060 }, draw = function()
|
||||
OPEN["Tables & Columns"] = true; OPEN["Borders, background"] = true; OPEN["Sorting"] = true
|
||||
DEMO_RECT = { 10, 10, 680, 720 }; ImGui.ShowDemoWindow()
|
||||
end }
|
||||
|
||||
scenes.style = { crop = { 0, 0, 560, 740 }, shots = { 40 }, mouse = { 1900, 1060 }, draw = function()
|
||||
SELECT_TAB = "Colors"
|
||||
Pos(10, 10, 540, 720); ImGui.Begin("Dear ImGui Style Editor"); ImGui.ShowStyleEditor(); ImGui.End()
|
||||
end }
|
||||
|
||||
scenes.metrics = { crop = { 0, 0, 620, 560 }, shots = { 40 }, mouse = { 1900, 1060 }, draw = function()
|
||||
OPEN["Windows"] = true; OPEN["DrawLists"] = true
|
||||
Pos(10, 10, 600, 540); ImGui.ShowMetricsWindow()
|
||||
end }
|
||||
|
||||
scenes.docking = { crop = { 0, 0, 1100, 640 }, shots = { 40 }, mouse = { 1900, 1060 }, draw = function(frame)
|
||||
Pos(10, 10, 1080, 620)
|
||||
ImGui.Begin("Editor", nil, G.ImGuiWindowFlags.MenuBar)
|
||||
if ImGui.BeginMenuBar() then
|
||||
if ImGui.BeginMenu("File") then ImGui.EndMenu() end
|
||||
if ImGui.BeginMenu("View") then ImGui.EndMenu() end
|
||||
ImGui.EndMenuBar()
|
||||
end
|
||||
local id = ImGui.GetID("MainDock")
|
||||
if frame == 2 then
|
||||
ImGui.DockBuilderRemoveNode(id)
|
||||
ImGui.DockBuilderAddNode(id, G.ImGuiDockNodeFlags.None)
|
||||
ImGui.DockBuilderSetNodeSize(id, ImVec2(1064, 560))
|
||||
local left, right = ImGui.DockBuilderSplitNode(id, G.ImGuiDir.Left, 0.3)
|
||||
local rtop, rbottom = ImGui.DockBuilderSplitNode(right, G.ImGuiDir.Up, 0.65)
|
||||
ImGui.DockBuilderDockWindow("Scene", left)
|
||||
ImGui.DockBuilderDockWindow("Inspector", rtop)
|
||||
ImGui.DockBuilderDockWindow("Properties", rtop)
|
||||
ImGui.DockBuilderDockWindow("Console", rbottom)
|
||||
ImGui.DockBuilderFinish(id)
|
||||
end
|
||||
ImGui.DockSpace(id)
|
||||
ImGui.End()
|
||||
ImGui.Begin("Scene")
|
||||
for _, n in ipairs({ "Workspace", "Lighting", "Players", "ReplicatedStorage" }) do
|
||||
if ImGui.TreeNode(n) then ImGui.BulletText("Part"); ImGui.BulletText("Model"); ImGui.TreePop() end
|
||||
end
|
||||
ImGui.End()
|
||||
ImGui.Begin("Inspector")
|
||||
ImGui.SeparatorText("Transform")
|
||||
ImGui.DragFloat3("Position", { 12.0, 4.5, -3.0 }, 0.1)
|
||||
ImGui.DragFloat3("Rotation", { 0.0, 90.0, 0.0 }, 1.0)
|
||||
ImGui.SeparatorText("Appearance")
|
||||
ImGui.ColorEdit3("Color", ImVec4(0.2, 0.6, 1.0, 1.0))
|
||||
ImGui.SliderFloat("Transparency", 0.25, 0.0, 1.0)
|
||||
ImGui.Checkbox("Anchored", true); ImGui.SameLine(); ImGui.Checkbox("CanCollide", false)
|
||||
ImGui.End()
|
||||
ImGui.Begin("Properties"); ImGui.Text("Properties"); ImGui.End()
|
||||
ImGui.Begin("Console")
|
||||
for i = 1, 6 do ImGui.TextColored(ImVec4(0.6, 0.9, 0.6, 1), "[%02d:00] Loaded chunk %d", i, i * 7) end
|
||||
ImGui.End()
|
||||
end }
|
||||
|
||||
-- README plot example (animated -> GIF)
|
||||
local values, hist = {}, { 0.6, 0.1, 1.0, 0.5, 0.92, 0.1, 0.2 }
|
||||
scenes.plot = { crop = { 0, 0, 440, 330 }, shots = {}, mouse = { 1900, 1060 }, draw = function(frame)
|
||||
local t = frame / 30
|
||||
for i = 1, 90 do values[i] = math.sin((i / 90) * math.pi * 4 + t * 3) * math.cos(t + i * 0.05) end
|
||||
for i = 1, #hist do hist[i] = 0.5 + 0.5 * math.sin(t * 2 + i) end
|
||||
Pos(10, 10, 420, 310)
|
||||
ImGui.Begin("Plotting")
|
||||
ImGui.PlotLines("Lines", values, nil, #values, 0, "sine", -1.0, 1.0, ImVec2(0, 90))
|
||||
ImGui.PlotHistogram("Histogram", hist, nil, #hist, 0, nil, 0.0, 1.0, ImVec2(0, 90))
|
||||
ImGui.ProgressBar((t * 0.4) % 1.0, ImVec2(-1, 0))
|
||||
ImGui.Text("frame %d", frame)
|
||||
ImGui.End()
|
||||
end }
|
||||
for i = 30, 149 do table.insert(scenes.plot.shots, i) end
|
||||
|
||||
scenes.drawlist = { crop = { 0, 0, 380, 300 }, shots = { 30 }, mouse = { 1900, 1060 }, draw = function()
|
||||
Pos(10, 10, 360, 280)
|
||||
ImGui.Begin("Custom drawing")
|
||||
local dl = ImGui.GetWindowDrawList()
|
||||
local p = ImGui.GetCursorScreenPos()
|
||||
dl:AddRectFilled(p, p + ImVec2(100, 100), G.IM_COL32(255, 120, 60, 255), 12.0)
|
||||
dl:AddCircleFilled(p + ImVec2(170, 50), 45, G.IM_COL32(80, 160, 255, 255))
|
||||
dl:AddTriangleFilled(p + ImVec2(230, 100), p + ImVec2(280, 0), p + ImVec2(330, 100), G.IM_COL32(120, 220, 120, 255))
|
||||
dl:AddBezierCubic(p + ImVec2(0, 140), p + ImVec2(100, 100), p + ImVec2(200, 220), p + ImVec2(330, 150), G.IM_COL32(255, 255, 0, 255), 3.0)
|
||||
dl:AddText(p + ImVec2(0, 190), G.IM_COL32(255, 255, 255, 255), "ImDrawList: lines, shapes, text")
|
||||
ImGui.Dummy(ImVec2(330, 220))
|
||||
ImGui.End()
|
||||
end }
|
||||
|
||||
-- README hero: same "Debug" window in the dark and light styles (like upstream's README)
|
||||
local dbg_buf = ImGui.NewTextBuffer("quick brown fox", 256)
|
||||
local function DebugWindow(title)
|
||||
ImGui.Begin(title)
|
||||
ImGui.Text("Hello, world %d", 123)
|
||||
ImGui.Button("Save")
|
||||
ImGui.InputText("string", dbg_buf, 256)
|
||||
ImGui.SliderFloat("float", 0.6, 0.0, 1.0)
|
||||
ImGui.End()
|
||||
end
|
||||
scenes.debug = { crop = { 0, 0, 540, 150 }, shots = { 30 }, mouse = { 140, 113 }, cursor = true, draw = function()
|
||||
Pos(10, 10, 250, 130); DebugWindow("Debug##dark")
|
||||
local saved = {}
|
||||
for i, c in pairs(ImGui.GetStyle().Colors) do saved[i] = ImVec4(c.x, c.y, c.z, c.w) end
|
||||
ImGui.StyleColorsLight()
|
||||
Pos(280, 10, 250, 130); DebugWindow("Debug##light")
|
||||
for i, c in pairs(saved) do G.ImVec4_Copy(ImGui.GetStyle().Colors[i], c) end
|
||||
end }
|
||||
|
||||
-- README "My First Tool" (verbatim from README.md / t_readme.luau), with the color picker popup opened by a click
|
||||
local my_tool_active, tool_color = true, ImVec4(0.455, 0.282, 0.455, 1.0)
|
||||
local samples = {}
|
||||
local btn = nil
|
||||
scenes.tool = { crop = { 0, 0, 560, 420 }, shots = { 40 }, cursor = true,
|
||||
mouse = function(frame) if frame < 16 then return btn and btn[1] or 5, btn and btn[2] or 5 end return 300, 240 end,
|
||||
button = function(frame) if frame == 12 then return true elseif frame == 13 then return false end end,
|
||||
draw = function()
|
||||
io.ConfigColorEditFlags = bit32.bor(G.ImGuiColorEditFlags.PickerHueWheel, G.ImGuiColorEditFlags.Uint8, G.ImGuiColorEditFlags.DisplayRGB, G.ImGuiColorEditFlags.InputRGB)
|
||||
Pos(10, 10, 340, 330)
|
||||
-- [snippet:tool]
|
||||
-- Create a window called "My First Tool", with a menu bar.
|
||||
my_tool_active = ImGui.Begin("My First Tool", my_tool_active, G.ImGuiWindowFlags.MenuBar)
|
||||
if ImGui.BeginMenuBar() then
|
||||
if ImGui.BeginMenu("File") then
|
||||
if ImGui.MenuItem("Open..", "Ctrl+O") then --[[ Do stuff ]] end
|
||||
if ImGui.MenuItem("Save", "Ctrl+S") then --[[ Do stuff ]] end
|
||||
if ImGui.MenuItem("Close", "Ctrl+W") then my_tool_active = false end
|
||||
ImGui.EndMenu()
|
||||
end
|
||||
ImGui.EndMenuBar()
|
||||
end
|
||||
|
||||
-- Edit a color stored as 4 floats
|
||||
local p, w, h = ImGui.GetCursorScreenPos(), ImGui.CalcItemWidth(), ImGui.GetFrameHeight()
|
||||
btn = { p.x + w - h * 0.5, p.y + h * 0.5 }
|
||||
ImGui.ColorEdit4("Color", tool_color)
|
||||
|
||||
-- Generate samples and plot them
|
||||
for n = 1, 100 do samples[n] = math.sin((n - 1) * 0.2 + ImGui.GetTime() * 1.5) end
|
||||
ImGui.PlotLines("Samples", samples, nil, 100)
|
||||
|
||||
-- Display contents in a scrolling region
|
||||
ImGui.TextColored(ImVec4(1, 1, 0, 1), "Important Stuff")
|
||||
ImGui.BeginChild("Scrolling")
|
||||
for n = 0, 49 do ImGui.Text("%04d: Some text", n) end
|
||||
ImGui.EndChild()
|
||||
ImGui.End()
|
||||
-- [/snippet]
|
||||
end }
|
||||
|
||||
local SC = assert(scenes[SCENE], "unknown SCENE")
|
||||
io.MouseDrawCursor = SC.cursor == true
|
||||
|
||||
-- ---------------------------------------------------------------- compositor
|
||||
local CX, CY, CW, CH = SC.crop[1], SC.crop[2], SC.crop[3], SC.crop[4]
|
||||
local function composite(gui)
|
||||
local screen = buffer.create(CW * CH * 3)
|
||||
for i = 0, CW * CH - 1 do buffer.writeu8(screen, i * 3, BG[1]); buffer.writeu8(screen, i * 3 + 1, BG[2]); buffer.writeu8(screen, i * 3 + 2, BG[3]) end
|
||||
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 >= ky0 and sy < ky1 then
|
||||
for tx = 0, sz - 1 do
|
||||
local sx = x + tx
|
||||
if sx >= kx0 and sx < kx1 then
|
||||
local o = (ty * sz + tx) * 4
|
||||
local a = buffer.readu8(px, o + 3)
|
||||
if a > 0 then
|
||||
local d = ((sy - CY) * CW + (sx - CX)) * 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, CX, CY, CX + CW, CY + CH)
|
||||
return screen
|
||||
end
|
||||
|
||||
local shoot = {}
|
||||
for _, s in ipairs(SC.shots) do shoot[s] = true end
|
||||
local frame = 0
|
||||
game:GetService("RunService").Stepped:Connect(function()
|
||||
frame += 1
|
||||
G.ImGui_ImplRoblox.NewFrame()
|
||||
io.DeltaTime = 1 / 30
|
||||
if type(SC.mouse) == "function" then io:AddMousePosEvent(SC.mouse(frame)) else io:AddMousePosEvent(SC.mouse[1], SC.mouse[2]) end
|
||||
local b = SC.button and SC.button(frame)
|
||||
if b ~= nil then io:AddMouseButtonEvent(0, b) end
|
||||
ImGui.NewFrame()
|
||||
SC.draw(frame)
|
||||
ImGui.Render()
|
||||
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||
if shoot[frame] then
|
||||
local screen = composite(game:GetService("Players").LocalPlayer.PlayerGui:FindFirstChild("ImGui"))
|
||||
print("SIZE", CW, CH)
|
||||
for y = 0, CH - 1 do
|
||||
local t = table.create(CW * 3)
|
||||
for i = 0, CW * 3 - 1 do t[i + 1] = string.format("%02x", buffer.readu8(screen, y * CW * 3 + i)) end
|
||||
print("PX", frame, y, table.concat(t))
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
# Renders README images into docs/images/: python3 tools/screenshots/make.py [scene ...]
|
||||
# Stills -> PNG (zlib, stdlib only); the "plot" scene -> GIF via ffmpeg (palette).
|
||||
import os, struct, subprocess, sys, tempfile, zlib
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OUT = os.path.join(ROOT, "docs", "images")
|
||||
STILLS = ["debug", "tool", "hello", "demo", "tables", "style", "metrics", "docking", "drawlist"]
|
||||
|
||||
def png(path, w, h, rows):
|
||||
raw = b"".join(b"\x00" + r for r in rows)
|
||||
def chunk(t, d): return struct.pack(">I", len(d)) + t + d + struct.pack(">I", zlib.crc32(t + d) & 0xffffffff)
|
||||
open(path, "wb").write(b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0))
|
||||
+ chunk(b"IDAT", zlib.compress(raw, 9)) + chunk(b"IEND", b""))
|
||||
|
||||
def run(scene):
|
||||
src = open(os.path.join(ROOT, "tools", "screenshots", "capture.luau")).read()
|
||||
tmp = os.path.join(tempfile.mkdtemp(), "scene.luau")
|
||||
open(tmp, "w").write('SCENE = "%s"\n' % scene + src)
|
||||
frames = 152 if scene == "plot" else 42
|
||||
p = subprocess.run([sys.executable, os.path.join(ROOT, "tools", "test", "run.py"), "--main", tmp, "--frames", str(frames),
|
||||
"--quiet", "--roblox", "--timeout", "1200"], capture_output=True, text=True, env=dict(os.environ, FULL_OUTPUT="1"))
|
||||
shots, size = {}, None
|
||||
for l in p.stdout.splitlines():
|
||||
if l.startswith("SIZE"): size = tuple(map(int, l.split()[1:]))
|
||||
elif l.startswith("PX"):
|
||||
_, f, y, hx = l.split(); shots.setdefault(int(f), {})[int(y)] = bytes.fromhex(hx)
|
||||
if not shots: sys.exit("scene %s failed:\n%s" % (scene, p.stdout[-3000:]))
|
||||
w, h = size
|
||||
return w, h, [[rows[y] for y in range(h)] for _, rows in sorted(shots.items())]
|
||||
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
for scene in sys.argv[1:] or STILLS + ["plot"]:
|
||||
w, h, frames = run(scene)
|
||||
if scene == "plot":
|
||||
d = tempfile.mkdtemp()
|
||||
for i, rows in enumerate(frames): png(os.path.join(d, "f%04d.png" % i), w, h, rows)
|
||||
subprocess.run(["ffmpeg", "-loglevel", "error", "-y", "-framerate", "30", "-i", os.path.join(d, "f%04d.png"), "-vf",
|
||||
"split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=none", "-loop", "0",
|
||||
os.path.join(OUT, "plot.gif")], check=True)
|
||||
else:
|
||||
png(os.path.join(OUT, scene + ".png"), w, h, frames[-1])
|
||||
print("wrote", scene)
|
||||
@@ -0,0 +1,139 @@
|
||||
-- Every code snippet in README.md, verbatim between the "-- [snippet]" markers. Must print README OK.
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local RunService = game:GetService("RunService")
|
||||
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
|
||||
local ImGui, ImVec2, ImVec4 = G.ImGui, G.ImVec2, G.ImVec4
|
||||
ImGui.CreateContext()
|
||||
G.ImGui_ImplRoblox.Init()
|
||||
|
||||
-- [snippet:hello]
|
||||
local f, counter = 0.6, 0
|
||||
local buf = ImGui.NewTextBuffer("Hello, world!", 256) -- InputText edits a byte buffer in place
|
||||
local my_color = ImVec4(1.0, 0.4, 0.2, 1.0)
|
||||
|
||||
local function MyFirstTool()
|
||||
ImGui.Begin("My First Tool")
|
||||
ImGui.Text("Hello, world %d", 123)
|
||||
if ImGui.Button("Save") then
|
||||
counter += 1
|
||||
end
|
||||
ImGui.InputText("string", buf, 256)
|
||||
f = ImGui.SliderFloat("float", f, 0.0, 1.0)
|
||||
ImGui.ColorEdit4("Color", my_color) -- ImVec4 arguments are edited in place
|
||||
ImGui.End()
|
||||
end
|
||||
-- [/snippet]
|
||||
|
||||
-- [snippet:conventions]
|
||||
local enabled, volume, mode = true, 50, 0
|
||||
local function Conventions()
|
||||
ImGui.Begin("Conventions")
|
||||
local pressed
|
||||
pressed, enabled = ImGui.Checkbox("Enabled", enabled) -- bool widgets: pressed, new value
|
||||
local changed
|
||||
volume, changed = ImGui.SliderInt("Volume", volume, 0, 100) -- scalar widgets: new value, changed
|
||||
mode = ImGui.Combo("Mode", mode, { "Easy", "Normal", "Hard" }) -- Combo/ListBox: 0-based index
|
||||
if pressed or changed then print("settings changed") end
|
||||
ImGui.End()
|
||||
end
|
||||
-- [/snippet]
|
||||
|
||||
-- [snippet:menus]
|
||||
local show_log = false
|
||||
local function MenusAndTable()
|
||||
ImGui.Begin("Inventory", nil, G.ImGuiWindowFlags.MenuBar)
|
||||
if ImGui.BeginMenuBar() then
|
||||
if ImGui.BeginMenu("View") then
|
||||
_, show_log = ImGui.MenuItem("Show log", nil, show_log)
|
||||
ImGui.EndMenu()
|
||||
end
|
||||
ImGui.EndMenuBar()
|
||||
end
|
||||
local flags = bit32.bor(G.ImGuiTableFlags.Borders, G.ImGuiTableFlags.RowBg)
|
||||
if ImGui.BeginTable("items", 3, flags) then
|
||||
ImGui.TableSetupColumn("Name")
|
||||
ImGui.TableSetupColumn("Count")
|
||||
ImGui.TableSetupColumn("Value")
|
||||
ImGui.TableHeadersRow()
|
||||
for _, item in ipairs({ { "Sword", 1, 120 }, { "Potion", 5, 15 }, { "Arrow", 64, 1 } }) do
|
||||
ImGui.TableNextRow()
|
||||
ImGui.TableNextColumn(); ImGui.Text(item[1])
|
||||
ImGui.TableNextColumn(); ImGui.Text("%d", item[2])
|
||||
ImGui.TableNextColumn(); ImGui.Text("%d", item[3])
|
||||
end
|
||||
ImGui.EndTable()
|
||||
end
|
||||
ImGui.End()
|
||||
end
|
||||
-- [/snippet]
|
||||
|
||||
-- [snippet:plot]
|
||||
local samples = {}
|
||||
local function Plot()
|
||||
for i = 1, 90 do
|
||||
samples[i] = math.sin(i / 90 * math.pi * 4 + os.clock() * 3)
|
||||
end
|
||||
ImGui.Begin("Plotting")
|
||||
ImGui.PlotLines("Lines", samples, nil, #samples, 0, "sine", -1.0, 1.0, ImVec2(0, 80))
|
||||
ImGui.ProgressBar((os.clock() * 0.4) % 1.0, ImVec2(-1, 0))
|
||||
ImGui.End()
|
||||
end
|
||||
-- [/snippet]
|
||||
|
||||
-- [snippet:drawlist]
|
||||
local function CustomDrawing()
|
||||
ImGui.Begin("Custom drawing")
|
||||
local draw_list = ImGui.GetWindowDrawList()
|
||||
local p = ImGui.GetCursorScreenPos()
|
||||
draw_list:AddRectFilled(p, p + ImVec2(100, 100), G.IM_COL32(255, 120, 60, 255), 12.0)
|
||||
draw_list:AddCircleFilled(p + ImVec2(170, 50), 45, G.IM_COL32(80, 160, 255, 255))
|
||||
draw_list:AddLine(p + ImVec2(0, 120), p + ImVec2(220, 120), G.IM_COL32(255, 255, 0, 255), 2.0)
|
||||
ImGui.Dummy(ImVec2(220, 130)) -- reserve the space so the window sizes/scrolls correctly
|
||||
ImGui.End()
|
||||
end
|
||||
-- [/snippet]
|
||||
|
||||
-- [snippet:tool]
|
||||
local my_tool_active, my_color2, samples2 = true, ImVec4(0.45, 0.28, 0.45, 1.0), {}
|
||||
local function MyTool()
|
||||
-- Create a window called "My First Tool", with a menu bar.
|
||||
my_tool_active = ImGui.Begin("My First Tool", my_tool_active, G.ImGuiWindowFlags.MenuBar)
|
||||
if ImGui.BeginMenuBar() then
|
||||
if ImGui.BeginMenu("File") then
|
||||
if ImGui.MenuItem("Open..", "Ctrl+O") then --[[ Do stuff ]] end
|
||||
if ImGui.MenuItem("Save", "Ctrl+S") then --[[ Do stuff ]] end
|
||||
if ImGui.MenuItem("Close", "Ctrl+W") then my_tool_active = false end
|
||||
ImGui.EndMenu()
|
||||
end
|
||||
ImGui.EndMenuBar()
|
||||
end
|
||||
|
||||
-- Edit a color stored as 4 floats
|
||||
ImGui.ColorEdit4("Color", my_color2)
|
||||
|
||||
-- Generate samples and plot them
|
||||
for n = 1, 100 do samples2[n] = math.sin((n - 1) * 0.2 + ImGui.GetTime() * 1.5) end
|
||||
ImGui.PlotLines("Samples", samples2, nil, 100)
|
||||
|
||||
-- Display contents in a scrolling region
|
||||
ImGui.TextColored(ImVec4(1, 1, 0, 1), "Important Stuff")
|
||||
ImGui.BeginChild("Scrolling")
|
||||
for n = 0, 49 do ImGui.Text("%04d: Some text", n) end
|
||||
ImGui.EndChild()
|
||||
ImGui.End()
|
||||
end
|
||||
-- [/snippet]
|
||||
|
||||
local frame = 0
|
||||
RunService.Stepped:Connect(function()
|
||||
frame += 1
|
||||
G.ImGui_ImplRoblox.NewFrame()
|
||||
ImGui.NewFrame()
|
||||
MyFirstTool(); MyTool(); Conventions(); MenusAndTable(); Plot(); CustomDrawing()
|
||||
ImGui.Render()
|
||||
G.ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||
if frame == 20 then
|
||||
assert(ImGui.TextBufferToString(buf) == "Hello, world!")
|
||||
print("README OK")
|
||||
end
|
||||
end)
|
||||