xbintsc 0.3.79 → 0.3.81

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.
Files changed (53) hide show
  1. package/README.md +1 -3
  2. package/README.zh-CN.md +1 -2
  3. package/dist/src/driver/compiler.js +6 -1
  4. package/dist/src/driver/compiler.js.map +1 -1
  5. package/dist/src/extensions/gui/index.js +10 -2
  6. package/dist/src/extensions/gui/index.js.map +1 -1
  7. package/dist/tests/e2e/gui-example-svg.test.d.ts +21 -0
  8. package/dist/tests/e2e/gui-example-svg.test.js +105 -0
  9. package/dist/tests/e2e/gui-example-svg.test.js.map +1 -0
  10. package/dist/tests/e2e/gui-transform.test.d.ts +7 -0
  11. package/dist/tests/e2e/gui-transform.test.js +198 -0
  12. package/dist/tests/e2e/gui-transform.test.js.map +1 -0
  13. package/doc/ai/cli.md +5 -0
  14. package/doc/ai/extensions.md +5 -1
  15. package/doc/ai/zh-CN/extensions.md +5 -1
  16. package/doc/gui/animation.md +53 -2
  17. package/doc/gui/api.md +1 -1
  18. package/doc/gui/dom.md +6 -1
  19. package/doc/gui/input.md +4 -1
  20. package/doc/gui/known-issues.md +16 -4
  21. package/doc/gui/layout.md +36 -0
  22. package/doc/gui/rendering.md +15 -0
  23. package/doc/gui.md +32 -3
  24. package/doc/implemented.md +1 -1
  25. package/doc/zh-CN/gui/animation.md +49 -2
  26. package/doc/zh-CN/gui/api.md +1 -1
  27. package/doc/zh-CN/gui/dom.md +6 -1
  28. package/doc/zh-CN/gui/input.md +2 -0
  29. package/doc/zh-CN/gui/known-issues.md +12 -2
  30. package/doc/zh-CN/gui/layout.md +31 -0
  31. package/doc/zh-CN/gui/rendering.md +11 -0
  32. package/doc/zh-CN/gui.md +28 -3
  33. package/doc/zh-CN/implemented.md +1 -1
  34. package/package.json +1 -1
  35. package/runtime/ext_gui/document.cpp +21 -3
  36. package/runtime/ext_gui/document.h +4 -1
  37. package/runtime/ext_gui/dom_api_node.cpp +6 -4
  38. package/runtime/ext_gui/layout.cpp +78 -1
  39. package/runtime/ext_gui/layout.h +21 -0
  40. package/runtime/ext_gui/paint.cpp +48 -15
  41. package/runtime/ext_gui/paint.h +8 -2
  42. package/runtime/ext_gui/renderer.cpp +4 -2
  43. package/runtime/ext_gui/renderer_image.cpp +2 -1
  44. package/runtime/ext_gui/renderer_internal.h +6 -1
  45. package/runtime/ext_gui/renderer_text.cpp +37 -15
  46. package/runtime/ext_gui/style.h +10 -0
  47. package/runtime/ext_gui/style_print.cpp +6 -0
  48. package/runtime/ext_gui/style_values.cpp +43 -0
  49. package/runtime/ext_gui/transform.cpp +412 -0
  50. package/runtime/ext_gui/transform.h +112 -0
  51. package/runtime/ext_gui/window.cpp +6 -4
  52. package/src/driver/compiler.ts +6 -1
  53. package/src/extensions/gui/index.ts +10 -2
package/doc/gui/dom.md CHANGED
@@ -20,7 +20,12 @@ pseudo-classes `:first-child`, `:last-child`, `:only-child`, `:empty`,
20
20
  stateful `:hover` and `:focus` (see [Input](./input.md)). Values:
21
21
  lengths (`px`, `%`, `em`, `rem`, `vw`, `vh`, `pt`, `pc`, `in`, `cm`, `mm`, `q`),
22
22
  colors (hex, `rgb()`/`rgba()`, a named subset), numbers, keywords and shorthands
23
- (`margin`/`padding`/`border`/`flex`).
23
+ (`margin`/`padding`/`border`/`flex`). `transform` (`translate`/`translate3d`/
24
+ `translateX`/`translateY`, `scale`/`scale3d`/`scaleX`/`scaleY`, `rotate`/
25
+ `rotateZ`, `matrix`; the `3d` spellings' `z` argument is dropped) and
26
+ `transform-origin` are parsed into the computed style as a list of operations
27
+ plus two (unresolved) origin lengths; an unsupported function invalidates the
28
+ whole declaration ([Layout](./layout.md#transforms-m15)).
24
29
 
25
30
  **Cascade & computed style** (`runtime/ext_gui/style.{h,cpp}`): a small built-in
26
31
  UA stylesheet, author rules sorted by `(!important, specificity, source order)`,
package/doc/gui/input.md CHANGED
@@ -6,7 +6,10 @@
6
6
 
7
7
  `LayoutTree::hitTest` returns the deepest box containing a point (probing later
8
8
  siblings first so the topmost element wins), and `xt_dom_describe` turns the
9
- element into the `div#id.class` descriptor carried by event payloads.
9
+ element into the `div#id.class` descriptor carried by event payloads. A
10
+ transformed box maps the point through its inverse matrix first, so a rotated,
11
+ scaled or translated element is hit exactly where it is drawn — and `:hover`
12
+ follows the artwork, not the box flow left behind.
10
13
 
11
14
  - SDL pointer/wheel/key events are routed to the owning window, hit tested, and
12
15
  delivered to the handlers registered with `on`. `mousedown` also moves focus.
@@ -48,11 +48,23 @@
48
48
  rectangles/text/images, so `<svg>` subtrees would need a whole second
49
49
  geometry+paint path. Vector art therefore has to arrive as an SVG/PNG file or
50
50
  be drawn from boxes, as `examples/gui/pelican-bike` does.
51
+ - **Windows: the C++ runtime must not be named on the link line.** `gui.lib` is
52
+ built with the *static* CRT (`CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded`) and
53
+ every engine object records `libcpmt` as a dependent library, which is also
54
+ clang's Windows default and what the xbintsc runtime objects use
55
+ (`scripts/build-gui.ts`). Asking for the DLL runtime as well — the gui
56
+ extension used to pass `-lmsvcprt` — makes the two copies of the same symbols
57
+ collide: `LNK2038: mismatch detected for 'RuntimeLibrary'` plus a wall of
58
+ `LNK2005: already defined`, surfaced as `clang: linker command failed with exit
59
+ code 1169`. `src/extensions/gui/index.ts` now lists only the OS libraries
60
+ (`user32`, `gdi32`, `d3d12`, …). A Windows link is also the only place the
61
+ MSVC environment matters, so build from an x64 Native Tools prompt.
51
62
  - Windows: an MSVC-compatible `gui.lib` (COFF objects + `ar -M`/`llvm-ar`) is
52
- produced by `scripts/build-gui.ts`, but it has not been validated in CI yet,
53
- so the Windows step is provisional (the job stays green) and `package` skips
54
- it. It also needs an SDL3 build with the D3D12/DXIL backend (DXIL requires
55
- `dxc`).
63
+ produced by `scripts/build-gui.ts` but is not validated in CI, so the Windows
64
+ step is provisional (the job stays green) and `package` skips it. Building and
65
+ running `examples/gui/pelican-svg` and `examples/gui/pelican-bike` on a
66
+ developer machine works. It also needs an SDL3 build with the D3D12/DXIL
67
+ backend (DXIL requires `dxc`).
56
68
  - **Media on Windows:** the media decoders (FFmpeg) are only built and merged
57
69
  into the POSIX `gui.a`; the Windows `gui.lib` is produced without them, so
58
70
  `<video>`/`<audio>` are inert there (failures fire `error`). Enabling it needs
package/doc/gui/layout.md CHANGED
@@ -66,6 +66,42 @@ flex items from non-whitespace text, full `z-index` stacking contexts, and
66
66
  `float` affecting block children (only inline content wraps; a block must
67
67
  `clear`).
68
68
 
69
+ ## Transforms (M15)
70
+
71
+ `transform` and `transform-origin` are a **paint-time** effect: the box model
72
+ above is untouched (nothing reflows, no geometry is recomputed), and a second
73
+ pass after `xt_layout_run` gives every `LayoutBox` a `Matrix2D world` — the
74
+ transform of the whole ancestor chain — plus its `inverse`:
75
+
76
+ - **Only element boxes contribute.** A text box shares its parent element's
77
+ `style` pointer, so letting both contribute would apply the element's list
78
+ twice; the pass composes an element's own list, its ancestors', and nothing
79
+ else.
80
+ - **`transform-origin` is resolved here**, not in the cascade: the computed style
81
+ keeps the two (possibly `%`) `Length`s (default `50% 50%`) and layout resolves
82
+ them against the *border box*, applying `T(origin) · M · T(-origin)`. That is
83
+ why `computedStyle(el, "transform-origin")` still reports `50% 92%`. The
84
+ keywords are positional as in CSS: `top` alone is `center top`, and the
85
+ keyword pair may be given in either order (`bottom right` = `right bottom`).
86
+ - **Lengths resolve against the box**: `%` in `translate()` is a share of the
87
+ border box's width/height, `em`/`rem` against the root font size (16px), and
88
+ `vw`/`vh` against the viewport.
89
+ - **Composition order** follows CSS: the list applies right to left, so
90
+ `rotate(90deg) translate(10px)` moves the box *down* the screen, not right.
91
+
92
+ Everything downstream follows the matrices:
93
+
94
+ | Consumer | Behaviour |
95
+ | --- | --- |
96
+ | Paint | each op carries the matrix (see [Rendering](./rendering.md#paint-and-display-list-m4am4b)); a clipped subtree clips to the bounding box of its padding box through the matrix |
97
+ | Hit testing | `hitTestBox` maps the point through each box's inverse before the containment test, so a rotated or scaled element is clickable where it is *drawn* |
98
+ | `getBoundingClientRect` | the axis-aligned bounding box of the **transformed** border box (`xt_layout_transformed_bounds`) |
99
+ | Layout geometry | unchanged — `layoutTree()` and `offsetWidth`/`offsetHeight` keep reporting the untransformed box |
100
+
101
+ Unsupported transform functions (`skew`, `perspective`, …) invalidate the whole
102
+ declaration, so a box is either correctly transformed or not transformed at all
103
+ rather than partially applied.
104
+
69
105
  **Document** (`runtime/ext_gui/document.{h,cpp}`): owns the DOM tree, gathers
70
106
  `<style>` text into one stylesheet, computes styles and layout for a viewport and
71
107
  offers `querySelector`/`querySelectorAll`/`styleOf`/`boxOf`.
@@ -54,6 +54,21 @@ its own GPU scissor rectangle (scaled to device pixels), so painter order and
54
54
  - Geometry is uploaded only when the document or viewport changes
55
55
  (`geometry.dirty`), so steady-state frames are bind-and-draw.
56
56
 
57
+ Each op also carries the `Matrix2D` of the box it came from
58
+ (`runtime/ext_gui/transform.h`, `PaintRect::transform` and friends). It is
59
+ applied while the vertex buffer is built, not in the shader: `pushRect` maps its
60
+ four corners through the matrix (the distance-field `local`/half-extent/radius
61
+ fields stay in the *box* frame, so a rounded corner stays round after a
62
+ rotation), and `pushQuad`/`pushGlyph` map the glyph or image quad the same way.
63
+ That kept the MSL/SPIR-V/DXIL shaders and the pipelines untouched — a transform
64
+ costs a few multiplies per vertex and nothing per pixel. A clip rectangle of a
65
+ transformed box is the bounding box of the transformed padding box, because the
66
+ GPU scissor is axis aligned. Text is the one case that needs more than
67
+ multiplying corners: a scaling transform would magnify an atlas raster, so the
68
+ atlas is asked for glyphs at the device scale **times the matrix's
69
+ magnification** (clamped to 0.5–4x, since the atlas is finite) and the quad is
70
+ divided back into logical units, exactly the way HiDPI scaling already worked.
71
+
57
72
  The window background (`setBackground`) is the render-pass clear colour.
58
73
 
59
74
  Still to do in M4: gradients.
package/doc/gui.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xbintsc GUI extension (self-hosted HTML/CSS renderer)
2
2
 
3
- Status: **M14** — features (M1–M13) are complete: HTML parsing, CSS selector
3
+ Status: **M15** — features (M1–M14) are complete: HTML parsing, CSS selector
4
4
  matching, the cascade, computed styles and layout (block, inline, Flexbox and
5
5
  CSS positioning) are
6
6
  in place, and the engine *paints*: it builds a display list of rectangles, images
@@ -24,7 +24,13 @@ video frames go through the image texture path and audio to an SDL stream, with
24
24
  the DOM media API exposing `play`/`pause`/`currentTime`/… and media events.
25
25
  **M14** adds `<canvas>` with a self-written 2D software rasteriser (anti-aliased
26
26
  fills, paths, text and `drawImage`) uploaded through the image texture path —
27
- see [Canvas](./gui/canvas.md).
27
+ see [Canvas](./gui/canvas.md). **M15** adds CSS `transform` /
28
+ `transform-origin`: a composited `Matrix2D` per layout box that the display list
29
+ carries into the renderer, so boxes (and the text, images and children inside
30
+ them) can be translated, scaled and rotated without reflowing anything — and
31
+ `transition: transform`, so those poses animate. See
32
+ [Layout](./gui/layout.md#transforms-m15) and
33
+ [Animation](./gui/animation.md#css-transforms-m15).
28
34
  This overview records the locked decisions, the architecture, the milestone plan
29
35
  and the current progress; the engine itself is documented by function in the
30
36
  pages above.
@@ -38,7 +44,7 @@ pages above.
38
44
  - [Media](./gui/media.md) — images, SVG, video and audio
39
45
  - [Canvas](./gui/canvas.md) — `<canvas>` and the 2D context
40
46
  - [Input](./gui/input.md) — hit testing, events and `:hover`/`:focus`
41
- - [Animation](./gui/animation.md) — `requestAnimationFrame` and CSS transitions
47
+ - [Animation](./gui/animation.md) — `requestAnimationFrame`, CSS transitions and transforms
42
48
  - [Runtime integration](./gui/runtime.md) — event loop, native bridge and memory
43
49
  - [Packaging and build](./gui/build.md) — building `gui.a` and running examples
44
50
  - [Known issues and open questions](./gui/known-issues.md)
@@ -240,6 +246,16 @@ engine were Rust.
240
246
  GPU texture through the image path (`renderer_image.cpp`) and the display
241
247
  list carries a `canvas_id` (`paint.*`). See [Canvas](./gui/canvas.md);
242
248
  e2e in `tests/e2e/gui-canvas.test.ts`.
249
+ 15. **M15 — CSS `transform`** ✅
250
+ - `transform`/`transform-origin` parse into a `TransformOp` list
251
+ (`transform.{h,cpp}`, `style.*`), transition like any other paint
252
+ property (`document.*`), and are composed into a per-box `world` matrix
253
+ (plus its inverse) after layout (`layout.*`). Paint carries the matrix on
254
+ every op and the renderer maps quad corners through it, so the shaders are
255
+ untouched. Hit testing and `getBoundingClientRect` follow. See
256
+ [Layout](./gui/layout.md#transforms-m15) and
257
+ [Animation](./gui/animation.md#css-transforms-m15); e2e in
258
+ `tests/e2e/gui-transform.test.ts` and `tests/e2e/gui-example-svg.test.ts`.
243
259
 
244
260
  ## Progress log
245
261
 
@@ -325,4 +341,17 @@ engine were Rust.
325
341
  image texture path (`renderer_image.cpp`) and the display list carries a
326
342
  `canvas_id` (`paint.*`). See [Canvas](./gui/canvas.md); e2e in
327
343
  `tests/e2e/gui-canvas.test.ts`.
344
+ - **M15** ✅ CSS `transform` / `transform-origin`. A new translation unit
345
+ (`transform.{h,cpp}`) parses `translate`/`translateX`/`translateY`/`scale`/
346
+ `scaleX`/`scaleY`/`rotate`/`matrix` (plus `translate3d`/`scale3d`, whose `z`
347
+ argument is dropped) into a `Matrix2D` list, reports them back through
348
+ `computedStyle`, and interpolates them for transitions (a shape or unit change
349
+ snaps). Layout composes a `world` matrix and its inverse per box after the
350
+ normal pass (`applyTransforms`), resolving `transform-origin` against the
351
+ border box — so layout geometry, and therefore flow, never changes. Paint
352
+ attaches the matrix to every shape/text/image op and the renderer maps quad
353
+ corners through it (no shader or uniform change), hit testing maps the point
354
+ through the inverse, and `getBoundingClientRect` returns the transformed AABB.
355
+ `examples/gui/pelican-svg` animates its artwork with it; e2e in
356
+ `tests/e2e/gui-transform.test.ts` and `tests/e2e/gui-example-svg.test.ts`.
328
357
 
@@ -366,7 +366,7 @@ Location: `tests/` (`lexer` / `parser` / `binder` / `codegen` / `driver` / `exte
366
366
  | Value model | 64-bit NaN-boxing, uniform function ABI (including `this`), closure environments, object prototype chains |
367
367
  | Runtime | Strings / objects / arrays / typed arrays / closures / arithmetic / comparison / catchable exceptions / Promise / collections / symbols / generators / `fetch` / `console` |
368
368
  | Extensions | Extension registry, `core` (print), `node` (fs / path / os / process / buffer / stream / net / dgram / http imported by specifier) |
369
- | GUI (`gui` extension) | Self-hosted HTML/CSS renderer window (SDL3 + SDL_GPU): DOM/CSS/layout, `<img>`/SVG, `<video>`/`<audio>`, `<canvas>` 2D, events, transitions |
369
+ | GUI (`gui` extension) | Self-hosted HTML/CSS renderer window (SDL3 + SDL_GPU): DOM/CSS/layout, CSS `transform`/`transform-origin`, `<img>`/SVG, `<video>`/`<audio>`, `<canvas>` 2D, events, transitions (including `transform`) |
370
370
  | Toolchain | clang compiles IR/C, linking, incremental cache |
371
371
  | Project config | `xbintsc.config.json` (`entry` / `outDir` / `optimize` / `extensions` / `app`), discovery + CLI precedence |
372
372
  | Application icon | compile-time embed (PNG/ICO/ICNS), PE resource on Windows, `.app` bundle on macOS, runtime window/Dock icon via `gui` |
@@ -31,8 +31,10 @@ transition 的属性时,会记录一个正在运行的 transition,并在每
31
31
  让测试可以确定性地推进。
32
32
 
33
33
  - 支持的属性:`background-color`、`color`、`border-color`、
34
- `border-radius`;`transition: all` 覆盖它们。影响结构/布局的属性暂不做
35
- 动画(那会每帧重新布局)。
34
+ `border-radius` 与 `transform`;`transition: all` 覆盖它们。影响布局的
35
+ 属性(width、position 等)仍不做动画——那会每帧重新布局——但 `transform`
36
+ 是绘制期效果,因此为它做动画的代价只是每个被动画的盒子每帧一个矩阵
37
+ (见[布局](./layout.md#变换m15))。
36
38
  - `transition` 简写与 `transition-property` / `-duration` / `-delay` /
37
39
  `-timing-function` 长写都会被解析;时间值接受 `s` 与 `ms`;缓动函数为
38
40
  `linear`、`ease`、`ease-in`、`ease-out` 与 `ease-in-out`(`ease` 是
@@ -42,5 +44,50 @@ transition 的属性时,会记录一个正在运行的 transition,并在每
42
44
  - `computedStyle()` 报告显示(已插值)的值,因此 transition 在测试中可直接
43
45
  观察。
44
46
 
47
+ `transform` 列表按**逐项**插值(`document.cpp`、`xt_transform_interpolate`),
48
+ 这正是让常见的一对 `translateY(-4px) rotate(-0.35deg)` 与 `none` 之间平滑
49
+ 滑行而不是跳变的原因:缺失的一侧会用同类型的单位(identity)操作补齐。两个
50
+ 列表形状或单位对不上时(`rotate(10deg)` 对 `rotate(10rad)`,或函数个数不同)
51
+ 完全不做插值——transition 直接吸附到目标值,即 CSS 的离散回退,而不是一段
52
+ 看起来错误的扫过。
53
+
54
+ ## CSS transform(M15)
55
+
56
+ `transform` 与 `transform-origin` 作为*绘制期*效果得到支持
57
+ (`translate`/`translate3d`/`translateX`/`translateY`、
58
+ `scale`/`scale3d`/`scaleX`/`scaleY`、`rotate`/`rotateZ`、`matrix`;`3d` 拼写的
59
+ `z` 参数会被解析后丢弃,因为引擎是 2D 的;`deg`/`rad`/`grad`/`turn`;`%`、
60
+ `px`、`em`、`rem`、`vw`、`vh`)。布局不受影响:几何仍是流式布局放下的位置,只有绘制结果移动,
61
+ 因此尺寸、滚动位置或兄弟节点的位置从不依赖某一动画帧。合成规则见
62
+ [布局](./layout.md#变换m15),如何到达 GPU 见[绘制](./rendering.md)。
63
+
64
+ 驱动方式有两种:
65
+
66
+ ```ts
67
+ // 1. 逐帧——由帧时钟算出一个姿态。不会触发重排:字符串被解析成矩阵,
68
+ // 显示列表在同一帧里就取用它们。
69
+ const tick = (t: number): void => {
70
+ const s = t / 1000;
71
+ art.style.setProperty(
72
+ "transform",
73
+ "translateY(" + (Math.sin(s * 2.4) * 5).toFixed(2) + "px) " +
74
+ "rotate(" + (Math.sin(s * 1.15) * 1.1).toFixed(2) + "deg)",
75
+ );
76
+ win.requestAnimationFrame(tick);
77
+ };
78
+ ```
79
+
80
+ ```css
81
+ /* 2. 由状态变化驱动——和任何其他属性一样参与 transition 的 transform。 */
82
+ #caption {
83
+ transform-origin: 50% 100%;
84
+ transition: transform 260ms ease-out;
85
+ }
86
+ #caption:hover { transform: translateY(-2px) scale(1.03); }
87
+ ```
88
+
89
+ 两种方式都由 `examples/gui/pelican-svg` 演示(逐帧摇摆 + 悬停时说明文字的
90
+ transition),并由 `tests/e2e/gui-transform.test.ts` 固定住。
91
+
45
92
  动画仍需完成:`@keyframes` 动画与 `cubic-bezier(...)`。
46
93
 
@@ -59,7 +59,7 @@ win.on("keydown", (e) => console.log(e.key, e.code, e.ctrl, e.shift, e.alt, e.me
59
59
  ```ts
60
60
  win.computedStyle(selector, property) // e.g. ("#main", "width") -> "60%"
61
61
  win.queryCount(selector) // 匹配元素的数量
62
- win.getBoundingClientRect(selector) // { x, y, width, height }(边框盒)
62
+ win.getBoundingClientRect(selector) // { x, y, width, height }(边框盒,含变换)
63
63
  win.documentTree() // 序列化的 DOM(调试用)
64
64
  win.layoutTree() // 序列化的布局盒(调试用)
65
65
  win.paintCount() // 显示列表中图形的数量
@@ -20,7 +20,12 @@ doctype 跳过,实体解码(命名 + 十进制/十六进制),空元素
20
20
  有状态的 `:hover` 与 `:focus`(见[输入](./input.md))。值:长度
21
21
  (`px`、`%`、`em`、`rem`、`vw`、`vh`、`pt`、`pc`、`in`、`cm`、`mm`、`q`)、
22
22
  颜色(十六进制、`rgb()`/`rgba()`、一个命名子集)、数字、关键字与简写
23
- (`margin`/`padding`/`border`/`flex`)。
23
+ (`margin`/`padding`/`border`/`flex`)。`transform`
24
+ (`translate`/`translate3d`/`translateX`/`translateY`、
25
+ `scale`/`scale3d`/`scaleX`/`scaleY`、`rotate`/`rotateZ`、`matrix`;`3d`
26
+ 拼写的 `z` 参数会被丢弃)与 `transform-origin`
27
+ 会被解析进计算样式:前者是操作列表,后者是两个(未解析的)原点长度;不支持的
28
+ 函数会让整条声明失效(见[布局](./layout.md#变换m15))。
24
29
 
25
30
  **层叠与计算样式**(`runtime/ext_gui/style.{h,cpp}`):一份小的内置 UA
26
31
  样式表,作者规则按 `(!important, 特异性, 源码顺序)` 排序,然后是内联
@@ -7,6 +7,8 @@
7
7
 
8
8
  `LayoutTree::hitTest` 返回包含某点的最深盒(先探测靠后的兄弟,使最上层元素
9
9
  胜出),`xt_dom_describe` 把元素转换为事件负载携带的 `div#id.class` 描述符。
10
+ 被变换的盒子先把点经其逆矩阵映射,因此旋转、缩放或平移过的元素恰好在它**被画
11
+ 出来**的位置被命中——`:hover` 跟着画面走,而不是跟着流式布局留下的盒子走。
10
12
 
11
13
  - SDL 指针/滚轮/键盘事件会被路由到所属窗口,进行命中测试,并投递给用 `on`
12
14
  注册的处理器。`mousedown` 还会移动焦点。
@@ -35,10 +35,20 @@
35
35
 
36
36
  - Linux 首发是只发布 X11、Wayland,还是两者都发布。(决定:先 X11,
37
37
  Wayland 随后。)
38
+ - **Windows:链接命令行里不能点名 C++ 运行时。** `gui.lib` 用*静态* CRT 构建
39
+ (`CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded`),引擎每个目标文件都把 `libcpmt`
40
+ 记为默认依赖库;这既是 clang 在 Windows 上的默认,也是 xbintsc 运行时对象所用
41
+ 的运行时(见 `scripts/build-gui.ts`)。如果再额外要求 DLL 版运行时——gui 扩展
42
+ 过去会传 `-lmsvcprt`——同一批符号的两份定义就会冲突:`LNK2038: mismatch detected
43
+ for 'RuntimeLibrary'`,外加满屏 `LNK2005: already defined`,最终表现为
44
+ `clang: linker command failed with exit code 1169`。现在
45
+ `src/extensions/gui/index.ts` 只列系统库(`user32`、`gdi32`、`d3d12` 等)。
46
+ Windows 链接也是唯一需要 MSVC 环境的环节,请在 x64 Native Tools 命令行里构建。
38
47
  - Windows:`scripts/build-gui.ts` 会生成 MSVC 兼容的 `gui.lib`
39
48
  (COFF 对象 + `ar -M`/`llvm-ar`),但尚未在 CI 中验证,因此 Windows 步骤
40
- 为试验性(job 保持绿色),`package` 会跳过它。它还需要一个带 D3D12/DXIL
41
- 后端的 SDL3 构建(DXIL 需要 `dxc`)。
49
+ 为试验性(job 保持绿色),`package` 会跳过它;在开发者机器上构建并运行
50
+ `examples/gui/pelican-svg` 与 `examples/gui/pelican-bike` 是可以的。它还需要
51
+ 一个带 D3D12/DXIL 后端的 SDL3 构建(DXIL 需要 `dxc`)。
42
52
  - **Windows 上的媒体:** 媒体解码器(FFmpeg)只构建并合并进 POSIX 的
43
53
  `gui.a`;Windows 的 `gui.lib` 不含它们,因此那里的 `<video>`/`<audio>` 为空
44
54
  操作(失败会触发 `error`)。启用它需要 MSVC 兼容的 FFmpeg 构建,推迟到
@@ -54,6 +54,37 @@
54
54
  `z-index` 层叠上下文,以及 `float` 对块级子元素的影响(只有行内内容会绕排,
55
55
  块必须 `clear`)。
56
56
 
57
+ ## 变换(M15)
58
+
59
+ `transform` 与 `transform-origin` 是**绘制期**效果:上面的盒模型完全不变
60
+ (不重排、不重算几何),`xt_layout_run` 之后的一次额外遍历会给每个
61
+ `LayoutBox` 一个 `Matrix2D world`(整条祖先链的变换)及其 `inverse`:
62
+
63
+ - **只有元素盒参与。** 文本盒与其父元素共享同一个 `style` 指针,若两者都参与
64
+ 就会把元素的列表应用两次;该遍历只合成元素自身的列表与祖先的。
65
+ - **`transform-origin` 在这里解析**,而不是在层叠阶段:计算样式保留两个
66
+ (可能是 `%` 的)`Length`(默认 `50% 50%`),布局按*边框盒*解析它们,应用
67
+ `T(origin) · M · T(-origin)`。这就是
68
+ `computedStyle(el, "transform-origin")` 仍报告 `50% 92%` 的原因。关键字与
69
+ CSS 一样带方向:单独写 `top` 等于 `center top`,两个关键字可以任意顺序
70
+ (`bottom right` 等于 `right bottom`)。
71
+ - **长度按盒子解析**:`translate()` 里的 `%` 是边框盒宽/高的比例,`em`/`rem`
72
+ 按根字号(16px),`vw`/`vh` 按视口。
73
+ - **合成顺序**遵循 CSS:列表从右往左应用,所以
74
+ `rotate(90deg) translate(10px)` 把盒子往屏幕**下方**移动,而不是往右。
75
+
76
+ 下游消费者都跟着矩阵走:
77
+
78
+ | 消费者 | 行为 |
79
+ | --- | --- |
80
+ | 绘制 | 每个 op 携带矩阵(见[绘制](./rendering.md#绘制与显示列表m4am4b));被裁剪的子树按它的内边距盒经矩阵变换后的包围盒裁剪 |
81
+ | 命中测试 | `hitTestBox` 先把点经每个盒的逆矩阵映射,再做包含判断,所以旋转或缩放的元素在它**被画出来**的位置可点击 |
82
+ | `getBoundingClientRect` | **变换后**边框盒的轴对齐包围盒(`xt_layout_transformed_bounds`) |
83
+ | 布局几何 | 不变——`layoutTree()` 与 `offsetWidth`/`offsetHeight` 仍报告未变换的盒子 |
84
+
85
+ 不支持的变换函数(`skew`、`perspective` 等)会让整条声明失效,因此一个盒子
86
+ 要么被正确变换、要么完全不变换,而不是只应用了一部分。
87
+
57
88
  **Document**(`runtime/ext_gui/document.{h,cpp}`):持有 DOM 树,把 `<style>`
58
89
  文本汇总为一份样式表,为某个视口计算样式与布局,并提供
59
90
  `querySelector`/`querySelectorAll`/`styleOf`/`boxOf`。
@@ -47,6 +47,17 @@
47
47
  - 只有在文档或视口变化(`geometry.dirty`)时才上传几何数据,因此稳态帧
48
48
  只需绑定并绘制。
49
49
 
50
+ 每个 op 还携带它来自的那个盒子的 `Matrix2D`(`runtime/ext_gui/transform.h`、
51
+ `PaintRect::transform` 等)。它在构建顶点缓冲时应用,而不是在着色器里:
52
+ `pushRect` 把四个角经矩阵映射(距离场的 `local`/半尺寸/半径字段仍留在**盒子**
53
+ 坐标系,因此圆角旋转后依然是圆的),`pushQuad`/`pushGlyph` 同样映射字形或图像
54
+ 四边形。这样 MSL/SPIR-V/DXIL 着色器与管线都无需改动——一次变换的代价只是每个
55
+ 顶点几次乘法,与像素数无关。被变换盒子的裁剪矩形取其内边距盒经变换后的包围盒,
56
+ 因为 GPU 的 scissor 是轴对齐的。文本是唯一需要多于“乘一下四个角”的情况:
57
+ 缩放型的变换会放大图集中的位图,因此向图集请求字形时用的是设备缩放**乘以矩阵的
58
+ 放大倍率**(限制在 0.5–4 倍之间,图集容量有限),再把四边形除回逻辑单位——
59
+ 这正是 HiDPI 缩放原本就在用的做法。
60
+
50
61
  窗口背景(`setBackground`)是渲染通道的清除色。
51
62
 
52
63
  M4 仍需完成:渐变。
package/doc/zh-CN/gui.md CHANGED
@@ -2,12 +2,17 @@
2
2
 
3
3
  > 语言 / Language:[English](../gui.md) | **简体中文**
4
4
 
5
- 状态:**M14** —— 功能(M1–M13)已全部完成:HTML 解析、CSS 选择器匹配、层叠(cascade)、计算样式与布局(块级、行内、Flexbox 与 CSS 定位)均已就位,引擎还会**绘制**:它构建一个由矩形、图像与排版后的文本 run 组成的显示列表,并通过 SDL_GPU 渲染。输入事件会经过命中测试并投递给原生 TS 处理器,`:hover`/`:focus` 会被动态匹配,`<img>` 依据其固有尺寸确定大小并从纹理绘制——位图(PNG/JPEG/…)与 SVG 文件皆可,后者按绘制尺寸光栅化,因此矢量图保持清晰——CSS transition 会为绘制属性做动画。**M8** 增加了交互式 DOM:具有稳定标识的元素句柄、变更操作(`appendChild`、`textContent`、`classList`、`style` 等)以及支持捕获/冒泡的元素级事件。**M9** 会提前编译 `<script>` 主体(内联与 `<script src>`)——参见 `gui-scripts.md`。**M10** 增加 `requestAnimationFrame` 以及若干 DOM 辅助方法。**M11** 增加 `position: relative`/`absolute`/`fixed` 及其偏移,`examples/gui/pelican-bike`(一个可玩的 2D 游戏)就构建在它之上。**M12** 明确了 DOM 节点生命周期(哪些变更释放、哪些保留),并让引擎内存有界:参见[节点生命周期](./gui/dom.md)与[内存与 GC](./gui/runtime.md)。**M13** 真正播放
5
+ 状态:**M15** —— 功能(M1–M14)已全部完成:HTML 解析、CSS 选择器匹配、层叠(cascade)、计算样式与布局(块级、行内、Flexbox 与 CSS 定位)均已就位,引擎还会**绘制**:它构建一个由矩形、图像与排版后的文本 run 组成的显示列表,并通过 SDL_GPU 渲染。输入事件会经过命中测试并投递给原生 TS 处理器,`:hover`/`:focus` 会被动态匹配,`<img>` 依据其固有尺寸确定大小并从纹理绘制——位图(PNG/JPEG/…)与 SVG 文件皆可,后者按绘制尺寸光栅化,因此矢量图保持清晰——CSS transition 会为绘制属性做动画。**M8** 增加了交互式 DOM:具有稳定标识的元素句柄、变更操作(`appendChild`、`textContent`、`classList`、`style` 等)以及支持捕获/冒泡的元素级事件。**M9** 会提前编译 `<script>` 主体(内联与 `<script src>`)——参见 `gui-scripts.md`。**M10** 增加 `requestAnimationFrame` 以及若干 DOM 辅助方法。**M11** 增加 `position: relative`/`absolute`/`fixed` 及其偏移,`examples/gui/pelican-bike`(一个可玩的 2D 游戏)就构建在它之上。**M12** 明确了 DOM 节点生命周期(哪些变更释放、哪些保留),并让引擎内存有界:参见[节点生命周期](./gui/dom.md)与[内存与 GC](./gui/runtime.md)。**M13** 真正播放
6
6
  `<video>`/`<audio>`——一个 vendored 的最小 FFmpeg 在主线程解码,视频帧走图像
7
7
  纹理路径、音频进入 SDL 流,DOM 媒体 API 暴露 `play`/`pause`/`currentTime`/…
8
8
  与媒体事件。**M14** 增加了 `<canvas>`,配一个自研的 2D 软件光栅器(抗锯齿
9
9
  填充、路径、文本与 `drawImage`),并经图像纹理路径上传——参见
10
- [Canvas](./gui/canvas.md)。本总览记录了锁定决策、架构、里程碑计划与当前进度;引擎本身按功能拆分在以上各页中。
10
+ [Canvas](./gui/canvas.md)。**M15** 增加了 CSS `transform` /
11
+ `transform-origin`:每个布局盒一个合成好的 `Matrix2D`,由显示列表一路带到
12
+ 渲染器,因此盒子(以及其中的文本、图像与子元素)可以被平移、缩放与旋转而
13
+ 不触发任何重排——再加上 `transition: transform`,这些姿态可以动画(自绘的示例
14
+ `examples/gui/pelican-svg` 正是用它让矢量画动起来)。参见
15
+ [布局](./gui/layout.md#变换m15)与[动画](./gui/animation.md#css-transformm15)。本总览记录了锁定决策、架构、里程碑计划与当前进度;引擎本身按功能拆分在以上各页中。
11
16
 
12
17
  ## 目录
13
18
 
@@ -18,7 +23,7 @@
18
23
  - [媒体](./gui/media.md) —— 图像、SVG、视频与音频
19
24
  - [Canvas](./gui/canvas.md) —— `<canvas>` 与 2D 上下文
20
25
  - [输入](./gui/input.md) —— 命中测试、事件与 `:hover`/`:focus`
21
- - [动画](./gui/animation.md) —— `requestAnimationFrame` 与 CSS transition
26
+ - [动画](./gui/animation.md) —— `requestAnimationFrame`、CSS transition 与 transform
22
27
  - [运行时集成](./gui/runtime.md) —— 事件循环、原生桥接与内存
23
28
  - [打包与构建](./gui/build.md) —— 构建 `gui.a` 与运行示例
24
29
  - [已知问题与待决问题](./gui/known-issues.md)
@@ -197,6 +202,15 @@ FFmpeg `7.1`(可用 `SDL3_TAG` / `FREETYPE_VERSION` / `HARFBUZZ_VERSION` /
197
202
  位图经图像路径上传为 GPU 纹理(`renderer_image.cpp`),显示列表携带
198
203
  `canvas_id`(`paint.*`)。参见 [Canvas](./gui/canvas.md);e2e 见
199
204
  `tests/e2e/gui-canvas.test.ts`。
205
+ 15. **M15 — CSS `transform`** ✅
206
+ - `transform`/`transform-origin` 解析为 `TransformOp` 列表
207
+ (`transform.{h,cpp}`、`style.*`),和其他绘制属性一样参与 transition
208
+ (`document.*`),并在布局之后合成为每个盒子的 `world` 矩阵及其逆矩阵
209
+ (`layout.*`)。绘制把矩阵挂在每个 op 上,渲染器按矩阵映射四边形顶点,
210
+ 因此着色器无需改动。命中测试与 `getBoundingClientRect` 同样跟随。参见
211
+ [布局](./gui/layout.md#变换m15)与[动画](./gui/animation.md#css-transformm15);
212
+ e2e 见 `tests/e2e/gui-transform.test.ts` 与
213
+ `tests/e2e/gui-example-svg.test.ts`。
200
214
 
201
215
  ## 进度日志
202
216
 
@@ -273,4 +287,15 @@ FFmpeg `7.1`(可用 `SDL3_TAG` / `FREETYPE_VERSION` / `HARFBUZZ_VERSION` /
273
287
  图像纹理路径上传(`renderer_image.cpp`),显示列表携带 `canvas_id`
274
288
  (`paint.*`)。参见 [Canvas](./gui/canvas.md);e2e 见
275
289
  `tests/e2e/gui-canvas.test.ts`。
290
+ - **M15** ✅ CSS `transform` / `transform-origin`。新的翻译单元
291
+ (`transform.{h,cpp}`)把 `translate`/`translateX`/`translateY`/`scale`/
292
+ `scaleX`/`scaleY`/`rotate`/`matrix`(以及 `translate3d`/`scale3d`,其 `z` 参数被丢弃)
293
+ 解析为 `Matrix2D` 列表,经 `computedStyle` 原样报告,并为 transition 插值
294
+ (形状或单位变化则吸附)。布局在常规遍历之后为每个盒子合成 `world` 矩阵与
295
+ 逆矩阵(`applyTransforms`),`transform-origin` 按边框盒解析——所以布局几何
296
+ 与流式排布永不改变。绘制把矩阵挂到每个形状/文本/图像 op 上,渲染器按矩阵
297
+ 映射四边形顶点(不改着色器、不加 uniform);命中测试把点经逆矩阵映射,
298
+ `getBoundingClientRect` 返回变换后的包围盒。`examples/gui/pelican-svg` 用它
299
+ 为自己的画做动画;e2e 见 `tests/e2e/gui-transform.test.ts` 与
300
+ `tests/e2e/gui-example-svg.test.ts`。
276
301
 
@@ -358,7 +358,7 @@ const result = build("program.ts", { emit: "exe", outDir: "build" });
358
358
  | 值模型 | 64 位 NaN-boxing、统一函数 ABI(含 `this`)、闭包环境、对象原型链 |
359
359
  | 运行时 | 字符串 / 对象 / 数组 / 类型化数组 / 闭包 / 算术 / 比较 / 可捕获异常 / Promise / 集合 / symbol / 生成器 / `fetch` / `console` |
360
360
  | 扩展 | 扩展注册表、`core`(print)、`node`(fs / path / os / process / buffer / stream / net / dgram / http,按说明符导入) |
361
- | GUI(`gui` 扩展) | 自托管 HTML/CSS 渲染器窗口(SDL3 + SDL_GPU):DOM/CSS/布局、`<img>`/SVG、`<video>`/`<audio>`、`<canvas>` 2D、事件、transition |
361
+ | GUI(`gui` 扩展) | 自托管 HTML/CSS 渲染器窗口(SDL3 + SDL_GPU):DOM/CSS/布局、CSS `transform`/`transform-origin`、`<img>`/SVG、`<video>`/`<audio>`、`<canvas>` 2D、事件、transition(含 `transform`) |
362
362
  | 工具链 | clang 编译 IR/C、链接、增量缓存 |
363
363
  | 项目配置 | `xbintsc.config.json`(`entry` / `outDir` / `optimize` / `extensions` / `app`),支持查找与 CLI 优先级 |
364
364
  | 应用图标 | 编译期内嵌(PNG/ICO/ICNS),Windows PE 资源、macOS `.app` bundle、`gui` 运行时窗口/Dock 图标 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xbintsc",
3
- "version": "0.3.79",
3
+ "version": "0.3.81",
4
4
  "description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -119,7 +119,8 @@ void applyNumber(XtStyle *style, const std::string &property, float value) {
119
119
 
120
120
  void XtDocument::startTransitions() {
121
121
  if (display_.empty()) return; // first style computation: nothing to animate from
122
- static const char *const kProperties[] = {"background-color", "color", "border-color", "border-radius"};
122
+ static const char *const kProperties[] = {"background-color", "color", "border-color",
123
+ "border-radius", "transform"};
123
124
  for (auto &entry : styles_) {
124
125
  const Node *node = entry.first;
125
126
  const XtStyle &target = entry.second;
@@ -138,7 +139,18 @@ void XtDocument::startTransitions() {
138
139
  running.duration_ms = spec.duration * 1000.0f;
139
140
  running.delay_ms = spec.delay * 1000.0f;
140
141
  running.start_ms = (float)clock_ms_ + running.delay_ms;
141
- if (isNumberProperty(property)) {
142
+ if (std::string(property) == "transform") {
143
+ if (xt_transform_equal(current.transform, target.transform)) continue;
144
+ std::vector<TransformOp> probe;
145
+ /* Lists of a different shape (or with clashing units) cannot be
146
+ * mixed; such a change snaps instead of animating. */
147
+ if (!xt_transform_interpolate(current.transform, target.transform, 0.5f, &probe)) {
148
+ continue;
149
+ }
150
+ running.is_transform = true;
151
+ running.from_transform = current.transform;
152
+ running.to_transform = target.transform;
153
+ } else if (isNumberProperty(property)) {
142
154
  float from = numberOf(current, property);
143
155
  float to = numberOf(target, property);
144
156
  if (from == to) continue;
@@ -184,7 +196,13 @@ void XtDocument::updateDisplay() {
184
196
  if (progress > 1.0f) progress = 1.0f;
185
197
  float eased = easeProgress(progress, transition.timing);
186
198
  XtStyle &style = node_it->second;
187
- if (transition.is_color) {
199
+ if (transition.is_transform) {
200
+ std::vector<TransformOp> mixed;
201
+ if (xt_transform_interpolate(transition.from_transform, transition.to_transform, eased,
202
+ &mixed)) {
203
+ style.transform = mixed;
204
+ }
205
+ } else if (transition.is_color) {
188
206
  float rgba[4];
189
207
  for (int c = 0; c < 4; c++) rgba[c] = mix(transition.from_color[c], transition.to_color[c], eased);
190
208
  applyColor(&style, transition.property, rgba);
@@ -20,15 +20,18 @@
20
20
 
21
21
  namespace xtgui {
22
22
 
23
- /** A running CSS transition (only paint-affecting properties are supported). */
23
+ /** A running CSS transition (paint-affecting properties, plus `transform`). */
24
24
  struct ActiveTransition {
25
25
  const Node *node = nullptr;
26
26
  std::string property;
27
27
  bool is_color = false;
28
+ bool is_transform = false;
28
29
  float from_color[4] = {0, 0, 0, 0};
29
30
  float to_color[4] = {0, 0, 0, 0};
30
31
  float from_number = 0;
31
32
  float to_number = 0;
33
+ std::vector<TransformOp> from_transform;
34
+ std::vector<TransformOp> to_transform;
32
35
  float start_ms = 0; // when interpolation begins (after `delay`)
33
36
  float duration_ms = 0;
34
37
  float delay_ms = 0;
@@ -217,11 +217,13 @@ METHOD(node_get_bounding_rect) {
217
217
  if (node == nullptr || win == nullptr || win->document == nullptr) return XT_UNDEFINED;
218
218
  const xtgui::LayoutBox *box = win->document->boxOf(node);
219
219
  if (box == nullptr) return XT_UNDEFINED;
220
+ /* Like the DOM: the rectangle of the *transformed* border box. */
221
+ xtgui::LayoutRect rect = xtgui::xt_layout_transformed_bounds(box);
220
222
  xt_value object = xt_object_new();
221
- xt_object_set(object, xt_string_from_cstr("x"), xt_number(box->x));
222
- xt_object_set(object, xt_string_from_cstr("y"), xt_number(box->y));
223
- xt_object_set(object, xt_string_from_cstr("width"), xt_number(box->width));
224
- xt_object_set(object, xt_string_from_cstr("height"), xt_number(box->height));
223
+ xt_object_set(object, xt_string_from_cstr("x"), xt_number(rect.x));
224
+ xt_object_set(object, xt_string_from_cstr("y"), xt_number(rect.y));
225
+ xt_object_set(object, xt_string_from_cstr("width"), xt_number(rect.width));
226
+ xt_object_set(object, xt_string_from_cstr("height"), xt_number(rect.height));
225
227
  return object;
226
228
  }
227
229