173 lines
7.5 KiB
Markdown
173 lines
7.5 KiB
Markdown
# Regolith
|
|
|
|
Regolith is a retained mode GUI framework written in C99. It is built for
|
|
native desktop applications and for game user interfaces, and it compiles to
|
|
Linux, Windows, macOS, and WebAssembly. It uses no webview and no web backend.
|
|
|
|
## Goals
|
|
|
|
- A clean C API that other languages such as Odin and Zig can bind directly.
|
|
- A retained document tree that loads from disk.
|
|
- Lua as the document language and as a full runtime API, so an application
|
|
can be written entirely in Lua after initialization.
|
|
- Easy initialization, with a small number of calls to get a window and a
|
|
document on screen.
|
|
- Independence from any single platform or renderer.
|
|
|
|
## Inspiration
|
|
|
|
RmlUi is the main inspiration. Its document model, styling separation, data
|
|
binding, animations, and event bubbling are the parts I want. Regolith aims
|
|
for those ideas with a C API and a simpler event model. HTML and CSS parity
|
|
is not a goal.
|
|
|
|
## Architecture
|
|
|
|
Regolith is split into layers. Each layer depends only on the ones below it.
|
|
|
|
- **core**: node tree, style resolution, events, data bindings, animation,
|
|
and text. Pure C99. No Lua types and no Clay types appear in its public API.
|
|
- **layout adapter**: the only code that touches Clay. It walks the retained
|
|
tree each frame, emits Clay calls, and reads back computed layout.
|
|
- **lua module**: the document loader and the Lua API. It is a thin layer
|
|
over the C API.
|
|
- **backend interface**: a struct of function pointers defined in the main
|
|
header. It creates and updates textures, draws batches of colored and
|
|
textured quads, and sets the scissor rect.
|
|
- **default backend**: SDL3 and OpenGL.
|
|
- **null backend**: records the quad list instead of drawing, used for
|
|
headless tests.
|
|
|
|
The C API is the source of truth. Every capability exists in C first, and the
|
|
Lua binding exposes it without adding behavior of its own.
|
|
|
|
## Dependencies
|
|
|
|
All dependencies are vendored in `third_party/` and built by Regolith's own
|
|
CMake, so native and WASM builds use identical versions.
|
|
|
|
- Lua 5.4 for documents and scripting
|
|
- Clay for layout and render commands
|
|
- stb_truetype and stb_rect_pack for fonts and atlas packing
|
|
|
|
SDL3 is the only external dependency, and only the default backend uses it.
|
|
|
|
## Layout
|
|
|
|
Clay provides layout. Regolith exposes a Clay-flavored property vocabulary
|
|
(`sizing`, `padding`, `gap`, `align`, `direction`). Every node is a box with a
|
|
direction. Node types use Godot-style names such as `Panel`, `Label`,
|
|
`Button`, `HBox`, `VBox`, and `ScrollContainer`, where `HBox` and `VBox` are
|
|
shorthand for a box with a fixed direction.
|
|
|
|
## Text
|
|
|
|
The core owns all text handling. It loads fonts, caches glyph metrics, packs
|
|
glyphs into a single-channel atlas, and measures strings for Clay. The
|
|
backend receives the atlas as a texture and draws glyphs as textured quads.
|
|
Rasterization happens at logical size multiplied by display scale, so the font
|
|
API carries a scale factor from the start. The text module sits behind a small
|
|
interface so a shaper can replace it later. Shaping, bidirectional text, and
|
|
color emoji are out of scope for the first versions.
|
|
|
|
## Rendering
|
|
|
|
Rectangles, borders, and glyphs all reduce to quads. A backend only needs to
|
|
manage textures, draw quad batches, and apply a scissor rect. Rendering
|
|
backends are replaceable, and the SDL3 and OpenGL backend is the default.
|
|
|
|
## Documents
|
|
|
|
Documents are Lua files. Node constructors such as `VBox`, `Label`, and
|
|
`Button` return plain descriptor tables containing a type, properties, and
|
|
children. A separate step realizes the descriptor tree into live nodes
|
|
through the C API. Descriptors are pure data, which keeps them easy to print
|
|
and debug and allows hot reload to reconcile a new tree against the live one
|
|
by `id` or key.
|
|
|
|
Each document runs in its own `_ENV` that provides the node constructors and
|
|
helpers. A document is free to contain arbitrary Lua logic, including
|
|
frontend-only behavior and calls to the host application. The same `_ENV`
|
|
mechanism provides sandboxing, which is an option at initialization. A
|
|
sandboxed state omits `io`, `os`, `debug`, and arbitrary `load`, and installs
|
|
an instruction-count hook to stop runaway scripts.
|
|
|
|
Both authoring surfaces exist. The call style
|
|
(`VBox { Label { text = "Hi" } }`) is the primary form. The builder style
|
|
(create a node, set properties, append it to a parent) is the same C API
|
|
exposed to Lua and is used for dynamic content.
|
|
|
|
## Styling
|
|
|
|
Styles are Lua tables with a small selector engine. Supported selectors are
|
|
type, class, id, the state pseudo-classes `:hover`, `:focus`, `:active`, and
|
|
`:disabled`, and a child combinator. The cascade orders by specificity and
|
|
then declaration order. There is no `!important`. Components are Lua
|
|
functions that return descriptor tables, and shared appearance comes from
|
|
classes. Style resolution runs only when classes, state, or tree structure
|
|
change.
|
|
|
|
## Data binding
|
|
|
|
A binding is a callback with userdata attached to a node property. The data
|
|
model is a keyed store with version counters, and a binding re-evaluates only
|
|
when a key it depends on has a newer version than its last evaluation. In Lua
|
|
the model is a proxy table whose `__newindex` bumps the version. Dependencies
|
|
are declared as explicit keys. Automatic dependency tracking is a later
|
|
improvement.
|
|
|
|
## Animation
|
|
|
|
Property changes can transition over time, similar to CSS transitions. The
|
|
retained tree stores the previous value, and the interpolated value is fed
|
|
into layout each frame. Keyframes and coroutine-driven sequences come later.
|
|
|
|
## Events
|
|
|
|
Events bubble and have no capture phase. The fixed set is click, hover enter,
|
|
hover leave, focus, blur, change, key, and custom named events. A handler is
|
|
either a Lua function or a string name. A string name pushes an event into a
|
|
polled queue, so C, Zig, and Odin users consume events without callbacks
|
|
across the FFI boundary.
|
|
|
|
## API conventions
|
|
|
|
- One public header, `regolith.h`, with the implementation in ordinary `.c`
|
|
files compiled into a static or shared library.
|
|
- `regolith_lua.h` and `regolith_sdl3.h` are separate optional headers, so
|
|
`lua_State` and SDL types never appear in the main header.
|
|
- Fixed-width integer types, plain structs, no bitfields, no variadics, and
|
|
no `static inline` in the public header.
|
|
- Every public function is marked `RG_API` and prefixed with `rg_`.
|
|
- Enums are an `int` typedef with named constants.
|
|
- Nodes are referenced by opaque handles made of an index and a generation,
|
|
never by raw pointers.
|
|
- The user can supply an allocator, and internal allocation uses arenas.
|
|
|
|
## Build and environment
|
|
|
|
The build system is CMake with Ninja. Development happens on NixOS using
|
|
direnv and flakes, and the devShell provides CMake, Ninja, pkg-config, clang,
|
|
clang-tools, SDL3, Emscripten, and lua-language-server. Emscripten's cache
|
|
directory is set to `.emscripten-cache` inside the project. WASM builds go
|
|
through `emcmake` into `build-wasm/`. Sources live in a flat `src/` with no
|
|
generated files, so Zig and Odin users can compile them with their own
|
|
toolchain.
|
|
|
|
## Milestone 1
|
|
|
|
A `main.lua` builds a `VBox` of `Label` and `Button` nodes. A stylesheet with
|
|
classes and a `:hover` state styles them. Layout runs through Clay and draws
|
|
through the SDL3 and OpenGL backend with stb_truetype text. A button click
|
|
fires both a Lua handler and a named event in the polled queue. One
|
|
transition eases a button background on hover. A data-bound label shows a
|
|
counter that the button increments.
|
|
|
|
Text inputs and scrolling follow in milestone 2, along with focus handling.
|
|
|
|
## Open questions
|
|
|
|
- Context and ownership of the `rg_context`.
|
|
- Who owns the main loop.
|
|
- The input event path and gamepad navigation.
|