Compare commits
5
Commits
14e6da3b29
...
v0.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2558c30d32 | ||
|
|
e228ed8638 | ||
|
|
28dc08e043 | ||
|
|
f63a37f07d | ||
|
|
08356b4f1f |
+23
-23
@@ -10,7 +10,7 @@ Dear ImGui for Roblox (and more)
|
|||||||
|
|
||||||
A Lua/Luau port of [Dear ImGui](https://github.com/ocornut/imgui) (docking branch, 1.93 WIP). **Roblox is the primary target** (rendered with EditableImages); the same sources also run on [LÖVE](https://love2d.org) (LuaJIT, GPU renderer), and the backend layout leaves room for more. It follows the C++ source function by function, so upstream documentation, examples and the demo apply almost directly.
|
A Lua/Luau port of [Dear ImGui](https://github.com/ocornut/imgui) (docking branch, 1.93 WIP). **Roblox is the primary target** (rendered with EditableImages); the same sources also run on [LÖVE](https://love2d.org) (LuaJIT, GPU renderer), and the backend layout leaves room for more. It follows the C++ source function by function, so upstream documentation, examples and the demo apply almost directly.
|
||||||
|
|
||||||
| [The Pitch](#the-pitch) - [Usage](#usage) - [How it works](#how-it-works) - [Demo](#demo) - [Getting Started & Integration](#getting-started--integration) - [Backends](#backends) |
|
| [The Pitch](#the-pitch) - [Usage](#usage) - [Demo](#demo) - [Getting Started & Integration](#getting-started--integration-roblox) - [How it works](#how-it-works-roblox-backend) - [Backends](#backends) |
|
||||||
:----------------------------------------------------------: |
|
:----------------------------------------------------------: |
|
||||||
| [Differences from C++](#differences-from-c-dear-imgui) - [Performance](#performance) - [Testing](#testing) - [Not ported yet](#not-ported-yet) |
|
| [Differences from C++](#differences-from-c-dear-imgui) - [Performance](#performance) - [Testing](#testing) - [Not ported yet](#not-ported-yet) |
|
||||||
| [FAQ](#faq) - [Credits](#credits) - [License](#license) |
|
| [FAQ](#faq) - [Credits](#credits) - [License](#license) |
|
||||||
@@ -79,27 +79,6 @@ Dear ImGui allows you to **create elaborate tools** as well as very short-lived
|
|||||||
|
|
||||||
Every snippet in this README is run by [`tools/test/scripts/t_readme.luau`](/tools/test/scripts/t_readme.luau), and every image was rendered by this port with [`tools/screenshots/make.py`](/tools/screenshots/make.py).
|
Every snippet in this README is run by [`tools/test/scripts/t_readme.luau`](/tools/test/scripts/t_readme.luau), and every image was rendered by this port with [`tools/screenshots/make.py`](/tools/screenshots/make.py).
|
||||||
|
|
||||||
### How it works (Roblox backend)
|
|
||||||
|
|
||||||
Dear ImGui itself never draws anything: every frame it produces `ImDrawData`, a list of textured, coloured triangles plus clip rectangles, and expects the backend to put them on screen. On desktop that is a few GPU draw calls. Roblox has no API to draw arbitrary triangles in 2D, so the Roblox backend ([backends/imgui_impl_roblox.lua](/backends/imgui_impl_roblox.lua)) does what a GPU would do, in Luau, and pushes the result into **EditableImages**.
|
|
||||||
|
|
||||||
**Why software rasterization?** The alternatives don't fit Dear ImGui:
|
|
||||||
- *One GUI Instance per widget/shape* (what most Roblox UI libraries do) means re-creating, re-parenting or diffing thousands of Instances every frame, can't express anti-aliased curves, per-vertex gradients or font-atlas glyphs, and would no longer be Dear ImGui's renderer output.
|
|
||||||
- *Rasterizing the draw data* keeps the output identical to Dear ImGui's: the same fonts, anti-aliasing, rounding and gradients, and every widget (including custom `ImDrawList` drawing) works without backend support.
|
|
||||||
|
|
||||||
The cost is CPU time in Luau, so the backend is built around **not drawing**:
|
|
||||||
|
|
||||||
1. **Layers per window.** Every draw list (one per top-level window, popup, tooltip) gets its own `Frame` with `ZIndex` following Dear ImGui's draw order. Its content is rasterized relative to the window, so **moving a window only changes the Frame's `Position`**; no pixel is redrawn or uploaded.
|
|
||||||
2. **Tiles.** Each layer is covered by 128×128 EditableImage tiles (`ImageLabel`s). Every primitive is hashed (positions relative to the layer, UVs, colours, texture, clip) into the tiles it touches. **Only tiles whose hash changed are re-rasterized and uploaded**, and only the dirty region of those (`WritePixelsBuffer` with a sub-rectangle). A static UI uploads nothing.
|
|
||||||
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
|
|
||||||
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
|
|
||||||
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
|
|
||||||
6. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
|
|
||||||
|
|
||||||
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Games can't read the clipboard, so while a text field is active a hidden `TextBox` holds focus: the OS delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
|
|
||||||
|
|
||||||
**Instances inside windows.** The backend can place your own Instances (e.g. a `ViewportFrame`) inside a window's layer, so they stack, clip and move with it; see the `ImGui.Embed` widget in [examples/example_roblox/main.client.luau](/examples/example_roblox/main.client.luau).
|
|
||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
|
||||||
Calling `ImGui.ShowDemoWindow()` creates a demo window that showcases a variety of features and examples. The code is in [imgui_demo.lua](/imgui_demo.lua), like upstream's `imgui_demo.cpp`. **It's strongly recommended to keep it at hand as a reference**: you can find the code for any widget you see by searching for its label.
|
Calling `ImGui.ShowDemoWindow()` creates a demo window that showcases a variety of features and examples. The code is in [imgui_demo.lua](/imgui_demo.lua), like upstream's `imgui_demo.cpp`. **It's strongly recommended to keep it at hand as a reference**: you can find the code for any widget you see by searching for its label.
|
||||||
@@ -137,7 +116,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): add `ImGui = "bug/[email protected].0"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source:
|
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/[email protected].2"` 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`.
|
||||||
@@ -184,6 +163,27 @@ io.Fonts:AddFontDefault()
|
|||||||
io.Fonts:AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0)
|
io.Fonts:AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### How it works (Roblox backend)
|
||||||
|
|
||||||
|
Dear ImGui itself never draws anything: every frame it produces `ImDrawData`, a list of textured, coloured triangles plus clip rectangles, and expects the backend to put them on screen. On desktop that is a few GPU draw calls. Roblox has no API to draw arbitrary triangles in 2D, so the Roblox backend ([backends/imgui_impl_roblox.lua](/backends/imgui_impl_roblox.lua)) does what a GPU would do, in Luau, and pushes the result into **EditableImages**.
|
||||||
|
|
||||||
|
**Why software rasterization?** The alternatives don't fit Dear ImGui:
|
||||||
|
- *One GUI Instance per widget/shape* (what most Roblox UI libraries do) means re-creating, re-parenting or diffing thousands of Instances every frame, can't express anti-aliased curves, per-vertex gradients or font-atlas glyphs, and would no longer be Dear ImGui's renderer output.
|
||||||
|
- *Rasterizing the draw data* keeps the output identical to Dear ImGui's: the same fonts, anti-aliasing, rounding and gradients, and every widget (including custom `ImDrawList` drawing) works without backend support.
|
||||||
|
|
||||||
|
The cost is CPU time in Luau, so the backend is built around **not drawing**:
|
||||||
|
|
||||||
|
1. **Layers per window.** Every draw list (one per top-level window, popup, tooltip) gets its own `Frame` with `ZIndex` following Dear ImGui's draw order. Its content is rasterized relative to the window, so **moving a window only changes the Frame's `Position`**; no pixel is redrawn or uploaded.
|
||||||
|
2. **Tiles.** Each layer is covered by 128×128 EditableImage tiles (`ImageLabel`s). Every primitive is hashed (positions relative to the layer, UVs, colours, texture, clip) into the tiles it touches. **Only tiles whose hash changed are re-rasterized and uploaded**, and only the dirty region of those (`WritePixelsBuffer` with a sub-rectangle). A static UI uploads nothing.
|
||||||
|
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
|
||||||
|
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
|
||||||
|
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
|
||||||
|
6. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
|
||||||
|
|
||||||
|
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
|
||||||
|
|
||||||
|
**Instances inside windows.** The backend can place your own Instances (e.g. a `ViewportFrame`) inside a window's layer, so they stack, clip and move with it; see the `ImGui.Embed` widget in [examples/example_roblox/main.client.luau](/examples/example_roblox/main.client.luau).
|
||||||
|
|
||||||
### Backends
|
### Backends
|
||||||
|
|
||||||
| Backend | File | Build | Renderer | Example |
|
| Backend | File | Build | Renderer | Example |
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Dear ImGui for Roblox (and more)
|
|||||||
|
|
||||||
A Lua/Luau port of [Dear ImGui](https://github.com/ocornut/imgui) (docking branch, 1.93 WIP). **Roblox is the primary target** (rendered with EditableImages); the same sources also run on [LÖVE](https://love2d.org) (LuaJIT, GPU renderer), and the backend layout leaves room for more. It follows the C++ source function by function, so upstream documentation, examples and the demo apply almost directly.
|
A Lua/Luau port of [Dear ImGui](https://github.com/ocornut/imgui) (docking branch, 1.93 WIP). **Roblox is the primary target** (rendered with EditableImages); the same sources also run on [LÖVE](https://love2d.org) (LuaJIT, GPU renderer), and the backend layout leaves room for more. It follows the C++ source function by function, so upstream documentation, examples and the demo apply almost directly.
|
||||||
|
|
||||||
| [The Pitch](#the-pitch) - [Usage](#usage) - [How it works](#how-it-works) - [Demo](#demo) - [Getting Started & Integration](#getting-started--integration) - [Backends](#backends) |
|
| [The Pitch](#the-pitch) - [Usage](#usage) - [Demo](#demo) - [Getting Started & Integration](#getting-started--integration-roblox) - [How it works](#how-it-works-roblox-backend) - [Backends](#backends) |
|
||||||
:----------------------------------------------------------: |
|
:----------------------------------------------------------: |
|
||||||
| [Differences from C++](#differences-from-c-dear-imgui) - [Performance](#performance) - [Testing](#testing) - [Not ported yet](#not-ported-yet) |
|
| [Differences from C++](#differences-from-c-dear-imgui) - [Performance](#performance) - [Testing](#testing) - [Not ported yet](#not-ported-yet) |
|
||||||
| [FAQ](#faq) - [Credits](#credits) - [License](#license) |
|
| [FAQ](#faq) - [Credits](#credits) - [License](#license) |
|
||||||
@@ -76,27 +76,6 @@ Dear ImGui allows you to **create elaborate tools** as well as very short-lived
|
|||||||
|
|
||||||
Every snippet in this README is run by [`tools/test/scripts/t_readme.luau`](tools/test/scripts/t_readme.luau), and every image was rendered by this port with [`tools/screenshots/make.py`](tools/screenshots/make.py).
|
Every snippet in this README is run by [`tools/test/scripts/t_readme.luau`](tools/test/scripts/t_readme.luau), and every image was rendered by this port with [`tools/screenshots/make.py`](tools/screenshots/make.py).
|
||||||
|
|
||||||
### How it works (Roblox backend)
|
|
||||||
|
|
||||||
Dear ImGui itself never draws anything: every frame it produces `ImDrawData`, a list of textured, coloured triangles plus clip rectangles, and expects the backend to put them on screen. On desktop that is a few GPU draw calls. Roblox has no API to draw arbitrary triangles in 2D, so the Roblox backend ([backends/imgui_impl_roblox.lua](backends/imgui_impl_roblox.lua)) does what a GPU would do, in Luau, and pushes the result into **EditableImages**.
|
|
||||||
|
|
||||||
**Why software rasterization?** The alternatives don't fit Dear ImGui:
|
|
||||||
- *One GUI Instance per widget/shape* (what most Roblox UI libraries do) means re-creating, re-parenting or diffing thousands of Instances every frame, can't express anti-aliased curves, per-vertex gradients or font-atlas glyphs, and would no longer be Dear ImGui's renderer output.
|
|
||||||
- *Rasterizing the draw data* keeps the output identical to Dear ImGui's: the same fonts, anti-aliasing, rounding and gradients, and every widget (including custom `ImDrawList` drawing) works without backend support.
|
|
||||||
|
|
||||||
The cost is CPU time in Luau, so the backend is built around **not drawing**:
|
|
||||||
|
|
||||||
1. **Layers per window.** Every draw list (one per top-level window, popup, tooltip) gets its own `Frame` with `ZIndex` following Dear ImGui's draw order. Its content is rasterized relative to the window, so **moving a window only changes the Frame's `Position`**; no pixel is redrawn or uploaded.
|
|
||||||
2. **Tiles.** Each layer is covered by 128×128 EditableImage tiles (`ImageLabel`s). Every primitive is hashed (positions relative to the layer, UVs, colours, texture, clip) into the tiles it touches. **Only tiles whose hash changed are re-rasterized and uploaded**, and only the dirty region of those (`WritePixelsBuffer` with a sub-rectangle). A static UI uploads nothing.
|
|
||||||
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
|
|
||||||
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
|
|
||||||
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
|
|
||||||
6. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
|
|
||||||
|
|
||||||
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Games can't read the clipboard, so while a text field is active a hidden `TextBox` holds focus: the OS delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
|
|
||||||
|
|
||||||
**Instances inside windows.** The backend can place your own Instances (e.g. a `ViewportFrame`) inside a window's layer, so they stack, clip and move with it; see the `ImGui.Embed` widget in [examples/example_roblox/main.client.luau](examples/example_roblox/main.client.luau).
|
|
||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
|
||||||
Calling `ImGui.ShowDemoWindow()` creates a demo window that showcases a variety of features and examples. The code is in [imgui_demo.lua](imgui_demo.lua), like upstream's `imgui_demo.cpp`. **It's strongly recommended to keep it at hand as a reference**: you can find the code for any widget you see by searching for its label.
|
Calling `ImGui.ShowDemoWindow()` creates a demo window that showcases a variety of features and examples. The code is in [imgui_demo.lua](imgui_demo.lua), like upstream's `imgui_demo.cpp`. **It's strongly recommended to keep it at hand as a reference**: you can find the code for any widget you see by searching for its label.
|
||||||
@@ -134,7 +113,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): add `ImGui = "bug/[email protected].0"` under `[dependencies]` in your `wally.toml`, run `wally install`, then `require(ReplicatedStorage.Packages.ImGui)`. Or from source:
|
With [Wally](https://wally.run) ([4dbug/imgui-luau](https://wally.run/package/4dbug/imgui-luau)): add `ImGui = "4dbug/[email protected].2"` 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`.
|
||||||
@@ -181,6 +160,27 @@ io.Fonts:AddFontDefault()
|
|||||||
io.Fonts:AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0)
|
io.Fonts:AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### How it works (Roblox backend)
|
||||||
|
|
||||||
|
Dear ImGui itself never draws anything: every frame it produces `ImDrawData`, a list of textured, coloured triangles plus clip rectangles, and expects the backend to put them on screen. On desktop that is a few GPU draw calls. Roblox has no API to draw arbitrary triangles in 2D, so the Roblox backend ([backends/imgui_impl_roblox.lua](backends/imgui_impl_roblox.lua)) does what a GPU would do, in Luau, and pushes the result into **EditableImages**.
|
||||||
|
|
||||||
|
**Why software rasterization?** The alternatives don't fit Dear ImGui:
|
||||||
|
- *One GUI Instance per widget/shape* (what most Roblox UI libraries do) means re-creating, re-parenting or diffing thousands of Instances every frame, can't express anti-aliased curves, per-vertex gradients or font-atlas glyphs, and would no longer be Dear ImGui's renderer output.
|
||||||
|
- *Rasterizing the draw data* keeps the output identical to Dear ImGui's: the same fonts, anti-aliasing, rounding and gradients, and every widget (including custom `ImDrawList` drawing) works without backend support.
|
||||||
|
|
||||||
|
The cost is CPU time in Luau, so the backend is built around **not drawing**:
|
||||||
|
|
||||||
|
1. **Layers per window.** Every draw list (one per top-level window, popup, tooltip) gets its own `Frame` with `ZIndex` following Dear ImGui's draw order. Its content is rasterized relative to the window, so **moving a window only changes the Frame's `Position`**; no pixel is redrawn or uploaded.
|
||||||
|
2. **Tiles.** Each layer is covered by 128×128 EditableImage tiles (`ImageLabel`s). Every primitive is hashed (positions relative to the layer, UVs, colours, texture, clip) into the tiles it touches. **Only tiles whose hash changed are re-rasterized and uploaded**, and only the dirty region of those (`WritePixelsBuffer` with a sub-rectangle). A static UI uploads nothing.
|
||||||
|
3. **Scrolling reuses pixels.** Window content is a separate layer inside a `ClipsDescendants` frame sized to the window's visible area, positioned at the scroll offset. Scrolling moves it like dragging moves a window; only rows entering view are drawn. Decorations (title bar, borders, scrollbar) stay in their own layers.
|
||||||
|
4. **Skipping unchanged windows.** Vertices are stored in a flat Luau `buffer` (36 bytes each) instead of tables. Before any per-tile work, a window's raw vertex/index bytes are compared with last frame's; if identical, its layers are just restacked.
|
||||||
|
5. **Fast rasterizer.** `@native` functions; axis-aligned quads (most rects and every glyph) take a span-fill path, flat rows are copied instead of re-blended, gradient rects (color pickers) are filled per row, and only real triangles (anti-aliased edges, curves) use the generic edge-function rasterizer.
|
||||||
|
6. **Render rate.** Tiles update at most `SetRenderRate(hz)` times per second (default 60), optionally lower while scrolling/resizing (`SetBusyRenderRate`). Your UI code and Dear ImGui's logic still run every frame, so input is never dropped.
|
||||||
|
|
||||||
|
**Input.** Mouse and keys come from `UserInputService`. While the mouse is over Dear ImGui (`io.WantCaptureMouse`), the tiles are `Active`, so clicks and the wheel don't reach the game (camera zoom). Typing comes from key codes by default (printable ASCII). Call `ImGui_ImplRoblox.SetTextProxy(true)` to opt in to a hidden `TextBox` that holds focus while a text field is active: the OS then delivers typing (any layout), paste and copy/cut through it, and its text mirrors Dear ImGui's current selection. Fonts are TTFs rasterized by the ported stb_truetype into the font atlas, which becomes a backend texture like any image (`CreateTexture` works for your own pixels too).
|
||||||
|
|
||||||
|
**Instances inside windows.** The backend can place your own Instances (e.g. a `ViewportFrame`) inside a window's layer, so they stack, clip and move with it; see the `ImGui.Embed` widget in [examples/example_roblox/main.client.luau](examples/example_roblox/main.client.luau).
|
||||||
|
|
||||||
### Backends
|
### Backends
|
||||||
|
|
||||||
| Backend | File | Build | Renderer | Example |
|
| Backend | File | Build | Renderer | Example |
|
||||||
|
|||||||
@@ -1127,8 +1127,23 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Text proxy: a hidden TextBox holds focus while an imgui text field is active, so the OS gives us typing (any
|
bd.PendingChars = {}
|
||||||
-- layout/unicode), paste (Ctrl+V) and copy/cut (Ctrl+C/X of the proxy's text = imgui's current selection).
|
|
||||||
|
connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end)
|
||||||
|
connect(UserInputService.WindowFocusReleased, function() ImGui.GetIO():AddFocusEvent(false) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Text proxy (opt-in, ImGui_ImplRoblox.SetTextProxy(true)): a hidden TextBox holds focus while an imgui text field is
|
||||||
|
-- active, so the OS gives us typing (any layout/unicode), paste (Ctrl+V) and copy/cut (Ctrl+C/X of imgui's selection).
|
||||||
|
-- Off: typing comes from KeyCodes (printable ASCII), no OS clipboard.
|
||||||
|
function ImGui_ImplRoblox_SetTextProxy(enabled)
|
||||||
|
local bd = ImGui_ImplRoblox_GetBackendData()
|
||||||
|
if not enabled then
|
||||||
|
if bd.Proxy then bd.Proxy:Destroy(); bd.Proxy = nil end
|
||||||
|
proxy_focused = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if bd.Proxy then return end
|
||||||
local proxy = Instance.new("TextBox")
|
local proxy = Instance.new("TextBox")
|
||||||
proxy.Name = "TextProxy"
|
proxy.Name = "TextProxy"
|
||||||
proxy.BackgroundTransparency = 1
|
proxy.BackgroundTransparency = 1
|
||||||
@@ -1140,12 +1155,11 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
|
|||||||
proxy.ClearTextOnFocus = false
|
proxy.ClearTextOnFocus = false
|
||||||
proxy.TextEditable = true
|
proxy.TextEditable = true
|
||||||
proxy.Text = ""
|
proxy.Text = ""
|
||||||
proxy.Parent = gui
|
proxy.Parent = bd.Gui
|
||||||
bd.Proxy, bd.ProxyText, bd.PendingChars = proxy, "", {}
|
bd.Proxy, bd.ProxyText = proxy, ""
|
||||||
-- Clicking elsewhere (or Roblox taking focus) ends the edit, like clicking outside the field in imgui.
|
-- Clicking elsewhere (or Roblox taking focus) ends the edit, like clicking outside the field in imgui.
|
||||||
-- (Text is polled in NewFrame instead of using Text changed signals: those are deferred in Roblox.)
|
-- (Text is polled in NewFrame instead of using Text changed signals: those are deferred in Roblox.)
|
||||||
connect(proxy.FocusLost, function()
|
proxy.FocusLost:Connect(function()
|
||||||
if bd.ProxyDebug then print("[ImGui text proxy] FocusLost, mouse over imgui:", ImGui.GetIO().WantCaptureMouse) end
|
|
||||||
if bd.ProxyReleasing then return end
|
if bd.ProxyReleasing then return end
|
||||||
-- Roblox drops TextBox focus on any click, including clicks inside imgui (e.g. moving the cursor within the
|
-- Roblox drops TextBox focus on any click, including clicks inside imgui (e.g. moving the cursor within the
|
||||||
-- same field). Those must not end the edit: just recapture next frame. Only a click outside imgui ends it.
|
-- same field). Those must not end the edit: just recapture next frame. Only a click outside imgui ends it.
|
||||||
@@ -1154,9 +1168,6 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
|
|||||||
local g = ImGui.GetCurrentContext()
|
local g = ImGui.GetCurrentContext()
|
||||||
if g and g.ActiveId ~= 0 and g.ActiveId == bd.ProxyActiveId then ImGui.ClearActiveID() end
|
if g and g.ActiveId ~= 0 and g.ActiveId == bd.ProxyActiveId then ImGui.ClearActiveID() end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end)
|
|
||||||
connect(UserInputService.WindowFocusReleased, function() ImGui.GetIO():AddFocusEvent(false) end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function ImGui_ImplRoblox_Shutdown()
|
function ImGui_ImplRoblox_Shutdown()
|
||||||
@@ -1201,15 +1212,16 @@ function ImGui_ImplRoblox_NewFrame()
|
|||||||
-- otherwise the KeyCode fallback keeps typing working.
|
-- otherwise the KeyCode fallback keeps typing working.
|
||||||
local proxy = bd.Proxy
|
local proxy = bd.Proxy
|
||||||
local g = ImGui.GetCurrentContext()
|
local g = ImGui.GetCurrentContext()
|
||||||
local focused_box = UserInputService:GetFocusedTextBox()
|
local focused_box = proxy and UserInputService:GetFocusedTextBox()
|
||||||
if io.WantTextInput and g.ActiveId ~= 0 then
|
if not proxy then
|
||||||
|
-- text proxy disabled: KeyCode typing only
|
||||||
|
elseif io.WantTextInput and g.ActiveId ~= 0 then
|
||||||
if g.ActiveId ~= bd.ProxyActiveId or g.ActiveIdIsJustActivated or g.IO.MouseClicked[0] then bd.ProxyActiveId = g.ActiveId; bd.ProxyLostId = nil end -- (re)clicking a field recaptures
|
if g.ActiveId ~= bd.ProxyActiveId or g.ActiveIdIsJustActivated or g.IO.MouseClicked[0] then bd.ProxyActiveId = g.ActiveId; bd.ProxyLostId = nil end -- (re)clicking a field recaptures
|
||||||
local mouse_up = not (io.MouseDown[0] or io.MouseDown[1] or io.MouseDown[2])
|
local mouse_up = not (io.MouseDown[0] or io.MouseDown[1] or io.MouseDown[2])
|
||||||
if focused_box ~= proxy and mouse_up and bd.ProxyLostId ~= g.ActiveId and (focused_box == nil or not focused_box:IsDescendantOf(game)) then
|
if focused_box ~= proxy and mouse_up and bd.ProxyLostId ~= g.ActiveId and (focused_box == nil or not focused_box:IsDescendantOf(game)) then
|
||||||
bd.ProxyText = proxy.Text -- what it already holds is not new input (else re-typing it would collapse imgui's selection)
|
bd.ProxyText = proxy.Text -- what it already holds is not new input (else re-typing it would collapse imgui's selection)
|
||||||
proxy:CaptureFocus()
|
proxy:CaptureFocus()
|
||||||
focused_box = UserInputService:GetFocusedTextBox()
|
focused_box = UserInputService:GetFocusedTextBox()
|
||||||
if bd.ProxyDebug then print("[ImGui text proxy] CaptureFocus ->", focused_box == proxy and "focused" or ("FAILED, focused = " .. tostring(focused_box))) end
|
|
||||||
end
|
end
|
||||||
elseif focused_box == proxy then
|
elseif focused_box == proxy then
|
||||||
bd.ProxyReleasing = true
|
bd.ProxyReleasing = true
|
||||||
@@ -1217,13 +1229,11 @@ function ImGui_ImplRoblox_NewFrame()
|
|||||||
bd.ProxyReleasing = false
|
bd.ProxyReleasing = false
|
||||||
focused_box = nil
|
focused_box = nil
|
||||||
end
|
end
|
||||||
local was_focused = proxy_focused
|
proxy_focused = proxy ~= nil and focused_box == proxy
|
||||||
proxy_focused = focused_box == proxy
|
|
||||||
local used_proxy_text = false
|
local used_proxy_text = false
|
||||||
if proxy_focused then
|
if proxy_focused then
|
||||||
-- 1) whatever the OS put in the proxy since last frame (typing, paste, cut) replaces imgui's selection
|
-- 1) whatever the OS put in the proxy since last frame (typing, paste, cut) replaces imgui's selection
|
||||||
local text = proxy.Text
|
local text = proxy.Text
|
||||||
if text ~= bd.ProxyText and bd.ProxyDebug then print("[ImGui text proxy] text", string.format("%q", text), "was", string.format("%q", tostring(bd.ProxyText))) end
|
|
||||||
if text ~= bd.ProxyText and text ~= bd.ProxyLastRaw then -- (LastRaw: don't re-send if our reset hasn't applied yet)
|
if text ~= bd.ProxyText and text ~= bd.ProxyLastRaw then -- (LastRaw: don't re-send if our reset hasn't applied yet)
|
||||||
bd.ProxyLastRaw = text
|
bd.ProxyLastRaw = text
|
||||||
-- InputText ignores characters while Ctrl is held (upstream, so shortcuts don't type); a paste arrives with
|
-- InputText ignores characters while Ctrl is held (upstream, so shortcuts don't type); a paste arrives with
|
||||||
@@ -1259,7 +1269,6 @@ function ImGui_ImplRoblox_NewFrame()
|
|||||||
proxy.Text = sel
|
proxy.Text = sel
|
||||||
bd.ProxyLastRaw = nil
|
bd.ProxyLastRaw = nil
|
||||||
bd.ProxyText = sel
|
bd.ProxyText = sel
|
||||||
if bd.ProxyDebug then print("[ImGui text proxy] mirror selection", string.format("%q", sel)) end
|
|
||||||
end
|
end
|
||||||
-- keep imgui's selection fully selected in the proxy, so Ctrl+C copies it and typing/Ctrl+V replaces it
|
-- keep imgui's selection fully selected in the proxy, so Ctrl+C copies it and typing/Ctrl+V replaces it
|
||||||
local want_sel, want_cur = (#sel > 0) and 1 or -1, #sel + 1
|
local want_sel, want_cur = (#sel > 0) and 1 or -1, #sel + 1
|
||||||
@@ -1275,7 +1284,6 @@ function ImGui_ImplRoblox_NewFrame()
|
|||||||
if not used_proxy_text then for _, c in ipairs(pending) do io:AddInputCharacter(c) end end
|
if not used_proxy_text then for _, c in ipairs(pending) do io:AddInputCharacter(c) end end
|
||||||
table.clear(pending)
|
table.clear(pending)
|
||||||
end
|
end
|
||||||
if bd.ProxyDebug and was_focused ~= proxy_focused then print("[ImGui text proxy] proxy_focused =", proxy_focused) end
|
|
||||||
|
|
||||||
-- While the mouse is over imgui, make the tiles Active so Roblox stops passing clicks/wheel to the game (camera zoom etc.)
|
-- While the mouse is over imgui, make the tiles Active so Roblox stops passing clicks/wheel to the game (camera zoom etc.)
|
||||||
local want = io.WantCaptureMouse
|
local want = io.WantCaptureMouse
|
||||||
@@ -1317,7 +1325,7 @@ ImGui_ImplRoblox = {
|
|||||||
SetRenderScale = ImGui_ImplRoblox_SetRenderScale,
|
SetRenderScale = ImGui_ImplRoblox_SetRenderScale,
|
||||||
SetRenderRate = ImGui_ImplRoblox_SetRenderRate,
|
SetRenderRate = ImGui_ImplRoblox_SetRenderRate,
|
||||||
SetOverlay = ImGui_ImplRoblox_SetOverlay,
|
SetOverlay = ImGui_ImplRoblox_SetOverlay,
|
||||||
SetTextProxyDebug = function(on) ImGui_ImplRoblox_GetBackendData().ProxyDebug = on end, -- prints focus/text events
|
SetTextProxy = ImGui_ImplRoblox_SetTextProxy,
|
||||||
CreateTexture = ImGui_ImplRoblox_CreateTexture,
|
CreateTexture = ImGui_ImplRoblox_CreateTexture,
|
||||||
DestroyTexture = ImGui_ImplRoblox_DestroyTexture,
|
DestroyTexture = ImGui_ImplRoblox_DestroyTexture,
|
||||||
GetRenderScale = function() return ImGui_ImplRoblox_GetBackendData().Scale end,
|
GetRenderScale = function() return ImGui_ImplRoblox_GetBackendData().Scale end,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[[12, "imgui.lua"], [18510, "imgui_demo.lua"], [25795, "imgui_draw.lua"], [31033, "imgui_h.lua"], [33231, "imgui_impl_roblox.lua"], [34563, "imgui_impl_roblox_fonts.lua"], [40966, "imgui_internal.lua"], [44142, "imgui_tables.lua"], [48489, "imgui_widgets.lua"], [58244, "imstb_rectpack.lua"], [58690, "imstb_textedit.lua"], [59931, "imstb_truetype.lua"], [62399, "imstd_minstdio.lua"]]
|
[[12, "imgui.lua"], [18510, "imgui_demo.lua"], [25795, "imgui_draw.lua"], [31033, "imgui_h.lua"], [33231, "imgui_impl_roblox.lua"], [34571, "imgui_impl_roblox_fonts.lua"], [40974, "imgui_internal.lua"], [44150, "imgui_tables.lua"], [48497, "imgui_widgets.lua"], [58252, "imstb_rectpack.lua"], [58698, "imstb_textedit.lua"], [59939, "imstb_truetype.lua"], [62407, "imstd_minstdio.lua"]]
|
||||||
+27
-18
@@ -34357,8 +34357,23 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Text proxy: a hidden TextBox holds focus while an imgui text field is active, so the OS gives us typing (any
|
bd.PendingChars = {}
|
||||||
-- layout/unicode), paste (Ctrl+V) and copy/cut (Ctrl+C/X of the proxy's text = imgui's current selection).
|
|
||||||
|
connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end)
|
||||||
|
connect(UserInputService.WindowFocusReleased, function() ImGui.GetIO():AddFocusEvent(false) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Text proxy (opt-in, ImGui_ImplRoblox.SetTextProxy(true)): a hidden TextBox holds focus while an imgui text field is
|
||||||
|
-- active, so the OS gives us typing (any layout/unicode), paste (Ctrl+V) and copy/cut (Ctrl+C/X of imgui's selection).
|
||||||
|
-- Off: typing comes from KeyCodes (printable ASCII), no OS clipboard.
|
||||||
|
function ImGui_ImplRoblox_SetTextProxy(enabled)
|
||||||
|
local bd = ImGui_ImplRoblox_GetBackendData()
|
||||||
|
if not enabled then
|
||||||
|
if bd.Proxy then bd.Proxy:Destroy(); bd.Proxy = nil end
|
||||||
|
proxy_focused = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if bd.Proxy then return end
|
||||||
local proxy = Instance.new("TextBox")
|
local proxy = Instance.new("TextBox")
|
||||||
proxy.Name = "TextProxy"
|
proxy.Name = "TextProxy"
|
||||||
proxy.BackgroundTransparency = 1
|
proxy.BackgroundTransparency = 1
|
||||||
@@ -34370,12 +34385,11 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
|
|||||||
proxy.ClearTextOnFocus = false
|
proxy.ClearTextOnFocus = false
|
||||||
proxy.TextEditable = true
|
proxy.TextEditable = true
|
||||||
proxy.Text = ""
|
proxy.Text = ""
|
||||||
proxy.Parent = gui
|
proxy.Parent = bd.Gui
|
||||||
bd.Proxy, bd.ProxyText, bd.PendingChars = proxy, "", {}
|
bd.Proxy, bd.ProxyText = proxy, ""
|
||||||
-- Clicking elsewhere (or Roblox taking focus) ends the edit, like clicking outside the field in imgui.
|
-- Clicking elsewhere (or Roblox taking focus) ends the edit, like clicking outside the field in imgui.
|
||||||
-- (Text is polled in NewFrame instead of using Text changed signals: those are deferred in Roblox.)
|
-- (Text is polled in NewFrame instead of using Text changed signals: those are deferred in Roblox.)
|
||||||
connect(proxy.FocusLost, function()
|
proxy.FocusLost:Connect(function()
|
||||||
if bd.ProxyDebug then print("[ImGui text proxy] FocusLost, mouse over imgui:", ImGui.GetIO().WantCaptureMouse) end
|
|
||||||
if bd.ProxyReleasing then return end
|
if bd.ProxyReleasing then return end
|
||||||
-- Roblox drops TextBox focus on any click, including clicks inside imgui (e.g. moving the cursor within the
|
-- Roblox drops TextBox focus on any click, including clicks inside imgui (e.g. moving the cursor within the
|
||||||
-- same field). Those must not end the edit: just recapture next frame. Only a click outside imgui ends it.
|
-- same field). Those must not end the edit: just recapture next frame. Only a click outside imgui ends it.
|
||||||
@@ -34384,9 +34398,6 @@ function ImGui_ImplRoblox_Init(parent, render_scale)
|
|||||||
local g = ImGui.GetCurrentContext()
|
local g = ImGui.GetCurrentContext()
|
||||||
if g and g.ActiveId ~= 0 and g.ActiveId == bd.ProxyActiveId then ImGui.ClearActiveID() end
|
if g and g.ActiveId ~= 0 and g.ActiveId == bd.ProxyActiveId then ImGui.ClearActiveID() end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
connect(UserInputService.WindowFocused, function() ImGui.GetIO():AddFocusEvent(true) end)
|
|
||||||
connect(UserInputService.WindowFocusReleased, function() ImGui.GetIO():AddFocusEvent(false) end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function ImGui_ImplRoblox_Shutdown()
|
function ImGui_ImplRoblox_Shutdown()
|
||||||
@@ -34431,15 +34442,16 @@ function ImGui_ImplRoblox_NewFrame()
|
|||||||
-- otherwise the KeyCode fallback keeps typing working.
|
-- otherwise the KeyCode fallback keeps typing working.
|
||||||
local proxy = bd.Proxy
|
local proxy = bd.Proxy
|
||||||
local g = ImGui.GetCurrentContext()
|
local g = ImGui.GetCurrentContext()
|
||||||
local focused_box = UserInputService:GetFocusedTextBox()
|
local focused_box = proxy and UserInputService:GetFocusedTextBox()
|
||||||
if io.WantTextInput and g.ActiveId ~= 0 then
|
if not proxy then
|
||||||
|
-- text proxy disabled: KeyCode typing only
|
||||||
|
elseif io.WantTextInput and g.ActiveId ~= 0 then
|
||||||
if g.ActiveId ~= bd.ProxyActiveId or g.ActiveIdIsJustActivated or g.IO.MouseClicked[0] then bd.ProxyActiveId = g.ActiveId; bd.ProxyLostId = nil end -- (re)clicking a field recaptures
|
if g.ActiveId ~= bd.ProxyActiveId or g.ActiveIdIsJustActivated or g.IO.MouseClicked[0] then bd.ProxyActiveId = g.ActiveId; bd.ProxyLostId = nil end -- (re)clicking a field recaptures
|
||||||
local mouse_up = not (io.MouseDown[0] or io.MouseDown[1] or io.MouseDown[2])
|
local mouse_up = not (io.MouseDown[0] or io.MouseDown[1] or io.MouseDown[2])
|
||||||
if focused_box ~= proxy and mouse_up and bd.ProxyLostId ~= g.ActiveId and (focused_box == nil or not focused_box:IsDescendantOf(game)) then
|
if focused_box ~= proxy and mouse_up and bd.ProxyLostId ~= g.ActiveId and (focused_box == nil or not focused_box:IsDescendantOf(game)) then
|
||||||
bd.ProxyText = proxy.Text -- what it already holds is not new input (else re-typing it would collapse imgui's selection)
|
bd.ProxyText = proxy.Text -- what it already holds is not new input (else re-typing it would collapse imgui's selection)
|
||||||
proxy:CaptureFocus()
|
proxy:CaptureFocus()
|
||||||
focused_box = UserInputService:GetFocusedTextBox()
|
focused_box = UserInputService:GetFocusedTextBox()
|
||||||
if bd.ProxyDebug then print("[ImGui text proxy] CaptureFocus ->", focused_box == proxy and "focused" or ("FAILED, focused = " .. tostring(focused_box))) end
|
|
||||||
end
|
end
|
||||||
elseif focused_box == proxy then
|
elseif focused_box == proxy then
|
||||||
bd.ProxyReleasing = true
|
bd.ProxyReleasing = true
|
||||||
@@ -34447,13 +34459,11 @@ function ImGui_ImplRoblox_NewFrame()
|
|||||||
bd.ProxyReleasing = false
|
bd.ProxyReleasing = false
|
||||||
focused_box = nil
|
focused_box = nil
|
||||||
end
|
end
|
||||||
local was_focused = proxy_focused
|
proxy_focused = proxy ~= nil and focused_box == proxy
|
||||||
proxy_focused = focused_box == proxy
|
|
||||||
local used_proxy_text = false
|
local used_proxy_text = false
|
||||||
if proxy_focused then
|
if proxy_focused then
|
||||||
-- 1) whatever the OS put in the proxy since last frame (typing, paste, cut) replaces imgui's selection
|
-- 1) whatever the OS put in the proxy since last frame (typing, paste, cut) replaces imgui's selection
|
||||||
local text = proxy.Text
|
local text = proxy.Text
|
||||||
if text ~= bd.ProxyText and bd.ProxyDebug then print("[ImGui text proxy] text", string.format("%q", text), "was", string.format("%q", tostring(bd.ProxyText))) end
|
|
||||||
if text ~= bd.ProxyText and text ~= bd.ProxyLastRaw then -- (LastRaw: don't re-send if our reset hasn't applied yet)
|
if text ~= bd.ProxyText and text ~= bd.ProxyLastRaw then -- (LastRaw: don't re-send if our reset hasn't applied yet)
|
||||||
bd.ProxyLastRaw = text
|
bd.ProxyLastRaw = text
|
||||||
-- InputText ignores characters while Ctrl is held (upstream, so shortcuts don't type); a paste arrives with
|
-- InputText ignores characters while Ctrl is held (upstream, so shortcuts don't type); a paste arrives with
|
||||||
@@ -34489,7 +34499,6 @@ function ImGui_ImplRoblox_NewFrame()
|
|||||||
proxy.Text = sel
|
proxy.Text = sel
|
||||||
bd.ProxyLastRaw = nil
|
bd.ProxyLastRaw = nil
|
||||||
bd.ProxyText = sel
|
bd.ProxyText = sel
|
||||||
if bd.ProxyDebug then print("[ImGui text proxy] mirror selection", string.format("%q", sel)) end
|
|
||||||
end
|
end
|
||||||
-- keep imgui's selection fully selected in the proxy, so Ctrl+C copies it and typing/Ctrl+V replaces it
|
-- keep imgui's selection fully selected in the proxy, so Ctrl+C copies it and typing/Ctrl+V replaces it
|
||||||
local want_sel, want_cur = (#sel > 0) and 1 or -1, #sel + 1
|
local want_sel, want_cur = (#sel > 0) and 1 or -1, #sel + 1
|
||||||
@@ -34505,7 +34514,6 @@ function ImGui_ImplRoblox_NewFrame()
|
|||||||
if not used_proxy_text then for _, c in ipairs(pending) do io:AddInputCharacter(c) end end
|
if not used_proxy_text then for _, c in ipairs(pending) do io:AddInputCharacter(c) end end
|
||||||
table.clear(pending)
|
table.clear(pending)
|
||||||
end
|
end
|
||||||
if bd.ProxyDebug and was_focused ~= proxy_focused then print("[ImGui text proxy] proxy_focused =", proxy_focused) end
|
|
||||||
|
|
||||||
-- While the mouse is over imgui, make the tiles Active so Roblox stops passing clicks/wheel to the game (camera zoom etc.)
|
-- While the mouse is over imgui, make the tiles Active so Roblox stops passing clicks/wheel to the game (camera zoom etc.)
|
||||||
local want = io.WantCaptureMouse
|
local want = io.WantCaptureMouse
|
||||||
@@ -34547,7 +34555,7 @@ ImGui_ImplRoblox = {
|
|||||||
SetRenderScale = ImGui_ImplRoblox_SetRenderScale,
|
SetRenderScale = ImGui_ImplRoblox_SetRenderScale,
|
||||||
SetRenderRate = ImGui_ImplRoblox_SetRenderRate,
|
SetRenderRate = ImGui_ImplRoblox_SetRenderRate,
|
||||||
SetOverlay = ImGui_ImplRoblox_SetOverlay,
|
SetOverlay = ImGui_ImplRoblox_SetOverlay,
|
||||||
SetTextProxyDebug = function(on) ImGui_ImplRoblox_GetBackendData().ProxyDebug = on end, -- prints focus/text events
|
SetTextProxy = ImGui_ImplRoblox_SetTextProxy,
|
||||||
CreateTexture = ImGui_ImplRoblox_CreateTexture,
|
CreateTexture = ImGui_ImplRoblox_CreateTexture,
|
||||||
DestroyTexture = ImGui_ImplRoblox_DestroyTexture,
|
DestroyTexture = ImGui_ImplRoblox_DestroyTexture,
|
||||||
GetRenderScale = function() return ImGui_ImplRoblox_GetBackendData().Scale end,
|
GetRenderScale = function() return ImGui_ImplRoblox_GetBackendData().Scale end,
|
||||||
@@ -63018,6 +63026,7 @@ return {
|
|||||||
ImGui_ImplRoblox_SetProfiling = ImGui_ImplRoblox_SetProfiling,
|
ImGui_ImplRoblox_SetProfiling = ImGui_ImplRoblox_SetProfiling,
|
||||||
ImGui_ImplRoblox_SetRenderRate = ImGui_ImplRoblox_SetRenderRate,
|
ImGui_ImplRoblox_SetRenderRate = ImGui_ImplRoblox_SetRenderRate,
|
||||||
ImGui_ImplRoblox_SetRenderScale = ImGui_ImplRoblox_SetRenderScale,
|
ImGui_ImplRoblox_SetRenderScale = ImGui_ImplRoblox_SetRenderScale,
|
||||||
|
ImGui_ImplRoblox_SetTextProxy = ImGui_ImplRoblox_SetTextProxy,
|
||||||
ImGui_ImplRoblox_Shutdown = ImGui_ImplRoblox_Shutdown,
|
ImGui_ImplRoblox_Shutdown = ImGui_ImplRoblox_Shutdown,
|
||||||
ImGui_ImplRoblox_UpdateTexture = ImGui_ImplRoblox_UpdateTexture,
|
ImGui_ImplRoblox_UpdateTexture = ImGui_ImplRoblox_UpdateTexture,
|
||||||
ImHashData = ImHashData,
|
ImHashData = ImHashData,
|
||||||
|
|||||||
@@ -1,336 +1,495 @@
|
|||||||
-- LocalScript. Build the library first: `python3 tools/bundle.py` -> build/ImGui.luau (see default.project.json)
|
|
||||||
|
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local RunService = game:GetService("RunService")
|
local RunService = game:GetService("RunService")
|
||||||
|
local AssetService = game:GetService("AssetService")
|
||||||
|
|
||||||
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
|
local G = require(ReplicatedStorage:WaitForChild("ImGui"))
|
||||||
local ExampleImageViewerData, ExampleImageViewer_DrawOptions, ExampleImageViewer_DrawCanvas = G.ExampleImageViewerData, G.ExampleImageViewer_DrawOptions, G.ExampleImageViewer_DrawCanvas
|
|
||||||
|
|
||||||
local ImGui, ImVec4, ImGuiConfigFlags, ImGui_ImplRoblox = G.ImGui, G.ImVec4, G.ImGuiConfigFlags, G.ImGui_ImplRoblox
|
local BaseEnvironment = getfenv()
|
||||||
|
setfenv(1, setmetatable({}, {
|
||||||
|
__index = function(_, Key)
|
||||||
|
local Value = G[Key]
|
||||||
|
if Value ~= nil then
|
||||||
|
return Value
|
||||||
|
end
|
||||||
|
return BaseEnvironment[Key]
|
||||||
|
end,
|
||||||
|
}))
|
||||||
|
|
||||||
local main_scale = 1.0
|
local MainScale = 1.0
|
||||||
-- Roblox UI points are multiplied by the OS display scale (e.g. 150%), which makes imgui look large.
|
local RenderScale = 1.0
|
||||||
-- Set this to your display scale to render imgui at physical-pixel resolution (same size as a desktop imgui app).
|
local RenderRate = 60
|
||||||
local render_scale = 1.0
|
local ThumbnailSize = 420
|
||||||
local clear_color = ImVec4(0.45, 0.55, 0.60, 1.00)
|
local FontSize = 13.0
|
||||||
local show_demo_window = true
|
local FontNames = { "Cousine-Regular", "DroidSans", "Karla-Regular", "ProggyTiny", "Roboto-Medium" }
|
||||||
local show_another_window = false
|
|
||||||
local f = 0.0
|
local State = {
|
||||||
local counter = 0
|
ClearColor = ImVec4(0.45, 0.55, 0.60, 1.00),
|
||||||
|
ShowDemoWindow = true,
|
||||||
|
ShowAnotherWindow = false,
|
||||||
|
Float = 0.0,
|
||||||
|
Counter = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
local Spinner = {
|
||||||
|
Show = true,
|
||||||
|
Speed = 1.0,
|
||||||
|
Angle = 0.0,
|
||||||
|
Color = ImVec4(0.80, 0.65, 0.97, 1.0),
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.CreateContext()
|
ImGui.CreateContext()
|
||||||
|
|
||||||
local io = ImGui.GetIO()
|
local IO = ImGui.GetIO()
|
||||||
io.ConfigFlags = bit32.bor(io.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard, ImGuiConfigFlags.DockingEnable)
|
IO.ConfigFlags = bit32.bor(IO.ConfigFlags, ImGuiConfigFlags.NavEnableKeyboard, ImGuiConfigFlags.DockingEnable)
|
||||||
|
|
||||||
-- Catppuccin Mocha, Mauve accent (https://catppuccin.com/palette)
|
-- catppuccin mocha mauve https://catppuccin.com/palette
|
||||||
local function StyleColorsCatppuccinMocha(style)
|
local function StyleColorsCatppuccinMocha(Style)
|
||||||
local function hex(h, a)
|
local function Hex(Value, Alpha)
|
||||||
return ImVec4(bit32.extract(h, 16, 8) / 255, bit32.extract(h, 8, 8) / 255, bit32.extract(h, 0, 8) / 255, a or 1.0)
|
return ImVec4(
|
||||||
|
bit32.extract(Value, 16, 8) / 255,
|
||||||
|
bit32.extract(Value, 8, 8) / 255,
|
||||||
|
bit32.extract(Value, 0, 8) / 255,
|
||||||
|
Alpha or 1.0
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local Crust, Mantle, Base = 0x11111b, 0x181825, 0x1e1e2e
|
||||||
|
local Surface0, Surface1, Surface2 = 0x313244, 0x45475a, 0x585b70
|
||||||
|
local Overlay0, Overlay2, Text = 0x6c7086, 0x9399b2, 0xcdd6f4
|
||||||
|
local Mauve, Lavender = 0xcba6f7, 0xb4befe
|
||||||
|
|
||||||
|
local Entries = {
|
||||||
|
{ "Text", Text },
|
||||||
|
{ "TextDisabled", Overlay0 },
|
||||||
|
|
||||||
|
{ "WindowBg", Base },
|
||||||
|
{ "ChildBg", Base, 0.0 },
|
||||||
|
{ "PopupBg", Mantle, 0.98 },
|
||||||
|
{ "Border", Surface1 },
|
||||||
|
{ "BorderShadow", Crust, 0.0 },
|
||||||
|
|
||||||
|
{ "FrameBg", Surface0 },
|
||||||
|
{ "FrameBgHovered", Surface1 },
|
||||||
|
{ "FrameBgActive", Surface2 },
|
||||||
|
|
||||||
|
{ "TitleBg", Mantle },
|
||||||
|
{ "TitleBgActive", Crust },
|
||||||
|
{ "TitleBgCollapsed", Mantle, 0.75 },
|
||||||
|
{ "MenuBarBg", Mantle },
|
||||||
|
|
||||||
|
{ "ScrollbarBg", Mantle },
|
||||||
|
{ "ScrollbarGrab", Surface1 },
|
||||||
|
{ "ScrollbarGrabHovered", Surface2 },
|
||||||
|
{ "ScrollbarGrabActive", Overlay0 },
|
||||||
|
|
||||||
|
{ "CheckMark", Mauve },
|
||||||
|
{ "SliderGrab", Mauve, 0.8 },
|
||||||
|
{ "SliderGrabActive", Mauve },
|
||||||
|
|
||||||
|
{ "Button", Surface0 },
|
||||||
|
{ "ButtonHovered", Surface1 },
|
||||||
|
{ "ButtonActive", Mauve, 0.6 },
|
||||||
|
|
||||||
|
{ "Header", Surface0 },
|
||||||
|
{ "HeaderHovered", Surface1 },
|
||||||
|
{ "HeaderActive", Mauve, 0.5 },
|
||||||
|
|
||||||
|
{ "Separator", Surface1 },
|
||||||
|
{ "SeparatorHovered", Mauve, 0.7 },
|
||||||
|
{ "SeparatorActive", Mauve },
|
||||||
|
|
||||||
|
{ "ResizeGrip", Mauve, 0.2 },
|
||||||
|
{ "ResizeGripHovered", Mauve, 0.6 },
|
||||||
|
{ "ResizeGripActive", Mauve, 0.9 },
|
||||||
|
|
||||||
|
{ "InputTextCursor", Text },
|
||||||
|
|
||||||
|
{ "Tab", Surface0 },
|
||||||
|
{ "TabHovered", Surface2 },
|
||||||
|
{ "TabSelected", Surface1 },
|
||||||
|
{ "TabSelectedOverline", Mauve },
|
||||||
|
{ "TabDimmed", Mantle },
|
||||||
|
{ "TabDimmedSelected", Surface0 },
|
||||||
|
{ "TabDimmedSelectedOverline", Overlay0 },
|
||||||
|
|
||||||
|
{ "DockingPreview", Mauve, 0.7 },
|
||||||
|
{ "DockingEmptyBg", Crust },
|
||||||
|
|
||||||
|
{ "PlotLines", Overlay2 },
|
||||||
|
{ "PlotLinesHovered", Mauve },
|
||||||
|
{ "PlotHistogram", Mauve, 0.8 },
|
||||||
|
{ "PlotHistogramHovered", Lavender },
|
||||||
|
|
||||||
|
{ "TableHeaderBg", Surface0 },
|
||||||
|
{ "TableBorderStrong", Surface1 },
|
||||||
|
{ "TableBorderLight", Surface0 },
|
||||||
|
{ "TableRowBg", Base, 0.0 },
|
||||||
|
{ "TableRowBgAlt", Surface0, 0.3 },
|
||||||
|
|
||||||
|
{ "TextLink", Mauve },
|
||||||
|
{ "TextSelectedBg", Mauve, 0.35 },
|
||||||
|
{ "TreeLines", Surface2 },
|
||||||
|
{ "DragDropTarget", Mauve, 0.9 },
|
||||||
|
|
||||||
|
{ "NavCursor", Lavender },
|
||||||
|
{ "NavWindowingHighlight", Lavender, 0.7 },
|
||||||
|
{ "NavWindowingDimBg", Crust, 0.5 },
|
||||||
|
{ "ModalWindowDimBg", Crust, 0.6 },
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, Entry in ipairs(Entries) do
|
||||||
|
local Name, Color, Alpha = Entry[1], Entry[2], Entry[3]
|
||||||
|
Style.Colors[ImGuiCol[Name]] = Hex(Color, Alpha)
|
||||||
end
|
end
|
||||||
local crust, mantle, base = 0x11111b, 0x181825, 0x1e1e2e
|
|
||||||
local surface0, surface1, surface2 = 0x313244, 0x45475a, 0x585b70
|
|
||||||
local overlay0, overlay2, text = 0x6c7086, 0x9399b2, 0xcdd6f4
|
|
||||||
local mauve, lavender = 0xcba6f7, 0xb4befe
|
|
||||||
local C, c = G.ImGuiCol, style.Colors
|
|
||||||
local function set(idx, h, a) c[idx] = hex(h, a) end
|
|
||||||
set(C.Text, text); set(C.TextDisabled, overlay0)
|
|
||||||
set(C.WindowBg, base); set(C.ChildBg, base, 0.0)
|
|
||||||
set(C.PopupBg, mantle, 0.98); set(C.Border, surface1); set(C.BorderShadow, crust, 0.0)
|
|
||||||
set(C.FrameBg, surface0); set(C.FrameBgHovered, surface1); set(C.FrameBgActive, surface2)
|
|
||||||
set(C.TitleBg, mantle); set(C.TitleBgActive, crust); set(C.TitleBgCollapsed, mantle, 0.75)
|
|
||||||
set(C.MenuBarBg, mantle)
|
|
||||||
set(C.ScrollbarBg, mantle); set(C.ScrollbarGrab, surface1)
|
|
||||||
set(C.ScrollbarGrabHovered, surface2); set(C.ScrollbarGrabActive, overlay0)
|
|
||||||
set(C.CheckMark, mauve); set(C.SliderGrab, mauve, 0.8); set(C.SliderGrabActive, mauve)
|
|
||||||
set(C.Button, surface0); set(C.ButtonHovered, surface1); set(C.ButtonActive, mauve, 0.6)
|
|
||||||
set(C.Header, surface0); set(C.HeaderHovered, surface1); set(C.HeaderActive, mauve, 0.5)
|
|
||||||
set(C.Separator, surface1); set(C.SeparatorHovered, mauve, 0.7); set(C.SeparatorActive, mauve)
|
|
||||||
set(C.ResizeGrip, mauve, 0.2); set(C.ResizeGripHovered, mauve, 0.6); set(C.ResizeGripActive, mauve, 0.9)
|
|
||||||
set(C.InputTextCursor, text)
|
|
||||||
set(C.TabHovered, surface2); set(C.Tab, surface0)
|
|
||||||
set(C.TabSelected, surface1); set(C.TabSelectedOverline, mauve)
|
|
||||||
set(C.TabDimmed, mantle); set(C.TabDimmedSelected, surface0); set(C.TabDimmedSelectedOverline, overlay0)
|
|
||||||
set(C.DockingPreview, mauve, 0.7); set(C.DockingEmptyBg, crust)
|
|
||||||
set(C.PlotLines, overlay2); set(C.PlotLinesHovered, mauve)
|
|
||||||
set(C.PlotHistogram, mauve, 0.8); set(C.PlotHistogramHovered, lavender)
|
|
||||||
set(C.TableHeaderBg, surface0); set(C.TableBorderStrong, surface1); set(C.TableBorderLight, surface0)
|
|
||||||
set(C.TableRowBg, base, 0.0); set(C.TableRowBgAlt, surface0, 0.3)
|
|
||||||
set(C.TextLink, mauve); set(C.TextSelectedBg, mauve, 0.35)
|
|
||||||
set(C.TreeLines, surface2)
|
|
||||||
set(C.DragDropTarget, mauve, 0.9)
|
|
||||||
set(C.NavCursor, lavender); set(C.NavWindowingHighlight, lavender, 0.7)
|
|
||||||
set(C.NavWindowingDimBg, crust, 0.5); set(C.ModalWindowDimBg, crust, 0.6)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local style = ImGui.GetStyle()
|
local Style = ImGui.GetStyle()
|
||||||
StyleColorsCatppuccinMocha(style)
|
StyleColorsCatppuccinMocha(Style)
|
||||||
style:ScaleAllSizes(main_scale)
|
Style:ScaleAllSizes(MainScale)
|
||||||
style.FontScaleDpi = main_scale
|
Style.FontScaleDpi = MainScale
|
||||||
|
|
||||||
-- Fonts: ProggyClean stays the default (added first); the rest show up in Style Editor > Fonts
|
IO.Fonts:AddFontDefault()
|
||||||
io.Fonts:AddFontDefault()
|
for _, Name in ipairs(FontNames) do
|
||||||
for _, name in ipairs({ "Cousine-Regular", "DroidSans", "Karla-Regular", "ProggyTiny", "Roboto-Medium" }) do
|
IO.Fonts:AddFontFromFileTTF("fonts/" .. Name .. ".ttf", FontSize)
|
||||||
io.Fonts:AddFontFromFileTTF("fonts/" .. name .. ".ttf", 13.0)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
ImGui_ImplRoblox.Init(nil, render_scale)
|
ImGui_ImplRoblox.Init(nil, RenderScale)
|
||||||
ImGui_ImplRoblox.SetRenderRate(60) -- tile redraws per second; imgui logic + your code still run every frame
|
ImGui_ImplRoblox.SetRenderRate(RenderRate)
|
||||||
-- ImGui_ImplRoblox.SetBusyRenderRate(30) -- optional: lower redraw rate while scrolling/resizing/moving windows
|
-- ImGui_ImplRoblox.SetBusyRenderRate(30) -- Optional: lower redraw rate while scrolling/resizing/moving windows
|
||||||
ImGui_ImplRoblox.SetProfiling(true) -- MicroProfiler (Ctrl+F6) labels; turn off when not profiling
|
-- ImGui_ImplRoblox.SetProfiling(true) -- MicroProfiler labels; turn off when not profiling
|
||||||
|
-- ImGui_ImplRoblox.SetTextProxy(true) -- Optional: hidden TextBox for OS typing (any layout), paste and copy/cut
|
||||||
|
|
||||||
|
local function ProfileBegin(Name)
|
||||||
|
if ImGui_ImplRoblox.IsProfiling() then
|
||||||
|
debug.profilebegin(Name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ProfileEnd()
|
||||||
|
if ImGui_ImplRoblox.IsProfiling() then
|
||||||
|
debug.profileend()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function P(name) if ImGui_ImplRoblox.IsProfiling() then debug.profilebegin(name) end end
|
|
||||||
local function PE() if ImGui_ImplRoblox.IsProfiling() then debug.profileend() end end
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------------------------------------------
|
|
||||||
-- Custom widget: ImGui.Embed(id, size, flags, init) -> Frame
|
|
||||||
-- Reserves a widget-sized area in the current window and shows a real Frame there, so any Instances (UI, a
|
|
||||||
-- ViewportFrame, ...) can live inside an imgui window. Immediate-mode friendly:
|
|
||||||
-- * one Frame per id (per window, through the ID stack, like any widget); `init(frame)` runs only when it is created
|
|
||||||
-- * call it every frame the Frame should be visible; when it isn't called (window closed/collapsed, tab or tree node
|
|
||||||
-- closed, scrolled fully out of view) the Frame is hidden, never destroyed, so your Instances keep their state
|
|
||||||
-- * size uses imgui sizing (ImVec2(-1, h) = fill width, 0 = default), style gives padding/rounding/background/border
|
|
||||||
-- * returns the Frame: edit properties, add or remove children any time
|
|
||||||
-- ImGui.DestroyEmbed(id) removes it for good (call while the same window is current, like the widget itself).
|
|
||||||
--------------------------------------------------------------------------------------------------------------------
|
|
||||||
local EmbedFlags = {
|
local EmbedFlags = {
|
||||||
None = 0,
|
None = 0,
|
||||||
NoClip = 1, -- don't clip to the window's content area (still moves/hides/stacks with the window)
|
NoClip = 1, -- Don't clip to the window's content area (still moves/hides/stacks with the window)
|
||||||
NoBackground = 2, -- no FrameBg behind the Frame
|
NoBackground = 2, -- No FrameBg behind the Frame
|
||||||
NoBorder = 4, -- no style border
|
NoBorder = 4, -- No style border
|
||||||
NoPadding = 8, -- no FramePadding inside the Frame (UIPadding)
|
NoPadding = 8, -- No FramePadding inside the Frame (UIPadding)
|
||||||
NoRounding = 16, -- no FrameRounding corners (UICorner)
|
NoRounding = 16, -- No FrameRounding corners (UICorner)
|
||||||
}
|
}
|
||||||
|
|
||||||
G.ImGuiEmbedFlags = EmbedFlags
|
G.ImGuiEmbedFlags = EmbedFlags
|
||||||
|
|
||||||
local embeds = {} -- [ImGuiID] = { Frame, Padding, Corner }
|
local Embeds = {}
|
||||||
|
|
||||||
function ImGui.Embed(id, size, flags, init)
|
local function HasFlag(Flags, Flag)
|
||||||
flags = flags or EmbedFlags.None
|
return bit32.band(Flags, Flag) ~= 0
|
||||||
local window = ImGui.GetCurrentWindow()
|
|
||||||
local gid = window:GetID(id)
|
|
||||||
local e = embeds[gid]
|
|
||||||
if not e then
|
|
||||||
local frame = Instance.new("Frame")
|
|
||||||
frame.Name = tostring(id)
|
|
||||||
frame.BackgroundTransparency = 1
|
|
||||||
frame.BorderSizePixel = 0
|
|
||||||
frame.Visible = false
|
|
||||||
e = { Frame = frame, Padding = Instance.new("UIPadding"), Corner = Instance.new("UICorner") }
|
|
||||||
e.Padding.Parent = frame
|
|
||||||
e.Corner.Parent = frame
|
|
||||||
embeds[gid] = e
|
|
||||||
if init then init(frame) end
|
|
||||||
end
|
|
||||||
if window.SkipItems then return e.Frame end
|
|
||||||
|
|
||||||
local style = ImGui.GetStyle()
|
|
||||||
local sz = ImGui.CalcItemSize(size or G.ImVec2(0, 0), ImGui.CalcItemWidth(), ImGui.GetFrameHeight() * 4)
|
|
||||||
local p = window.DC.CursorPos
|
|
||||||
local bb = G.ImRect(p.x, p.y, p.x + sz.x, p.y + sz.y)
|
|
||||||
ImGui.ItemSize(sz)
|
|
||||||
if not ImGui.ItemAdd(bb, gid) then return e.Frame end -- fully clipped: not submitted -> hidden this frame
|
|
||||||
|
|
||||||
local rounding = bit32.band(flags, EmbedFlags.NoRounding) == 0 and style.FrameRounding or 0
|
|
||||||
if bit32.band(flags, EmbedFlags.NoBackground) == 0 then
|
|
||||||
ImGui.RenderFrame(bb.Min, bb.Max, ImGui.GetColorU32(G.ImGuiCol.FrameBg), bit32.band(flags, EmbedFlags.NoBorder) == 0, rounding)
|
|
||||||
elseif bit32.band(flags, EmbedFlags.NoBorder) == 0 then
|
|
||||||
ImGui.RenderFrameBorder(bb.Min, bb.Max, rounding)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- style -> Instances (only written when changed)
|
|
||||||
local scale = ImGui_ImplRoblox.GetRenderScale()
|
|
||||||
local px, py = 0, 0
|
|
||||||
if bit32.band(flags, EmbedFlags.NoPadding) == 0 then px, py = style.FramePadding.x / scale, style.FramePadding.y / scale end
|
|
||||||
if e.PX ~= px or e.PY ~= py then
|
|
||||||
e.PX, e.PY = px, py
|
|
||||||
e.Padding.PaddingLeft, e.Padding.PaddingRight = UDim.new(0, px), UDim.new(0, px)
|
|
||||||
e.Padding.PaddingTop, e.Padding.PaddingBottom = UDim.new(0, py), UDim.new(0, py)
|
|
||||||
end
|
|
||||||
if e.R ~= rounding then e.R = rounding; e.Corner.CornerRadius = UDim.new(0, rounding / scale) end
|
|
||||||
|
|
||||||
ImGui_ImplRoblox.SetOverlay(e.Frame, bb.Min.x, bb.Min.y, sz.x, sz.y, bit32.band(flags, EmbedFlags.NoClip) == 0)
|
|
||||||
return e.Frame
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function ImGui.DestroyEmbed(id)
|
local function CreateEmbed(Id)
|
||||||
local gid = ImGui.GetCurrentWindow():GetID(id)
|
local Frame = Instance.new("Frame")
|
||||||
local e = embeds[gid]
|
Frame.Name = tostring(Id)
|
||||||
if e then e.Frame:Destroy(); embeds[gid] = nil end
|
Frame.BackgroundTransparency = 1
|
||||||
|
Frame.BorderSizePixel = 0
|
||||||
|
Frame.Visible = false
|
||||||
|
|
||||||
|
local Padding = Instance.new("UIPadding")
|
||||||
|
Padding.Parent = Frame
|
||||||
|
|
||||||
|
local Corner = Instance.new("UICorner")
|
||||||
|
Corner.Parent = Frame
|
||||||
|
|
||||||
|
return { Frame = Frame, Padding = Padding, Corner = Corner }
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Example: Decal Viewer. Loads a decal/image asset's pixels into an imgui texture and shows it in the demo's
|
function ImGui.Embed(Id, Size, Flags, Init)
|
||||||
-- image viewer (zoom with the wheel, drag to pan, toggleable pixel grid).
|
Flags = Flags or EmbedFlags.None
|
||||||
local AssetService = game:GetService("AssetService")
|
|
||||||
local MarketplaceService = game:GetService("MarketplaceService")
|
|
||||||
local decal = { input = ImGui.NewTextBuffer("", 32), status = "Enter a decal or image id", tex = nil, w = 0, h = 0, loading = false,
|
|
||||||
viewer = ExampleImageViewerData(), show = true }
|
|
||||||
|
|
||||||
-- A Decal asset wraps an Image asset with a different id; the image is almost always uploaded just before it
|
local Window = ImGui.GetCurrentWindow()
|
||||||
-- (same creator, id slightly lower). ponytail: heuristic scan of 10 ids, InsertService:LoadAsset when owned is exact.
|
local GuiId = Window:GetID(Id)
|
||||||
local function ResolveImageId(id)
|
|
||||||
local ok, info = pcall(MarketplaceService.GetProductInfo, MarketplaceService, id)
|
local Embed = Embeds[GuiId]
|
||||||
if not ok then return nil, "GetProductInfo failed: " .. tostring(info) end
|
if not Embed then
|
||||||
if info.AssetTypeId == 1 then return id end -- already an Image
|
Embed = CreateEmbed(Id)
|
||||||
if info.AssetTypeId ~= 13 then return nil, "asset is not a decal or image" end
|
Embeds[GuiId] = Embed
|
||||||
for cand = id - 1, id - 10, -1 do
|
|
||||||
local ok2, ci = pcall(MarketplaceService.GetProductInfo, MarketplaceService, cand)
|
if Init then
|
||||||
if ok2 and ci.AssetTypeId == 1 and ci.Creator and info.Creator and ci.Creator.Id == info.Creator.Id then return cand end
|
Init(Embed.Frame)
|
||||||
end
|
end
|
||||||
return nil, "couldn't find the decal's image id (try the image id directly)"
|
end
|
||||||
|
|
||||||
|
if Window.SkipItems then
|
||||||
|
return Embed.Frame
|
||||||
|
end
|
||||||
|
|
||||||
|
local EmbedStyle = ImGui.GetStyle()
|
||||||
|
local ItemSize = ImGui.CalcItemSize(
|
||||||
|
Size or ImVec2(0, 0),
|
||||||
|
ImGui.CalcItemWidth(),
|
||||||
|
ImGui.GetFrameHeight() * 4
|
||||||
|
)
|
||||||
|
local CursorPos = Window.DC.CursorPos
|
||||||
|
local BoundingBox = ImRect(CursorPos.x, CursorPos.y, CursorPos.x + ItemSize.x, CursorPos.y + ItemSize.y)
|
||||||
|
|
||||||
|
ImGui.ItemSize(ItemSize)
|
||||||
|
if not ImGui.ItemAdd(BoundingBox, GuiId) then
|
||||||
|
return Embed.Frame
|
||||||
|
end
|
||||||
|
|
||||||
|
local Rounding = HasFlag(Flags, EmbedFlags.NoRounding) and 0 or EmbedStyle.FrameRounding
|
||||||
|
local ShowBorder = not HasFlag(Flags, EmbedFlags.NoBorder)
|
||||||
|
|
||||||
|
if not HasFlag(Flags, EmbedFlags.NoBackground) then
|
||||||
|
ImGui.RenderFrame(
|
||||||
|
BoundingBox.Min,
|
||||||
|
BoundingBox.Max,
|
||||||
|
ImGui.GetColorU32(ImGuiCol.FrameBg),
|
||||||
|
ShowBorder,
|
||||||
|
Rounding
|
||||||
|
)
|
||||||
|
elseif ShowBorder then
|
||||||
|
ImGui.RenderFrameBorder(BoundingBox.Min, BoundingBox.Max, Rounding)
|
||||||
|
end
|
||||||
|
|
||||||
|
local Scale = ImGui_ImplRoblox.GetRenderScale()
|
||||||
|
local PaddingX, PaddingY = 0, 0
|
||||||
|
|
||||||
|
if not HasFlag(Flags, EmbedFlags.NoPadding) then
|
||||||
|
PaddingX = EmbedStyle.FramePadding.x / Scale
|
||||||
|
PaddingY = EmbedStyle.FramePadding.y / Scale
|
||||||
|
end
|
||||||
|
|
||||||
|
if Embed.PaddingX ~= PaddingX or Embed.PaddingY ~= PaddingY then
|
||||||
|
Embed.PaddingX, Embed.PaddingY = PaddingX, PaddingY
|
||||||
|
|
||||||
|
local Padding = Embed.Padding
|
||||||
|
Padding.PaddingLeft = UDim.new(0, PaddingX)
|
||||||
|
Padding.PaddingRight = UDim.new(0, PaddingX)
|
||||||
|
Padding.PaddingTop = UDim.new(0, PaddingY)
|
||||||
|
Padding.PaddingBottom = UDim.new(0, PaddingY)
|
||||||
|
end
|
||||||
|
|
||||||
|
if Embed.Rounding ~= Rounding then
|
||||||
|
Embed.Rounding = Rounding
|
||||||
|
Embed.Corner.CornerRadius = UDim.new(0, Rounding / Scale)
|
||||||
|
end
|
||||||
|
|
||||||
|
ImGui_ImplRoblox.SetOverlay(
|
||||||
|
Embed.Frame,
|
||||||
|
BoundingBox.Min.x,
|
||||||
|
BoundingBox.Min.y,
|
||||||
|
ItemSize.x,
|
||||||
|
ItemSize.y,
|
||||||
|
not HasFlag(Flags, EmbedFlags.NoClip)
|
||||||
|
)
|
||||||
|
|
||||||
|
return Embed.Frame
|
||||||
end
|
end
|
||||||
|
|
||||||
local function LoadDecal(id)
|
function ImGui.DestroyEmbed(Id)
|
||||||
decal.loading = true
|
local GuiId = ImGui.GetCurrentWindow():GetID(Id)
|
||||||
decal.status = "Loading " .. id .. "..."
|
local Embed = Embeds[GuiId]
|
||||||
|
|
||||||
|
if Embed then
|
||||||
|
Embed.Frame:Destroy()
|
||||||
|
Embeds[GuiId] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local Decal = {
|
||||||
|
Show = true,
|
||||||
|
Input = ImGui.NewTextBuffer("", 32),
|
||||||
|
Status = "Enter a decal or image id",
|
||||||
|
Loading = false,
|
||||||
|
Texture = nil,
|
||||||
|
Width = 0,
|
||||||
|
Height = 0,
|
||||||
|
Viewer = ExampleImageViewerData(),
|
||||||
|
}
|
||||||
|
|
||||||
|
local function LoadDecal(Id)
|
||||||
|
Decal.Loading = true
|
||||||
|
Decal.Status = "Loading " .. Id .. "..."
|
||||||
|
|
||||||
task.spawn(function()
|
task.spawn(function()
|
||||||
local image_id, err = ResolveImageId(id)
|
local Uri = string.format("rbxthumb://type=Asset&id=%d&w=%d&h=%d", Id, ThumbnailSize, ThumbnailSize)
|
||||||
decal.thumb = nil
|
local Success, Result = pcall(AssetService.CreateEditableImageAsync, AssetService, Content.fromUri(Uri))
|
||||||
if image_id then
|
|
||||||
-- 1) the image itself (needs permission), 2) its 420x420 thumbnail (may be allowed when 1 isn't)
|
if Success then
|
||||||
local ok, img = pcall(AssetService.CreateEditableImageAsync, AssetService, Content.fromUri("rbxassetid://" .. image_id))
|
local Size = Result.Size
|
||||||
if not ok then
|
local Pixels = Result:ReadPixelsBuffer(Vector2.zero, Size)
|
||||||
local ok2, img2 = pcall(AssetService.CreateEditableImageAsync, AssetService, Content.fromUri("rbxthumb://type=Asset&id=" .. id .. "&w=420&h=420"))
|
Result:Destroy()
|
||||||
if ok2 then ok, img = true, img2 end
|
|
||||||
|
if Decal.Texture then
|
||||||
|
ImGui_ImplRoblox.DestroyTexture(Decal.Texture)
|
||||||
end
|
end
|
||||||
if ok then
|
|
||||||
local size = img.Size
|
Decal.Texture = ImGui_ImplRoblox.CreateTexture(Pixels, Size.X, Size.Y)
|
||||||
local pixels = img:ReadPixelsBuffer(Vector2.zero, size)
|
Decal.Width, Decal.Height = Size.X, Size.Y
|
||||||
img:Destroy()
|
Decal.Viewer.ViewReset = true
|
||||||
if decal.tex then ImGui_ImplRoblox.DestroyTexture(decal.tex) end
|
Decal.Status = string.format("Thumbnail %d: %dx%d", Id, Size.X, Size.Y)
|
||||||
decal.thumb = nil
|
|
||||||
decal.tex, decal.w, decal.h = ImGui_ImplRoblox.CreateTexture(pixels, size.X, size.Y), size.X, size.Y
|
|
||||||
decal.viewer.ViewReset = true
|
|
||||||
decal.status = string.format("Image %d: %dx%d", image_id, size.X, size.Y)
|
|
||||||
else
|
else
|
||||||
-- 3) no pixel access: Roblox still displays any asset, so show its thumbnail in an ImageLabel (no grid)
|
Decal.Status = "Failed to load thumbnail: " .. tostring(Result)
|
||||||
decal.thumb = "rbxthumb://type=Asset&id=" .. id .. "&w=420&h=420"
|
|
||||||
decal.status = "No pixel access (asset not owned by this experience's creator): display only, no zoom/grid"
|
|
||||||
end
|
end
|
||||||
else
|
|
||||||
decal.thumb = "rbxthumb://type=Asset&id=" .. id .. "&w=420&h=420"
|
Decal.Loading = false
|
||||||
decal.status = (err or "") .. " - showing thumbnail"
|
|
||||||
end
|
|
||||||
decal.loading = false
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ShowDecalViewer()
|
local function ShowDecalViewer()
|
||||||
if not decal.show then return end
|
if not Decal.Show then
|
||||||
decal.show = ImGui.Begin("Decal Viewer", decal.show)
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Decal.Show = ImGui.Begin("Decal Viewer", Decal.Show)
|
||||||
|
|
||||||
ImGui.SetNextItemWidth(ImGui.GetFontSize() * 12)
|
ImGui.SetNextItemWidth(ImGui.GetFontSize() * 12)
|
||||||
local enter = ImGui.InputTextWithHint("##id", "decal id", decal.input, 32, G.ImGuiInputTextFlags.CharsDecimal + G.ImGuiInputTextFlags.EnterReturnsTrue)
|
local EnterPressed = ImGui.InputTextWithHint(
|
||||||
|
"##id",
|
||||||
|
"decal id",
|
||||||
|
Decal.Input,
|
||||||
|
32,
|
||||||
|
ImGuiInputTextFlags.CharsDecimal + ImGuiInputTextFlags.EnterReturnsTrue
|
||||||
|
)
|
||||||
|
|
||||||
ImGui.SameLine()
|
ImGui.SameLine()
|
||||||
ImGui.BeginDisabled(decal.loading)
|
ImGui.BeginDisabled(Decal.Loading)
|
||||||
if (ImGui.Button("Load") or enter) and not decal.loading then
|
if (ImGui.Button("Load") or EnterPressed) and not Decal.Loading then
|
||||||
local id = tonumber(ImGui.TextBufferToString(decal.input))
|
local Id = tonumber(ImGui.TextBufferToString(Decal.Input))
|
||||||
if id then LoadDecal(id) else decal.status = "Not a number" end
|
if Id and Id == math.floor(Id) then
|
||||||
|
LoadDecal(Id)
|
||||||
|
else
|
||||||
|
Decal.Status = "Not a valid id"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
ImGui.EndDisabled()
|
ImGui.EndDisabled()
|
||||||
ImGui.SameLine(); ImGui.TextDisabled(decal.status)
|
|
||||||
if decal.thumb then
|
ImGui.SameLine()
|
||||||
local avail = ImGui.GetContentRegionAvail()
|
ImGui.TextDisabled(Decal.Status)
|
||||||
local frame = ImGui.Embed("decal_thumb", G.ImVec2(-1, math.max(avail.y, 50)), G.ImGuiEmbedFlags.None, function(f)
|
|
||||||
local img = Instance.new("ImageLabel")
|
if Decal.Texture then
|
||||||
img.Name = "Image"
|
ExampleImageViewer_DrawOptions(Decal.Viewer)
|
||||||
img.Size = UDim2.fromScale(1, 1)
|
|
||||||
img.BackgroundTransparency = 1
|
local Available = ImGui.GetContentRegionAvail()
|
||||||
img.ScaleType = Enum.ScaleType.Fit
|
ExampleImageViewer_DrawCanvas(
|
||||||
img.Parent = f
|
Decal.Viewer,
|
||||||
end)
|
ImVec2(math.max(Available.x, 50), math.max(Available.y, 50)),
|
||||||
if frame.Image.Image ~= decal.thumb then frame.Image.Image = decal.thumb end
|
Decal.Texture,
|
||||||
elseif decal.tex then
|
Decal.Width,
|
||||||
ExampleImageViewer_DrawOptions(decal.viewer)
|
Decal.Height
|
||||||
local avail = ImGui.GetContentRegionAvail()
|
)
|
||||||
ExampleImageViewer_DrawCanvas(decal.viewer, G.ImVec2(math.max(avail.x, 50), math.max(avail.y, 50)), decal.tex, decal.w, decal.h)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
ImGui.End()
|
ImGui.End()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Example: a ViewportFrame with a spinning part, edited live from imgui widgets
|
local function ShowHelloWindow()
|
||||||
local spin_speed = 1.0
|
|
||||||
local part_color = ImVec4(0.80, 0.65, 0.97, 1.0)
|
|
||||||
local spin_angle = 0.0
|
|
||||||
local show_viewport_window = true
|
|
||||||
|
|
||||||
RunService.Stepped:Connect(function()
|
|
||||||
P("ImGui frame")
|
|
||||||
ImGui_ImplRoblox.NewFrame()
|
|
||||||
P("ImGui.NewFrame"); ImGui.NewFrame(); PE()
|
|
||||||
P("ImGui UI")
|
|
||||||
|
|
||||||
-- Dockspace covering the whole screen; the centre stays transparent so the game shows through
|
|
||||||
ImGui.DockSpaceOverViewport(0, nil, G.ImGuiDockNodeFlags.PassthruCentralNode)
|
|
||||||
|
|
||||||
if show_demo_window then
|
|
||||||
ImGui.ShowDemoWindow()
|
|
||||||
end
|
|
||||||
|
|
||||||
do
|
|
||||||
ImGui.Begin("Hello, world!")
|
ImGui.Begin("Hello, world!")
|
||||||
|
|
||||||
ImGui.Text("This is some useful text.")
|
ImGui.Text("This is some useful text.")
|
||||||
local _
|
|
||||||
_, show_demo_window = ImGui.Checkbox("Demo Window", show_demo_window)
|
|
||||||
_, show_another_window = ImGui.Checkbox("Another Window", show_another_window)
|
|
||||||
|
|
||||||
f = ImGui.SliderFloat("float", f, 0.0, 1.0)
|
local _
|
||||||
ImGui.ColorEdit3("clear color", clear_color)
|
_, State.ShowDemoWindow = ImGui.Checkbox("Demo Window", State.ShowDemoWindow)
|
||||||
|
_, State.ShowAnotherWindow = ImGui.Checkbox("Another Window", State.ShowAnotherWindow)
|
||||||
|
|
||||||
|
State.Float = ImGui.SliderFloat("float", State.Float, 0.0, 1.0)
|
||||||
|
ImGui.ColorEdit3("clear color", State.ClearColor)
|
||||||
|
|
||||||
if ImGui.Button("Button") then
|
if ImGui.Button("Button") then
|
||||||
counter = counter + 1
|
State.Counter += 1
|
||||||
end
|
end
|
||||||
ImGui.SameLine()
|
ImGui.SameLine()
|
||||||
ImGui.Text("counter = %d", counter)
|
ImGui.Text("counter = %d", State.Counter)
|
||||||
|
|
||||||
ImGui.Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0 / io.Framerate, io.Framerate)
|
ImGui.Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0 / IO.Framerate, IO.Framerate)
|
||||||
|
|
||||||
ImGui.End()
|
ImGui.End()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ShowAnotherWindow()
|
||||||
|
if not State.ShowAnotherWindow then
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if show_another_window then
|
State.ShowAnotherWindow = ImGui.Begin("Another Window", State.ShowAnotherWindow)
|
||||||
show_another_window = ImGui.Begin("Another Window", show_another_window)
|
|
||||||
|
|
||||||
ImGui.Text("Hello from another window!")
|
ImGui.Text("Hello from another window!")
|
||||||
if ImGui.Button("Close Me") then
|
if ImGui.Button("Close Me") then
|
||||||
show_another_window = false
|
State.ShowAnotherWindow = false
|
||||||
end
|
end
|
||||||
|
|
||||||
ImGui.End()
|
ImGui.End()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CreateSpinnerViewport(Container)
|
||||||
|
local Viewport = Instance.new("ViewportFrame")
|
||||||
|
Viewport.Size = UDim2.fromScale(1, 1)
|
||||||
|
Viewport.BackgroundTransparency = 1
|
||||||
|
Viewport.Parent = Container
|
||||||
|
|
||||||
|
local Camera = Instance.new("Camera")
|
||||||
|
Camera.CFrame = CFrame.lookAt(Vector3.new(0, 2, 6), Vector3.zero)
|
||||||
|
Camera.Parent = Viewport
|
||||||
|
Viewport.CurrentCamera = Camera
|
||||||
|
|
||||||
|
local Part = Instance.new("Part")
|
||||||
|
Part.Name = "Spinner"
|
||||||
|
Part.Size = Vector3.new(2, 2, 2)
|
||||||
|
Part.Parent = Viewport
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ShowViewportWindow()
|
||||||
|
if not Spinner.Show then
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
PE()
|
Spinner.Show = ImGui.Begin("Viewport", Spinner.Show)
|
||||||
|
|
||||||
|
Spinner.Speed = ImGui.SliderFloat("speed", Spinner.Speed, 0.0, 5.0)
|
||||||
|
ImGui.ColorEdit3("color", Spinner.Color)
|
||||||
|
|
||||||
|
local Frame = ImGui.Embed("viewport", ImVec2(-1, 220), EmbedFlags.None, CreateSpinnerViewport)
|
||||||
|
|
||||||
|
Spinner.Angle += Spinner.Speed * IO.DeltaTime
|
||||||
|
|
||||||
|
local Part = Frame.ViewportFrame.Spinner
|
||||||
|
Part.CFrame = CFrame.Angles(0.4, Spinner.Angle, 0)
|
||||||
|
Part.Color = Color3.new(Spinner.Color.x, Spinner.Color.y, Spinner.Color.z)
|
||||||
|
|
||||||
|
ImGui.End()
|
||||||
|
end
|
||||||
|
|
||||||
|
RunService.Stepped:Connect(function()
|
||||||
|
ProfileBegin("ImGui frame")
|
||||||
|
ImGui_ImplRoblox.NewFrame()
|
||||||
|
|
||||||
|
ProfileBegin("ImGui.NewFrame")
|
||||||
|
ImGui.NewFrame()
|
||||||
|
ProfileEnd()
|
||||||
|
|
||||||
|
ProfileBegin("ImGui UI")
|
||||||
|
ImGui.DockSpaceOverViewport(0, nil, ImGuiDockNodeFlags.PassthruCentralNode)
|
||||||
|
|
||||||
|
if State.ShowDemoWindow then
|
||||||
|
-- For reference: https://github.com/4DBug/imgui-luau/blob/master/imgui_demo.lua
|
||||||
|
ImGui.ShowDemoWindow()
|
||||||
|
end
|
||||||
|
|
||||||
|
ShowHelloWindow()
|
||||||
|
ShowAnotherWindow()
|
||||||
ShowDecalViewer()
|
ShowDecalViewer()
|
||||||
|
ShowViewportWindow()
|
||||||
|
ProfileEnd()
|
||||||
|
|
||||||
if show_viewport_window then
|
ProfileBegin("ImGui.Render")
|
||||||
show_viewport_window = ImGui.Begin("Viewport", show_viewport_window)
|
ImGui.EndFrame()
|
||||||
spin_speed = ImGui.SliderFloat("speed", spin_speed, 0.0, 5.0)
|
ImGui.Render()
|
||||||
ImGui.ColorEdit3("color", part_color)
|
ProfileEnd()
|
||||||
local frame = ImGui.Embed("viewport", G.ImVec2(-1, 220), G.ImGuiEmbedFlags.None, function(f)
|
|
||||||
-- runs once
|
|
||||||
local vpf = Instance.new("ViewportFrame")
|
|
||||||
vpf.Size = UDim2.fromScale(1, 1)
|
|
||||||
vpf.BackgroundTransparency = 1
|
|
||||||
vpf.Parent = f
|
|
||||||
local cam = Instance.new("Camera")
|
|
||||||
cam.CFrame = CFrame.lookAt(Vector3.new(0, 2, 6), Vector3.new(0, 0, 0))
|
|
||||||
cam.Parent = vpf
|
|
||||||
vpf.CurrentCamera = cam
|
|
||||||
local part = Instance.new("Part")
|
|
||||||
part.Name = "Spinner"
|
|
||||||
part.Size = Vector3.new(2, 2, 2)
|
|
||||||
part.Parent = vpf
|
|
||||||
end)
|
|
||||||
-- every frame: edit what was created once
|
|
||||||
spin_angle += spin_speed * io.DeltaTime
|
|
||||||
local part = frame.ViewportFrame.Spinner
|
|
||||||
part.CFrame = CFrame.Angles(0.4, spin_angle, 0)
|
|
||||||
part.Color = Color3.new(part_color.x, part_color.y, part_color.z)
|
|
||||||
ImGui.Text("Frames created: 1 (same Frame every frame)")
|
|
||||||
ImGui.End()
|
|
||||||
end
|
|
||||||
|
|
||||||
P("ImGui.Render"); ImGui.EndFrame(); ImGui.Render(); PE()
|
|
||||||
ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
ImGui_ImplRoblox.RenderDrawData(ImGui.GetDrawData())
|
||||||
PE()
|
ProfileEnd()
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
local G = require(game:GetService("ReplicatedStorage"):WaitForChild("ImGui"))
|
||||||
local ImGui, ImVec2 = G.ImGui, G.ImVec2
|
local ImGui, ImVec2 = G.ImGui, G.ImVec2
|
||||||
local UIS = game:GetService("UserInputService")
|
local UIS = game:GetService("UserInputService")
|
||||||
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init()
|
ImGui.CreateContext(); G.ImGui_ImplRoblox.Init(); G.ImGui_ImplRoblox.SetTextProxy(true)
|
||||||
local io = ImGui.GetIO()
|
local io = ImGui.GetIO()
|
||||||
local buf = ImGui.NewTextBuffer("", 64)
|
local buf = ImGui.NewTextBuffer("", 64)
|
||||||
local proxy
|
local proxy
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bug/imgui-luau"
|
name = "4dbug/imgui-luau"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
registry = "https://github.com/UpliftGames/wally-index"
|
registry = "https://github.com/UpliftGames/wally-index"
|
||||||
realm = "shared"
|
realm = "shared"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
Reference in New Issue
Block a user