From 71ef8470f42546654b53c8c3762664576e80024a Mon Sep 17 00:00:00 2001 From: chris bell Date: Mon, 2 Nov 2026 12:25:49 -0600 Subject: [PATCH] flake and design doc --- docs/dd.md | 182 ++++++++++++++++++++++++++++++++++++++++++++++++----- flake.lock | 27 ++++++++ flake.nix | 39 ++++++++++++ 3 files changed, 233 insertions(+), 15 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/docs/dd.md b/docs/dd.md index 04243b6..16ed276 100644 --- a/docs/dd.md +++ b/docs/dd.md @@ -1,20 +1,172 @@ -# Retained Mode GUI project +# Regolith -## Hard requirements: -- Must be written in C -- Must be RETAINED MODE with a document structure that loads from disk -- 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) +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 -## Medium requirements: -- Use an existing project to calculate layouts and render commands +- 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 -## Soft requirements: -- Bring your own rendering/platform backend (otherwise, just use OpenGL and SDL3) +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. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..706cc29 --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..e1f32b8 --- /dev/null +++ b/flake.nix @@ -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" + ''; + }; + }); + }; +}