flake and design doc

This commit is contained in:
chrisbell committed 2026-11-02 12:25:49 -06:00
1 parent 8da6770ad6
commit 71ef8470f4
3 files changed
+233 -15

No files matched your search

+167 -15
View File
@@ -1,20 +1,172 @@
# Retained Mode GUI project # Regolith
## Hard requirements: Regolith is a retained mode GUI framework written in C99. It is built for
- Must be written in C native desktop applications and for game user interfaces, and it compiles to
- Must be RETAINED MODE with a document structure that loads from disk Linux, Windows, macOS, and WebAssembly. It uses no webview and no web backend.
- Must be versitile and independent so it can be used for Games, Desktop App GUI, etc.
- Must be easy to initalize
- Must have a friendly document language (My first thought is Lua as the documenting language)
- Must have full Lua API for realtime changes and modability
- Must be NATIVE and not use a webview or web backend at all
- Used for NATIVE Desktop GUI applications as well as GUI for games
- Must compile to native Linux, Windows, Mac, etc, as well as Web (WASM)
## Goals
## Medium requirements: - A clean C API that other languages such as Odin and Zig can bind directly.
- Use an existing project to calculate layouts and render commands - 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
## Soft requirements: RmlUi is the main inspiration. Its document model, styling separation, data
- Bring your own rendering/platform backend (otherwise, just use OpenGL and SDL3) 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.
Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1793540411,
"narHash": "sha256-fiPJ88izTVucjeo2EQ3jk4aWmLg03I1D4vxnX2QjEgE=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0bb2eedc20e4739f29e8045034f2828ad7054f8f",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+39
View File
@@ -0,0 +1,39 @@
{
description = "Regolith, a retained mode GUI library in C";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs }:
let
systems = [ "x86_64-linux" "aarch64-linux" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system:
f nixpkgs.legacyPackages.${system});
in {
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell.override { stdenv = pkgs.clangStdenv; } {
nativeBuildInputs = with pkgs; [
cmake
ninja
pkg-config
clang-tools
emscripten
gdb
valgrind
lua5_4
lua-language-server
];
buildInputs = with pkgs; [
sdl3
libGL
];
shellHook = ''
export EM_CACHE="$PWD/.emscripten-cache"
'';
};
});
};
}