xbintsc 0.3.46 → 0.3.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +95 -0
- package/README.md +25 -0
- package/README.zh-CN.md +23 -0
- package/dist/src/cli/hints.d.ts +54 -0
- package/dist/src/cli/hints.js +165 -0
- package/dist/src/cli/hints.js.map +1 -0
- package/dist/src/cli/main.js +73 -9
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +26 -0
- package/dist/src/codegen/generator/tables.js +64 -12
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/diagnostics/source-text.d.ts +22 -0
- package/dist/src/diagnostics/source-text.js +76 -0
- package/dist/src/diagnostics/source-text.js.map +1 -0
- package/dist/src/driver/bundler/graph.js +2 -1
- package/dist/src/driver/bundler/graph.js.map +1 -1
- package/dist/src/driver/compiler.js +3 -2
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/lexer/scanner/strings.js +16 -3
- package/dist/src/lexer/scanner/strings.js.map +1 -1
- package/dist/tests/cli/hints.test.d.ts +9 -0
- package/dist/tests/cli/hints.test.js +143 -0
- package/dist/tests/cli/hints.test.js.map +1 -0
- package/dist/tests/cli/main.test.js +6 -4
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +17 -2
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/helpers.js +3 -2
- package/dist/tests/helpers.js.map +1 -1
- package/dist/tests/lexer/strings.test.js +14 -2
- package/dist/tests/lexer/strings.test.js.map +1 -1
- package/doc/DESIGN.md +117 -0
- package/doc/ai/README.md +63 -0
- package/doc/ai/build-recipe.md +137 -0
- package/doc/ai/cli.md +142 -0
- package/doc/ai/contributing.md +196 -0
- package/doc/ai/extensions.md +148 -0
- package/doc/ai/language-support.md +152 -0
- package/doc/ai/troubleshooting.md +163 -0
- package/doc/ai/zh-CN/README.md +56 -0
- package/doc/ai/zh-CN/build-recipe.md +132 -0
- package/doc/ai/zh-CN/cli.md +127 -0
- package/doc/ai/zh-CN/contributing.md +173 -0
- package/doc/ai/zh-CN/extensions.md +139 -0
- package/doc/ai/zh-CN/language-support.md +147 -0
- package/doc/ai/zh-CN/troubleshooting.md +150 -0
- package/doc/gui-scripts.md +350 -0
- package/doc/gui.md +646 -0
- package/doc/icon.md +265 -0
- package/doc/implemented.md +373 -0
- package/doc/node-implemented.md +588 -0
- package/doc/node-unimplemented.md +167 -0
- package/doc/post/announce.md +43 -0
- package/doc/requirements.md +145 -0
- package/doc/unimplemented.md +286 -0
- package/doc/xbintsc.config.schema.json +67 -0
- package/doc/zh-CN/DESIGN.md +104 -0
- package/doc/zh-CN/gui-scripts.md +329 -0
- package/doc/zh-CN/gui.md +588 -0
- package/doc/zh-CN/icon.md +241 -0
- package/doc/zh-CN/implemented.md +365 -0
- package/doc/zh-CN/node-implemented.md +533 -0
- package/doc/zh-CN/node-unimplemented.md +141 -0
- package/doc/zh-CN/plan-require-node-modules.md +284 -0
- package/doc/zh-CN/post/announce.md +47 -0
- package/doc/zh-CN/requirements.md +134 -0
- package/doc/zh-CN/unimplemented.md +247 -0
- package/llms.txt +45 -0
- package/package.json +4 -1
- package/runtime/ext_gui/gui.cpp +3 -1
- package/runtime/ext_gui/renderer.cpp +13 -11
- package/runtime/ext_gui/renderer_image.cpp +12 -8
- package/runtime/ext_gui/renderer_shaders.h +131 -4
- package/runtime/ext_gui/renderer_shaders_data.h +1809 -0
- package/runtime/ext_gui/renderer_text.cpp +12 -8
- package/runtime/ext_gui/shaders.hlsl +98 -0
- package/runtime/ext_gui/spirv/fill.frag +19 -0
- package/runtime/ext_gui/spirv/fill.vert +42 -0
- package/runtime/ext_gui/spirv/image.frag +16 -0
- package/runtime/ext_gui/spirv/quad.vert +30 -0
- package/runtime/ext_gui/spirv/text.frag +16 -0
- package/scripts/build-gui-shaders.mjs +204 -0
- package/scripts/build-gui.ts +35 -0
- package/scripts/check-file-length.ts +5 -1
- package/src/cli/hints.ts +194 -0
- package/src/cli/main.ts +82 -9
- package/src/codegen/generator/tables.ts +60 -14
- package/src/diagnostics/source-text.ts +78 -0
- package/src/driver/bundler/graph.ts +2 -1
- package/src/driver/compiler.ts +3 -2
- package/src/lexer/scanner/strings.ts +16 -3
package/doc/gui.md
ADDED
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
# xbintsc GUI extension (self-hosted HTML/CSS renderer)
|
|
2
|
+
|
|
3
|
+
Status: **M10** — features (M1–M10) are complete: HTML parsing, CSS selector
|
|
4
|
+
matching, the cascade, computed styles and layout (block, inline and Flexbox) are
|
|
5
|
+
in place, and the engine *paints*: it builds a display list of rectangles, images
|
|
6
|
+
and shaped text runs and renders them through SDL_GPU. Input events are hit
|
|
7
|
+
tested and delivered to native TS handlers, `:hover`/`:focus` are matched
|
|
8
|
+
dynamically, `<img>` is sized from its intrinsic dimensions and drawn from a
|
|
9
|
+
texture, and CSS transitions animate paint properties. **M8** adds an interactive
|
|
10
|
+
DOM: element handles with stable identity, mutation (`appendChild`, `textContent`,
|
|
11
|
+
`classList`, `style`, …) and element-level events with capture/bubbling.
|
|
12
|
+
**M9** compiles `<script>` bodies ahead of time (inline and `<script src>`) — see
|
|
13
|
+
`doc/gui-scripts.md`. **M10** adds `requestAnimationFrame` plus a few DOM helpers.
|
|
14
|
+
This document records the locked decisions, the architecture, the milestone plan
|
|
15
|
+
and the current progress of a cross-platform GUI extension that renders an
|
|
16
|
+
HTML/CSS UI with its own GPU-accelerated engine.
|
|
17
|
+
|
|
18
|
+
## Goals
|
|
19
|
+
|
|
20
|
+
1. **Cross-platform GUI** from TypeScript compiled by xbintsc to a native
|
|
21
|
+
binary: macOS, Linux, Windows.
|
|
22
|
+
2. **HTML5/CSS rendering** with a **self-written** engine (parser, cascade,
|
|
23
|
+
layout, paint), not a system WebView or an embedded browser.
|
|
24
|
+
3. **GPU accelerated** — hardware rasterization/compositing is a hard
|
|
25
|
+
requirement, not an optimization.
|
|
26
|
+
4. **Multiple windows.**
|
|
27
|
+
5. The engine must not compromise xbintsc's nature as a binary compiler: the
|
|
28
|
+
core compiler, lexer, parser, binder and code generator must stay
|
|
29
|
+
platform-agnostic and never grow GUI branches.
|
|
30
|
+
|
|
31
|
+
## Non-goals (for now)
|
|
32
|
+
|
|
33
|
+
- Executing **runtime** page `<script>` (scripts fetched over the network or
|
|
34
|
+
created dynamically). **Compile-time** scripts are AOT-compiled by xbintsc and
|
|
35
|
+
do run — see `doc/gui-scripts.md`. Logic may also live in native TS called
|
|
36
|
+
back through `xt_call_with_this`.
|
|
37
|
+
- A JS engine (QuickJS/V8/...). Explicitly out of scope; there is no runtime
|
|
38
|
+
interpreter/JIT, so `<script>` bodies are compiled ahead of time.
|
|
39
|
+
- Full web compatibility / a browser. We implement a practical HTML/CSS subset.
|
|
40
|
+
|
|
41
|
+
## Locked decisions
|
|
42
|
+
|
|
43
|
+
| # | Decision |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| 1 | **No page JS engine.** Behaviour is native TS, called back through `xt_call_with_this`. `<script>` bodies are **AOT-compiled** by xbintsc itself (no interpreter) — see `doc/gui-scripts.md`. |
|
|
46
|
+
| 2 | Third-party **low-level** libraries are allowed (GPU backend, text shaping, image decode). HTML/CSS parsing + layout + paint scheduling are self-written. |
|
|
47
|
+
| 3 | **GPU acceleration is mandatory.** |
|
|
48
|
+
| 4 | The engine is **not self-hosted** (it is C/C++, not TS), but must not affect the compiler's platform-agnostic design. Delivered as a per-platform prebuilt archive linked via `nativeObjects`. |
|
|
49
|
+
| 5 | The **event loop is generic**: the runtime exposes a generic main-loop hook and a poll primitive; nothing GUI-specific enters `runtime/`. |
|
|
50
|
+
| 6 | **Multiple windows** are supported by the core object model. |
|
|
51
|
+
| 7 | Third-party low-level libs are **statically linked into `gui.a`** so releases stay self-contained; only OS frameworks are added at link time. |
|
|
52
|
+
|
|
53
|
+
## Architecture
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
TypeScript (compiled by xbintsc to native code)
|
|
57
|
+
│ import { createWindow, run } from "gui"
|
|
58
|
+
▼
|
|
59
|
+
gui extension bindings (src/extensions/gui)
|
|
60
|
+
│ xt_gui_* symbols (uniform (argc, argv) ABI)
|
|
61
|
+
▼
|
|
62
|
+
gui.a ── the self-hosted engine (C/C++)
|
|
63
|
+
┌───────────────┬──────────────────┬───────────────────┐
|
|
64
|
+
▼ ▼ ▼ ▼
|
|
65
|
+
HTML parser CSS cascade + Layout GPU compositor
|
|
66
|
+
(subset) selector match (block/inline/flex) (SDL_GPU: Metal/
|
|
67
|
+
Vulkan/D3D12)
|
|
68
|
+
│
|
|
69
|
+
▼
|
|
70
|
+
windowing + input (SDL3) ── multiple windows
|
|
71
|
+
text (HarfBuzz + FreeType) ── glyph atlas / SDF
|
|
72
|
+
images (stb_image)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The engine never talks to the compiler. The compiler only sees an `Extension`
|
|
76
|
+
with `nativeObjects()`, `linkerFlags()` and `modules()`.
|
|
77
|
+
|
|
78
|
+
## Stack (confirmed)
|
|
79
|
+
|
|
80
|
+
| Concern | Choice | Why |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| Window + input + multi-window | **SDL3** | cross-platform windows, HiDPI, IME, clipboard, DnD, close/hide events |
|
|
83
|
+
| GPU | **SDL_GPU** (SDL3) | one render path over Metal / Vulkan / D3D12; avoids three backends |
|
|
84
|
+
| Text shaping | **HarfBuzz** | correct complex-script shaping |
|
|
85
|
+
| Glyph raster | **FreeType** | glyph outlines → GPU atlas / SDF |
|
|
86
|
+
| Images | **stb_image** (vendored header) | single header to start |
|
|
87
|
+
|
|
88
|
+
Pinned versions: SDL3 `release-3.2.10`, FreeType `2.13.3`, HarfBuzz `10.1.0`
|
|
89
|
+
(overridable with `SDL3_TAG` / `FREETYPE_VERSION` / `HARFBUZZ_VERSION`).
|
|
90
|
+
|
|
91
|
+
Alternative if SDL3 is rejected: **GLFW + OpenGL 3.3** (simpler, but OpenGL is
|
|
92
|
+
deprecated on macOS and gives no modern GPU abstraction). **wgpu-native** if the
|
|
93
|
+
engine were Rust.
|
|
94
|
+
|
|
95
|
+
> These vendored libraries are linked **statically into `gui.a`** so a released
|
|
96
|
+
> xbintsc stays "download and run"; only OS frameworks are added by
|
|
97
|
+
> `linkerFlags()`.
|
|
98
|
+
|
|
99
|
+
## TS-facing API
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { createWindow, run, quit } from "gui";
|
|
103
|
+
|
|
104
|
+
const win = createWindow({ title: "Demo", width: 900, height: 600 });
|
|
105
|
+
win.setBackground("#14161c");
|
|
106
|
+
win.loadHTML(INDEX_HTML); // parses HTML/CSS and computes styles
|
|
107
|
+
win.on("ready", () => console.log("first frame presented"));
|
|
108
|
+
win.on("close", () => console.log("window closed"));
|
|
109
|
+
|
|
110
|
+
run(); // drives the main loop until all windows close
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Methods implemented on a window handle: `setTitle` / `setSize` / `loadHTML` /
|
|
114
|
+
`getHTML` / `setBackground` / `close` / `isOpen` / `on` / `off`. Events emitted:
|
|
115
|
+
`ready` (after the first presented frame), `load`, `close`, and the input events
|
|
116
|
+
`mousemove`, `mousedown`, `mouseup`, `click`, `wheel`, `keydown`, `keyup`.
|
|
117
|
+
|
|
118
|
+
Input handlers receive a single payload object (lifecycle handlers receive none):
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
win.on("click", (e) => console.log(e.x, e.y, e.button, e.target));
|
|
122
|
+
win.on("wheel", (e) => console.log(e.deltaX, e.deltaY));
|
|
123
|
+
win.on("keydown", (e) => console.log(e.key, e.code, e.ctrl, e.shift, e.alt, e.meta));
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
| Field | Events | Meaning |
|
|
127
|
+
| --- | --- | --- |
|
|
128
|
+
| `x`, `y` / `clientX`, `clientY` | pointer, wheel | viewport-relative logical pixels |
|
|
129
|
+
| `button` | pointer | `0` left, `1` middle, `2` right (`-1` for move) |
|
|
130
|
+
| `clicks` | pointer | click count reported by the OS |
|
|
131
|
+
| `deltaX`, `deltaY` | wheel | scroll amount (`deltaY` positive = down) |
|
|
132
|
+
| `key`, `code` | keyboard | key name and physical scancode name |
|
|
133
|
+
| `repeat`, `ctrl`, `shift`, `alt`, `meta` | keyboard | modifiers |
|
|
134
|
+
| `target` | pointer, wheel | deepest element under the point as a CSS-like descriptor (`div#main.card`), or absent |
|
|
135
|
+
|
|
136
|
+
### Implemented HTML/CSS subset (M3a)
|
|
137
|
+
|
|
138
|
+
**HTML parser** (`runtime/ext_gui/dom.{h,cpp}`): tags/attributes/text,
|
|
139
|
+
comments and doctype skipped, entity decoding (named + numeric/hex), void
|
|
140
|
+
elements, raw-text elements (`<style>`/`<script>`), and the common implicit
|
|
141
|
+
close rules (`li`, `dt`/`dd`, `option`, `p`, headings, table cells/rows).
|
|
142
|
+
|
|
143
|
+
**CSS parser** (`runtime/ext_gui/css.{h,cpp}`): comments and at-rules skipped
|
|
144
|
+
(for now), rules with multiple selectors, and declarations. Selectors: type,
|
|
145
|
+
`.class`, `#id`, attribute (`=`, `~=`, `|=`, `^=`, `$=`, `*=`), the four
|
|
146
|
+
combinators (descendant, child, adjacent and general sibling) and the
|
|
147
|
+
pseudo-classes `:first-child`, `:last-child`, `:only-child`, `:empty`,
|
|
148
|
+
`:root`, `:not(...)`, `:nth-child(an+b)`, `:disabled`, `:checked`, plus the
|
|
149
|
+
stateful `:hover` and `:focus` (see *Implemented input*). Values:
|
|
150
|
+
lengths (`px`, `%`, `em`, `rem`, `vw`, `vh`, `pt`, `pc`, `in`, `cm`, `mm`, `q`),
|
|
151
|
+
colors (hex, `rgb()`/`rgba()`, a named subset), numbers, keywords and shorthands
|
|
152
|
+
(`margin`/`padding`/`border`/`flex`).
|
|
153
|
+
|
|
154
|
+
**Cascade & computed style** (`runtime/ext_gui/style.{h,cpp}`): a small built-in
|
|
155
|
+
UA stylesheet, author rules sorted by `(!important, specificity, source order)`,
|
|
156
|
+
then inline `style=""` (highest specificity, but non-`!important` inline loses to
|
|
157
|
+
`!important`), plus CSS inheritance of the text properties. Relative lengths are
|
|
158
|
+
kept unresolved until layout, except `font-size` (resolved against the *parent*
|
|
159
|
+
font size) and `line-height`.
|
|
160
|
+
|
|
161
|
+
### Diagnostics
|
|
162
|
+
|
|
163
|
+
So the HTML/CSS/paint pipeline is testable without a GPU, a window handle
|
|
164
|
+
exposes read-only hooks:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
win.computedStyle(selector, property) // e.g. ("#main", "width") -> "60%"
|
|
168
|
+
win.queryCount(selector) // number of matching elements
|
|
169
|
+
win.getBoundingClientRect(selector) // { x, y, width, height } (border box)
|
|
170
|
+
win.documentTree() // serialized DOM (debugging)
|
|
171
|
+
win.layoutTree() // serialized layout boxes (debugging)
|
|
172
|
+
win.paintCount() // number of shapes in the display list
|
|
173
|
+
win.paintList() // serialized display list (debugging)
|
|
174
|
+
win.measureText(text, fontSize?, family?) // shaped advance width in pixels
|
|
175
|
+
win.fontMetrics(fontSize?, family?) // { ascent, descent, lineHeight, ready }
|
|
176
|
+
win.hitTest(x, y) // deepest element descriptor, or ""
|
|
177
|
+
win.sendEvent(type, options?) // synthesise input (testing)
|
|
178
|
+
win.advance(ms) // step the CSS transition clock (testing)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
They are used by `tests/e2e/gui-*.test.ts` to assert parsing, selector matching,
|
|
182
|
+
specificity, inheritance, `!important` and layout geometry. They will stay useful
|
|
183
|
+
afterwards for debugging.
|
|
184
|
+
|
|
185
|
+
### Interactive DOM (M8)
|
|
186
|
+
|
|
187
|
+
`win.document` returns the document handle; element handles have stable identity
|
|
188
|
+
and read/write properties, attributes, traversal and geometry:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
const doc = win.document;
|
|
192
|
+
const box = doc.querySelector("#box");
|
|
193
|
+
const inner = doc.querySelector("#inner");
|
|
194
|
+
inner.textContent = "hi"; // write
|
|
195
|
+
inner.classList.add("hot");
|
|
196
|
+
inner.style.setProperty("color", "#0f0");
|
|
197
|
+
inner.setAttribute("data-role", "lead");
|
|
198
|
+
console.log(inner.id, inner.tagName, doc.querySelector("#box") === box);
|
|
199
|
+
|
|
200
|
+
const created = doc.createElement("div");
|
|
201
|
+
created.textContent = "added";
|
|
202
|
+
box.appendChild(created);
|
|
203
|
+
box.removeChild(created);
|
|
204
|
+
|
|
205
|
+
inner.addEventListener("click", (e) => console.log(e.target.id, e.currentTarget.id));
|
|
206
|
+
inner.click(); // synthesise a click at the element
|
|
207
|
+
console.log(box.offsetWidth, box.offsetHeight); // rounded border box
|
|
208
|
+
console.log(box.contains(inner)); // descendant test
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Mutations mark the document dirty; the engine restyles + relayouts lazily (before
|
|
212
|
+
the next read or frame). `inner = …`/`innerHTML = …` do **not** run scripts.
|
|
213
|
+
Element events support capture and bubble phases, `stopPropagation`, `once`, and
|
|
214
|
+
bubble up to `document`/`window`. The legacy `win.on(type, fn)` payload keeps its
|
|
215
|
+
**string** `e.target` (`div#id.class`); the element `Event.target` is a handle
|
|
216
|
+
whose descriptor matches the same string.
|
|
217
|
+
|
|
218
|
+
### AOT scripts (M9)
|
|
219
|
+
|
|
220
|
+
Import an `.html` file that contains inline `<script lang="ts">` bodies; the
|
|
221
|
+
loader compiles each body into a native function and `win.loadHTML(page)` runs
|
|
222
|
+
them once the document is parsed (no JavaScript engine, no runtime `eval`):
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { createWindow, run } from "gui";
|
|
226
|
+
import page from "./page.html";
|
|
227
|
+
|
|
228
|
+
const win = createWindow({ title: "counter", width: 320, height: 240 });
|
|
229
|
+
win.loadHTML(page);
|
|
230
|
+
run();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```html
|
|
234
|
+
<button id="b">0</button>
|
|
235
|
+
<script lang="ts">
|
|
236
|
+
const b = document.getElementById("b");
|
|
237
|
+
let n = 0;
|
|
238
|
+
b.addEventListener("click", () => { b.textContent = String(++n); });
|
|
239
|
+
</script>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The two parameters (`window`, `document`) are ordinary function arguments, so
|
|
243
|
+
script locals need no global-object machinery. External scripts work too:
|
|
244
|
+
`<script src="./counter.ts">` is read at compile time, its imports are hoisted
|
|
245
|
+
(rewritten to resolve from the HTML file) and its body is wrapped the same way —
|
|
246
|
+
so a script module can `import` helpers and still see `document`. Scripts are
|
|
247
|
+
**compile-time assets**: HTML created at runtime (`innerHTML`, fetched over the
|
|
248
|
+
network) never executes, and `src` URLs (`https://…`, `data:…`) are ignored.
|
|
249
|
+
Everything runs in document order after parsing (effectively deferred). See
|
|
250
|
+
`doc/gui-scripts.md` for the full design.
|
|
251
|
+
|
|
252
|
+
### Animation frames (M10)
|
|
253
|
+
|
|
254
|
+
`requestAnimationFrame` runs a callback once on the next frame; the callback
|
|
255
|
+
receives the frame timestamp (ms) and may mutate the DOM, which is restyled and
|
|
256
|
+
repainted in the same frame. Re-queue from inside the callback to animate:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
const win = createWindow({ title: "anim", width: 320, height: 240 });
|
|
260
|
+
let n = 0;
|
|
261
|
+
const tick = (t: number) => {
|
|
262
|
+
win.document.getElementById("label").textContent = String(n++);
|
|
263
|
+
if (n < 120) win.requestAnimationFrame(tick); // id returned; cancelAnimationFrame(id) drops it
|
|
264
|
+
};
|
|
265
|
+
win.on("ready", () => win.requestAnimationFrame(tick));
|
|
266
|
+
win.loadHTML("<div id='label'>0</div>");
|
|
267
|
+
run();
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Implemented layout (M3)
|
|
271
|
+
|
|
272
|
+
`runtime/ext_gui/layout.{h,cpp}` turns the styled DOM into a `LayoutBox` tree
|
|
273
|
+
with absolute (viewport-relative) geometry:
|
|
274
|
+
|
|
275
|
+
- **Block flow** — block-level children stack vertically (no margin collapsing
|
|
276
|
+
yet); `display: none` generates no box; `width: auto` fills the containing
|
|
277
|
+
block, `height: auto` wraps the content. The box model (margin/padding/border)
|
|
278
|
+
is resolved, including percentages against the containing block width.
|
|
279
|
+
- **Inline flow** — consecutive inline-level children form an anonymous inline
|
|
280
|
+
formatting context with greedy, word-based line breaking, `text-align` and
|
|
281
|
+
`line-height`. Inline elements get the union of their descendants' geometry;
|
|
282
|
+
`display: inline-block` is laid out atomically with a shrink-to-fit width.
|
|
283
|
+
Each text fragment remembers the run it covers, so paint can shape it. Text is
|
|
284
|
+
measured with the HarfBuzz/FreeType stack (see *Implemented text*).
|
|
285
|
+
- **Flexbox** — single-line `row`/`column` (and the `-reverse` variants) with
|
|
286
|
+
`gap`, `flex-basis`/`flex-grow`/`flex-shrink`, `justify-content` and
|
|
287
|
+
`align-items` (including `stretch` when the cross size is definite).
|
|
288
|
+
|
|
289
|
+
Not yet implemented: margin collapsing, multi-line flex wrapping, `position`
|
|
290
|
+
offsets (`relative`/`absolute`/`fixed`), `overflow` clipping and floats.
|
|
291
|
+
|
|
292
|
+
**Document** (`runtime/ext_gui/document.{h,cpp}`): owns the DOM tree, gathers
|
|
293
|
+
`<style>` text into one stylesheet, computes styles and layout for a viewport and
|
|
294
|
+
offers `querySelector`/`querySelectorAll`/`styleOf`/`boxOf`.
|
|
295
|
+
|
|
296
|
+
### Implemented paint (M4a/M4b)
|
|
297
|
+
|
|
298
|
+
`runtime/ext_gui/paint.{h,cpp}` walks the layout tree in painter's order and
|
|
299
|
+
emits a backend-agnostic `DisplayList` with two parallel lists: **rectangles**
|
|
300
|
+
(backgrounds and four solid border edges, with `border-radius`) and **text runs**
|
|
301
|
+
(each carrying its text, colour and resolved `FontSpec`). Keeping them separate
|
|
302
|
+
lets the renderer draw all rectangles, then all text on top, with one draw call
|
|
303
|
+
per list. `DisplayList::dump()` feeds `paintList()`/`paintCount()`.
|
|
304
|
+
|
|
305
|
+
`runtime/ext_gui/renderer.{h,cpp}` turns that list into two batched vertex
|
|
306
|
+
buffers per window (one for shapes, one for glyph quads) drawn through two
|
|
307
|
+
SDL_GPU graphics pipelines:
|
|
308
|
+
|
|
309
|
+
- The shared pipelines are created lazily from the device's supported shader
|
|
310
|
+
format. Each of SDL_GPU's three backends consumes a different binary and none
|
|
311
|
+
of them compiles GLSL/HLSL at runtime, so the engine ships all three and
|
|
312
|
+
`selectShader` (`renderer_shaders.h`) picks the one the device accepts:
|
|
313
|
+
**MSL** for Metal (compiled by SDL from the embedded source),
|
|
314
|
+
**SPIR-V** for Vulkan (`runtime/ext_gui/spirv/*.{vert,frag}`, compiled by
|
|
315
|
+
`glslc`), and **DXIL** for Direct3D 12 (`shaders.hlsl`, compiled by `dxc`).
|
|
316
|
+
`scripts/build-gui-shaders.mjs` regenerates the embedded blobs and the result
|
|
317
|
+
is committed, so nothing extra is needed to build the engine.
|
|
318
|
+
The descriptor bindings differ per format — SPIR-V follows what
|
|
319
|
+
`SDL_gpu_vulkan.c` builds (uniforms `set=1, binding=0`, samplers
|
|
320
|
+
`set=2, binding=0`, textures `set=2, binding=1`) — and glslc names every entry
|
|
321
|
+
point `main` where MSL/DXIL keep the descriptive names.
|
|
322
|
+
Note that SDL 3.2.10's Direct3D 12 backend cannot create a graphics pipeline
|
|
323
|
+
whose shaders declare a uniform buffer (it fails with `E_INVALIDARG`); the
|
|
324
|
+
engine passes the viewport that way, so the DXIL path is built but not usable
|
|
325
|
+
until that is resolved upstream.
|
|
326
|
+
- A **rounded-rectangle distance field** in the shape fragment shader gives
|
|
327
|
+
antialiased fills; the vertex carries `position`, `local`, `half extents`,
|
|
328
|
+
`radius` and colour, and a viewport-size push constant does the projection.
|
|
329
|
+
Alpha blending is enabled.
|
|
330
|
+
- The text pipeline samples a **single shared grayscale glyph atlas**
|
|
331
|
+
(`R8_UNORM`, 2048², shelf-packed, LINEAR filtering) and draws each glyph as a
|
|
332
|
+
textured quad (`position`, `uv`, colour), modulating alpha by the coverage.
|
|
333
|
+
- Geometry is uploaded only when the document or viewport changes
|
|
334
|
+
(`geometry.dirty`), so steady-state frames are bind-and-draw.
|
|
335
|
+
|
|
336
|
+
The window background (`setBackground`) is the render-pass clear colour.
|
|
337
|
+
|
|
338
|
+
Still to do in M4: gradients.
|
|
339
|
+
|
|
340
|
+
### Implemented input (M5)
|
|
341
|
+
|
|
342
|
+
`LayoutTree::hitTest` returns the deepest box containing a point (probing later
|
|
343
|
+
siblings first so the topmost element wins), and `xt_dom_describe` turns the
|
|
344
|
+
element into the `div#id.class` descriptor carried by event payloads.
|
|
345
|
+
|
|
346
|
+
- SDL pointer/wheel/key events are routed to the owning window, hit tested, and
|
|
347
|
+
delivered to the handlers registered with `on`. `mousedown` also moves focus.
|
|
348
|
+
- `:hover` matches the hovered element **and its ancestors** (so hovering a child
|
|
349
|
+
lights up its parents); `:focus` matches the focused element. When either
|
|
350
|
+
changes, `XtDocument::setHover`/`setFocus` recompute styles and layout and mark
|
|
351
|
+
the window's geometry dirty, so the change is painted on the next frame.
|
|
352
|
+
- `win.hitTest(x, y)` and `win.sendEvent(type, options)` expose hit testing and
|
|
353
|
+
synthetic input so the whole path is testable headlessly (the e2e suite drives
|
|
354
|
+
clicks, wheels and keys without a real mouse).
|
|
355
|
+
|
|
356
|
+
Still to do for full input: text selection, drag, IME and clipboard.
|
|
357
|
+
|
|
358
|
+
### Implemented images (M6a)
|
|
359
|
+
|
|
360
|
+
`runtime/ext_gui/image.{h,cpp}` wraps the vendored **stb_image** header
|
|
361
|
+
decoding PNG/JPEG/BMP/GIF/TGA to RGBA8, with a decode cache and a header-only
|
|
362
|
+
size cache (`stbi_info`). Paths accept a `file://` prefix and percent-encoding.
|
|
363
|
+
|
|
364
|
+
- `<img>` is a replaced element (`display: inline-block`): layout gives it the
|
|
365
|
+
CSS size when set, otherwise the intrinsic pixel size, and preserves the
|
|
366
|
+
aspect ratio when only one axis is constrained. The intrinsic size is read
|
|
367
|
+
from the file header (no full decode) while building the box tree.
|
|
368
|
+
- Paint emits one `PaintImage` per `<img>`; the renderer decodes/uploads each
|
|
369
|
+
unique `src` to an RGBA texture (cached by path) and draws textured quads,
|
|
370
|
+
batching consecutive quads that share a texture.
|
|
371
|
+
|
|
372
|
+
Still to do for images: CSS `background-image: url(...)`, `data:` URIs,
|
|
373
|
+
`object-fit` and 9-slice borders.
|
|
374
|
+
|
|
375
|
+
### Implemented transitions (M6b)
|
|
376
|
+
|
|
377
|
+
`XtDocument` runs CSS transitions between the *target* computed styles (the
|
|
378
|
+
cascade for the current `:hover`/`:focus` state) and the *displayed* styles used
|
|
379
|
+
for layout and paint. When a state change alters a transitioned property, a
|
|
380
|
+
running transition is recorded and re-applied every frame until it finishes;
|
|
381
|
+
the engine advances the clock with real frame deltas (`XtDocument::advance`),
|
|
382
|
+
and `win.advance(ms)` lets tests step it deterministically.
|
|
383
|
+
|
|
384
|
+
- Supported properties: `background-color`, `color`, `border-color`,
|
|
385
|
+
`border-radius`; `transition: all` covers them. Structural/layout-affecting
|
|
386
|
+
properties are not animated yet (that would relayout every frame).
|
|
387
|
+
- Both the `transition` shorthand and the `transition-property` / `-duration` /
|
|
388
|
+
`-delay` / `-timing-function` longhands are parsed; time values accept `s` and
|
|
389
|
+
`ms`; timing functions are `linear`, `ease`, `ease-in`, `ease-out` and
|
|
390
|
+
`ease-in-out` (`ease` is a smoothstep approximation).
|
|
391
|
+
- Retargeting mid-flight starts a new transition from the current interpolated
|
|
392
|
+
value, so reversing a hover animates smoothly from wherever it was.
|
|
393
|
+
- `computedStyle()` reports the displayed (interpolated) value, so transitions
|
|
394
|
+
are directly observable in tests.
|
|
395
|
+
|
|
396
|
+
Still to do for animation: `@keyframes` animations and `cubic-bezier(...)`.
|
|
397
|
+
|
|
398
|
+
### Implemented text stack (M4b)
|
|
399
|
+
|
|
400
|
+
`runtime/ext_gui/text.{h,cpp}` wraps **HarfBuzz** (shaping) and **FreeType**
|
|
401
|
+
(metrics + eventual rasterisation). Both are built statically and linked into
|
|
402
|
+
`gui.a` by `scripts/build-gui.ts`.
|
|
403
|
+
|
|
404
|
+
- Fonts are resolved from well-known system paths (Helvetica/Arial on macOS,
|
|
405
|
+
DejaVu/Liberation on Linux, Segoe UI/Arial on Windows), overridable with
|
|
406
|
+
`XT_GUI_FONT` (and `XT_GUI_FONT_MONO`), and cached per `(family class, size)`.
|
|
407
|
+
Only regular upright faces are used for now; weight/italic selection is a
|
|
408
|
+
later refinement.
|
|
409
|
+
- `xt_text_measure_width` shapes the run with HarfBuzz (so kerning and
|
|
410
|
+
ligatures are honoured), `xt_text_metrics` returns FreeType's ascent /
|
|
411
|
+
descent / normal line height. When no font file can be found the module falls
|
|
412
|
+
back to a deterministic per-byte approximation, so layout still works.
|
|
413
|
+
- `xt_text_shape_run` returns positioned glyphs and `xt_text_rasterize` renders
|
|
414
|
+
an 8-bit bitmap; the renderer packs those into the atlas. On HiDPI displays
|
|
415
|
+
glyphs are rasterised at `font_size * SDL_GetWindowPixelDensity` while quads
|
|
416
|
+
are positioned in logical pixels, so text stays crisp.
|
|
417
|
+
- Layout uses these real metrics for text widths, line breaking and
|
|
418
|
+
`line-height: normal`; `measureText`/`fontMetrics` expose them to tests.
|
|
419
|
+
|
|
420
|
+
Multiple windows fall out of the object model: `createWindow` returns a native
|
|
421
|
+
object handle; each handle owns its own `SDL_Window`/GPU surface and its own DOM
|
|
422
|
+
tree. `run()` drives one shared main loop that ticks every window and exits when
|
|
423
|
+
the last one closes.
|
|
424
|
+
|
|
425
|
+
## Event loop integration
|
|
426
|
+
|
|
427
|
+
The runtime change (already landed):
|
|
428
|
+
|
|
429
|
+
- `int xt_loop_poll(int timeout_ms)` — one reactor iteration; `0` polls without
|
|
430
|
+
blocking, `< 0` blocks.
|
|
431
|
+
- `void xt_loop_set_main(xt_main_loop_fn fn)` — a host may take over the main
|
|
432
|
+
loop. `xt_run_event_loop()` delegates to it; the generated `main` is unchanged.
|
|
433
|
+
- `xt_loop_set_main(NULL)` restores the default `select(2)` loop.
|
|
434
|
+
|
|
435
|
+
The GUI engine can either register its own loop through `xt_loop_set_main`, or
|
|
436
|
+
expose an explicit `run()`; **M2 uses the explicit `run()`** so the window is a
|
|
437
|
+
plain native call the TypeScript program controls. Each tick it:
|
|
438
|
+
|
|
439
|
+
1. pumps SDL window/input events for every window,
|
|
440
|
+
2. builds/uploads the display list when it changed and renders every open
|
|
441
|
+
window (clear pass + shape geometry + text geometry),
|
|
442
|
+
3. calls `xt_loop_poll(0)` and `xt_drain_microtasks()` so sockets/timers and
|
|
443
|
+
`await` continuations keep making progress,
|
|
444
|
+
4. repeats until all windows close or `quit()` is called.
|
|
445
|
+
|
|
446
|
+
The `xt_loop_set_main` hook remains available for hosts that want to own the
|
|
447
|
+
loop themselves.
|
|
448
|
+
|
|
449
|
+
This keeps network I/O, timers and `await` working inside a GUI program.
|
|
450
|
+
|
|
451
|
+
## Native ↔ TS bridge
|
|
452
|
+
|
|
453
|
+
- **TS → engine**: direct `xt_gui_*` calls / window methods.
|
|
454
|
+
- **Engine → TS**: `xt_call_with_this(fn, thisValue, argc, argv)` with function
|
|
455
|
+
values captured from TS (e.g. event handlers registered with `win.on(...)`).
|
|
456
|
+
- **Future page→native RPC**: not needed while there is no page JS; native TS is
|
|
457
|
+
the controller. If a declarative layer is added later, it will use the same
|
|
458
|
+
`xt_call_*` entry points.
|
|
459
|
+
|
|
460
|
+
## Packaging & build
|
|
461
|
+
|
|
462
|
+
- Sources live in `runtime/ext_gui/` (C/C++) plus vendored libs under `vendor/`
|
|
463
|
+
(gitignored; fetched on demand).
|
|
464
|
+
- `npm run gui` (`scripts/build-gui.ts`) fetches a pinned SDL3 (`SDL3_TAG`,
|
|
465
|
+
default `release-3.2.10`), builds a static SDL3, fetches and builds static
|
|
466
|
+
FreeType (`FREETYPE_VERSION`) and HarfBuzz (`HARFBUZZ_VERSION`), compiles the
|
|
467
|
+
engine and merges everything into `runtime/lib/<os>-<arch>/gui.a` (or
|
|
468
|
+
`gui.lib` with the MSVC ABI on Windows), same convention as `core.a`,
|
|
469
|
+
statically bundling all three. The merge uses `libtool` on macOS and
|
|
470
|
+
`ar -M` (GNU/LLVM) elsewhere; CMake archives are discovered under the build
|
|
471
|
+
root or `Release/` for either generator style.
|
|
472
|
+
- `src/extensions/gui/index.ts` exposes the archive through `nativeObjects()`
|
|
473
|
+
(via `findRuntimeLibrary`, so `.a`/`.lib` both work) and the OS frameworks
|
|
474
|
+
through `linkerFlags()`.
|
|
475
|
+
- CI builds `gui.a` before assembling the release archive so it ships inside
|
|
476
|
+
`runtime/lib/<slug>/` (the release tarball copies the whole `runtime/` tree).
|
|
477
|
+
The archive is built and the example is run on Linux (under Xvfb, with the
|
|
478
|
+
lavapipe software Vulkan driver) and macOS; Windows is provisional.
|
|
479
|
+
- The existing cache fingerprint already hashes `nativeObjects()` contents, so
|
|
480
|
+
rebuilding `gui.a` invalidates cached binaries automatically.
|
|
481
|
+
|
|
482
|
+
### Running the example
|
|
483
|
+
|
|
484
|
+
```sh
|
|
485
|
+
npm run runtime # rebuild core.a after a runtime change
|
|
486
|
+
npm run gui # build runtime/lib/<os>-<arch>/gui.a (fetches SDL3 once)
|
|
487
|
+
xbintsc run examples/gui/hello.ts --ext gui
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Set `XT_GUI_AUTOCLOSE_MS=<n>` to close all windows after `n` milliseconds,
|
|
491
|
+
which the e2e test (`tests/e2e/gui-*.test.ts`) uses to run headlessly.
|
|
492
|
+
|
|
493
|
+
On a headless Linux box, install the SDL3 build headers and run under Xvfb with a
|
|
494
|
+
software Vulkan driver:
|
|
495
|
+
|
|
496
|
+
```sh
|
|
497
|
+
sudo apt-get install -y clang cmake libx11-dev libxext-dev libxrandr-dev \
|
|
498
|
+
libxcursor-dev libxi-dev libxinerama-dev libxfixes-dev libxkbcommon-dev \
|
|
499
|
+
libwayland-dev wayland-protocols libdecor-0-dev libasound2-dev libpulse-dev \
|
|
500
|
+
libdbus-1-dev libudev-dev libdrm-dev libgbm-dev libgl1-mesa-dev \
|
|
501
|
+
libegl1-mesa-dev libvulkan-dev mesa-vulkan-drivers xvfb
|
|
502
|
+
npm run runtime && npm run gui
|
|
503
|
+
xvfb-run -a --server-args="-screen 0 1280x720x24" \
|
|
504
|
+
npx tsx src/cli/main.ts run examples/gui/hello.ts --ext gui
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Those are the packages the CI `compile-examples` job installs. The X11 backend is
|
|
508
|
+
used by default; Wayland is enabled too but not yet exercised.
|
|
509
|
+
|
|
510
|
+
## Milestones
|
|
511
|
+
|
|
512
|
+
1. **M1 — foundation** ✅
|
|
513
|
+
- Generic `xt_loop_poll` / `xt_loop_set_main` in the runtime.
|
|
514
|
+
- `gui` extension skeleton + generic CLI extension registration.
|
|
515
|
+
2. **M2 — window + GPU clear** ✅
|
|
516
|
+
- SDL3 window, SDL_GPU swapchain, multiple windows, main-loop integration.
|
|
517
|
+
- `createWindow` / `run` / `quit` + `on`/`off` window methods work
|
|
518
|
+
end-to-end (`runtime/ext_gui/`, `scripts/build-gui.ts`).
|
|
519
|
+
3. **M3 — HTML/CSS subset** ✅
|
|
520
|
+
- **M3a — parse + cascade** ✅ HTML parser, DOM tree, CSS parser, selector
|
|
521
|
+
matching, UA/author/inline cascade, inheritance, computed style
|
|
522
|
+
(`dom.*`, `css.*`, `style.*`, `document.*`).
|
|
523
|
+
- **M3b — layout** ✅ block/inline flow + Flexbox (`layout.*`),
|
|
524
|
+
`getBoundingClientRect`/`layoutTree`.
|
|
525
|
+
4. **M4 — paint + text + display list**
|
|
526
|
+
- **M4a — display list + GPU shapes** ✅ background/border display list,
|
|
527
|
+
rounded-rect SDL_GPU pipeline (`paint.*`, `renderer.*`).
|
|
528
|
+
- **M4b-1 — text stack + metrics** ✅ HarfBuzz + FreeType linked into
|
|
529
|
+
`gui.a`, font resolution/caching, shaping-based text metrics used by
|
|
530
|
+
layout (`text.*`, `measureText`/`fontMetrics`).
|
|
531
|
+
- **M4b-2 — glyph rendering** ✅ FreeType rasterisation, a shared shelf-packed
|
|
532
|
+
glyph atlas, textured text quads in the display list and HiDPI-aware raster
|
|
533
|
+
scaling. Gradients remain.
|
|
534
|
+
5. **M5 — input + events** ✅
|
|
535
|
+
- hit testing (`LayoutTree::hitTest`), pointer/wheel/keyboard events delivered
|
|
536
|
+
to TS handlers with a payload, `:hover`/`:focus` stateful matching and
|
|
537
|
+
restyle (`css.*`, `style.*`, `document.*`, `gui.cpp`), plus the
|
|
538
|
+
`hitTest`/`sendEvent` test hooks.
|
|
539
|
+
6. **M6 — images, then CSS transitions/animations**
|
|
540
|
+
- **M6a — images** ✅ stb_image decode, `<img>` replaced-element layout,
|
|
541
|
+
per-file GPU textures and textured quads (`image.*`, `paint.*`, `renderer.*`).
|
|
542
|
+
- **M6b — transitions/animations** ✅ `transition` shorthand + longhands,
|
|
543
|
+
animated `background-color`/`color`/`border-color`/`border-radius`, retargeting
|
|
544
|
+
and `win.advance(ms)`. `@keyframes` remain.
|
|
545
|
+
7. **M7 — CI & releases** ✅ (Linux/macOS build) / 🚧 (run + Windows)
|
|
546
|
+
- `compile-examples` builds `gui.a` on Linux and macOS (required), then runs
|
|
547
|
+
the example and `tests/e2e/gui-*.test.ts` under Xvfb + lavapipe on Linux
|
|
548
|
+
(required). The macOS run is provisional until a WindowServer is confirmed;
|
|
549
|
+
it is guarded so a failure is logged without annotating the run.
|
|
550
|
+
- The `package` job builds `gui.a` before assembling the release, so it ships
|
|
551
|
+
inside the existing runtime archive (`package-release` copies all of
|
|
552
|
+
`runtime/`).
|
|
553
|
+
- `vendor/` (SDL3/FreeType/HarfBuzz, the slow part) is cached per OS/arch,
|
|
554
|
+
keyed by `scripts/build-gui.ts`.
|
|
555
|
+
- Windows (build + run) stays provisional until the MSVC-compatible
|
|
556
|
+
`gui.lib` and D3D12/DXIL shader path are validated; a failure is logged
|
|
557
|
+
without annotating the run (see *Open questions*).
|
|
558
|
+
8. **M8 — interactive DOM** ✅
|
|
559
|
+
- Element/document handles with stable identity, read/write properties via
|
|
560
|
+
runtime accessors, traversal/attributes/queries, mutation with lazy
|
|
561
|
+
restyle/relayout (`dom_api.*`, `document.*`, `dom.*`, `gui.cpp`,
|
|
562
|
+
`window.cpp`), element events with capture + bubble phases and
|
|
563
|
+
`stopPropagation`, and window-level `e.target` compatibility. No compiler
|
|
564
|
+
changes. See `doc/gui-scripts.md`.
|
|
565
|
+
9. **M9 — AOT `<script>`** ✅
|
|
566
|
+
- **M9a** ✅ — generic `Extension.assetLoaders` hook + bundler integration.
|
|
567
|
+
- **M9b** ✅ — gui `.html` asset loader (`src/extensions/gui/html.ts`): inline
|
|
568
|
+
bodies are wrapped in `__xt_script_<hash>(window, document)` functions,
|
|
569
|
+
registered through the `__registerScript` builtin and replaced by
|
|
570
|
+
`<script data-xt-id="<hash>">` markers; `win.loadHTML` runs the matching
|
|
571
|
+
functions after parsing and fires `DOMContentLoaded` then `load`.
|
|
572
|
+
- **M9c** ✅ — `<script src>` files are read relative to the HTML, their
|
|
573
|
+
top-level imports are hoisted (specifiers rewritten from the HTML dir) and
|
|
574
|
+
their body is wrapped/registered; missing files and import-binding
|
|
575
|
+
collisions become diagnostics. Everything runs in document order.
|
|
576
|
+
10. **M10 — polish** ✅
|
|
577
|
+
- `win.requestAnimationFrame(fn)` / `win.cancelAnimationFrame(id)`; callbacks
|
|
578
|
+
run at the top of each frame with the frame timestamp and may mutate the
|
|
579
|
+
DOM (`gui.cpp`, `window.cpp`, `gui_engine.h`).
|
|
580
|
+
- `Element.offsetWidth` / `offsetHeight` (rounded border box, flushes pending
|
|
581
|
+
mutations) and `Element.contains(other)` (`dom_api.cpp`).
|
|
582
|
+
|
|
583
|
+
## Progress log
|
|
584
|
+
|
|
585
|
+
- **M1** ✅ generic `xt_loop_poll`/`xt_loop_set_main`; `gui` extension skeleton.
|
|
586
|
+
- **M2** ✅ SDL3 window + SDL_GPU clear, multiple windows, `createWindow`/`run`.
|
|
587
|
+
- **M3a** ✅ HTML parser (`dom.*`), CSS parser/matcher (`css.*`), cascade and
|
|
588
|
+
computed style (`style.*`), document model (`document.*`),
|
|
589
|
+
`computedStyle`/`queryCount`/`documentTree`, e2e coverage.
|
|
590
|
+
- **M3b** ✅ layout (`layout.*`): block flow, inline formatting context with line
|
|
591
|
+
breaking, single-line Flexbox, `getBoundingClientRect`/`layoutTree`, e2e
|
|
592
|
+
coverage.
|
|
593
|
+
- **M4a** ✅ display list (`paint.*`) and the SDL_GPU 2D renderer with an MSL
|
|
594
|
+
rounded-rect pipeline (`renderer.*`), `paintList`/`paintCount`, e2e coverage.
|
|
595
|
+
- **M4b-1** ✅ FreeType + HarfBuzz fetched/built/merged into `gui.a`, the text
|
|
596
|
+
module (`text.*`) with font resolution, HarfBuzz shaping and FreeType metrics,
|
|
597
|
+
real text metrics in layout, `measureText`/`fontMetrics`, e2e coverage.
|
|
598
|
+
- **M4b-2** ✅ glyph atlas + textured text pipeline in `renderer.*`, shaped text
|
|
599
|
+
runs in `paint.*`, `xt_text_shape_run`/`xt_text_rasterize` in `text.*`, HiDPI
|
|
600
|
+
raster scaling, e2e coverage.
|
|
601
|
+
- **M5** ✅ hit testing + input events (`LayoutTree::hitTest`, `xt_dom_describe`,
|
|
602
|
+
`xt_gui_dispatch_*`), `:hover`/`:focus` in the matcher with dynamic restyle,
|
|
603
|
+
`hitTest`/`sendEvent` test hooks, e2e coverage.
|
|
604
|
+
- **M6a** ✅ image decoding (`image.*`, vendored stb_image), `<img>` intrinsic
|
|
605
|
+
sizing in layout, `PaintImage` in the display list, per-file RGBA textures and
|
|
606
|
+
an image pipeline in `renderer.*`, e2e coverage.
|
|
607
|
+
- **M6b** ✅ CSS transitions (parsing in `style.*`, an animation clock and
|
|
608
|
+
transition state in `document.*`, `advance`/`advance(ms)` hooks, e2e coverage).
|
|
609
|
+
- **M7** ✅ builds `gui.a` in CI on Linux and macOS (required) and runs the
|
|
610
|
+
example plus the GUI e2e suite under Xvfb on Linux; `package` ships `gui.a`,
|
|
611
|
+
`vendor/` is cached, and the `ar -M` merge works on GNU/Linux and macOS.
|
|
612
|
+
macOS runs and all of Windows remain provisional in CI.
|
|
613
|
+
- **M8** ✅ element/document handles (`dom_api.*`), DOM mutation with lazy
|
|
614
|
+
restyle/relayout (`document.*`, `xt_gui_flush_dom`), element event dispatch
|
|
615
|
+
with capture/bubble and `stopPropagation` (`dom_api.*`, `gui.cpp`), and e2e
|
|
616
|
+
coverage in `tests/e2e/gui-*.test.ts`.
|
|
617
|
+
- **M9a** ✅ extensions can register asset loaders keyed by file extension;
|
|
618
|
+
`bundleModules`/`loadGraph` consult them after reading a file. Unit tests in
|
|
619
|
+
`tests/driver/modules.test.ts`.
|
|
620
|
+
- **M9b** ✅ `import page from "./page.html"` compiles inline `<script lang="ts">`
|
|
621
|
+
bodies into AOT functions registered at startup and run by `win.loadHTML`
|
|
622
|
+
(before first layout; `DOMContentLoaded` then `load`). Unit tests in
|
|
623
|
+
`tests/extensions/gui.test.ts`, e2e in `tests/e2e/gui-*.test.ts`.
|
|
624
|
+
- **M9c** ✅ external `<script src>` files are read, their imports hoisted
|
|
625
|
+
(specifiers rewritten from the HTML dir) and their body wrapped/registered;
|
|
626
|
+
missing files and import-binding collisions are diagnostics. Loader errors are
|
|
627
|
+
caught by `loadGraph` and reported as build errors.
|
|
628
|
+
- **M10** ✅ `requestAnimationFrame`/`cancelAnimationFrame` on window handles
|
|
629
|
+
(callbacks run with the frame timestamp before layout each frame) plus
|
|
630
|
+
`offsetWidth`/`offsetHeight`/`contains` on element handles; e2e coverage in
|
|
631
|
+
`tests/e2e/gui-*.test.ts`.
|
|
632
|
+
|
|
633
|
+
## Open questions
|
|
634
|
+
|
|
635
|
+
- Whether Linux ships X11, Wayland, or both in the first cut. (Decision:
|
|
636
|
+
X11 first, Wayland later.)
|
|
637
|
+
- Windows: an MSVC-compatible `gui.lib` (COFF objects + `ar -M`/`llvm-ar`) is
|
|
638
|
+
produced by `scripts/build-gui.ts`, but it has not been validated in CI yet,
|
|
639
|
+
so the Windows step is provisional (the job stays green) and `package` skips
|
|
640
|
+
it. It also needs an SDL3 build with the D3D12/DXIL backend (DXIL requires
|
|
641
|
+
`dxc`).
|
|
642
|
+
- **D3D12 shaders:** the SPIR-V (Vulkan) and DXIL (Direct3D 12) blobs are built
|
|
643
|
+
and embedded, so the non-Metal path no longer skips geometry. DXIL remains
|
|
644
|
+
unusable on **SDL 3.2.10**: its D3D12 backend rejects any graphics pipeline
|
|
645
|
+
whose shaders declare a uniform buffer, and the engine passes the viewport that
|
|
646
|
+
way. Re-check after an SDL upgrade.
|