xbintsc 0.5.41 → 0.5.42
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/dist/src/cli/help.d.ts +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/tests/e2e/gui-data-url.test.d.ts +10 -0
- package/dist/tests/e2e/gui-data-url.test.js +127 -0
- package/dist/tests/e2e/gui-data-url.test.js.map +1 -0
- package/dist/tests/e2e/gui-effects.test.js +96 -0
- package/dist/tests/e2e/gui-effects.test.js.map +1 -1
- package/dist/tests/e2e/gui-paint.test.js +77 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/doc/gui/canvas.md +6 -2
- package/doc/gui/known-issues.md +15 -9
- package/doc/gui/media.md +11 -2
- package/doc/gui/rendering.md +43 -18
- package/doc/gui.md +5 -4
- package/doc/zh-CN/gui/canvas.md +6 -4
- package/doc/zh-CN/gui/known-issues.md +11 -8
- package/doc/zh-CN/gui/media.md +9 -2
- package/doc/zh-CN/gui/rendering.md +30 -11
- package/doc/zh-CN/gui.md +1 -1
- package/package.json +7 -7
- package/runtime/ext_gui/dom_api_canvas.cpp +42 -24
- package/runtime/ext_gui/gradient_values.cpp +44 -48
- package/runtime/ext_gui/gui_events.cpp +16 -13
- package/runtime/ext_gui/image.cpp +180 -25
- package/runtime/ext_gui/image.h +6 -4
- package/runtime/ext_gui/paint.cpp +53 -3
- package/runtime/ext_gui/paint.h +11 -1
- package/runtime/ext_gui/paint_background.cpp +92 -11
- package/runtime/ext_gui/paint_effects.cpp +2 -1
- package/runtime/ext_gui/paint_internal.h +5 -1
- package/runtime/ext_gui/renderer.cpp +81 -13
- package/runtime/ext_gui/renderer.h +19 -5
- package/runtime/ext_gui/renderer_internal.h +26 -0
- package/runtime/ext_gui/renderer_layer.cpp +552 -0
- package/runtime/ext_gui/style.h +29 -1
- package/runtime/ext_gui/style_print.cpp +10 -2
- package/runtime/ext_gui/style_values.cpp +10 -0
- package/src/driver/version.ts +1 -1
package/doc/zh-CN/gui/canvas.md
CHANGED
|
@@ -166,8 +166,10 @@ canvas 由脚本重绘而不是引擎重绘,因此开销按绘制调用计。
|
|
|
166
166
|
9 参数(本引擎为**目标优先**:`dx, dy, dw, dh, sx, sy, sw, sh`)。完整的仿射变换
|
|
167
167
|
(旋转/斜切/缩放)会被应用:目标矩形变为平行四边形,每个设备像素反查到源矩形。
|
|
168
168
|
`source` 可以是 `<img>` 元素(经图像模块解码)、另一个 `<canvas>`、
|
|
169
|
-
`OffscreenCanvas
|
|
170
|
-
|
|
169
|
+
`OffscreenCanvas`、`ImageBitmap`,或——本引擎的扩展——直接传字符串路径/`data:` URL,
|
|
170
|
+
由图像模块解码,因此 SVG 或位图的 data URL 无需 `<img>` 就能绘制。矢量源按目标尺寸
|
|
171
|
+
(`dw`/`dh`)光栅化,放大时 SVG 依然清晰。`imageSmoothingEnabled` 为 `true`(默认)
|
|
172
|
+
时使用双线性采样,为 `false` 时使用最近邻。
|
|
171
173
|
|
|
172
174
|
测试钩子:`getImageData(x, y, w, h)` 把区域复制为 `{ width, height, data }`,其中
|
|
173
175
|
`data` 是按行主序的 RGBA8 字节数组。越界像素为透明。
|
|
@@ -186,8 +188,8 @@ dirtyWidth, dirtyHeight])` 直接把字节写入位图(不受变换、混合
|
|
|
186
188
|
`OffscreenCanvas.convertToBlob()` 解析为同一形状,`transferToImageBitmap()` 对位图
|
|
187
189
|
做快照。
|
|
188
190
|
|
|
189
|
-
CSS 背景:`background-image: url(...)` 接受文件路径或 `data:` URL
|
|
190
|
-
canvas 导出再作为 CSS 背景使用
|
|
191
|
+
CSS 背景:`background-image: url(...)` 接受文件路径或 `data:` URL(位图载荷或 SVG
|
|
192
|
+
文档,base64 或百分号编码均可),因此可以先把 canvas 导出再作为 CSS 背景使用
|
|
191
193
|
(`element.style.backgroundImage = "url(" + canvas.toDataURL() + ")"`)。图像层会拉伸
|
|
192
194
|
到元素的边框盒;`background-size`、`background-position` 与平铺尚未建模。
|
|
193
195
|
|
|
@@ -43,22 +43,25 @@
|
|
|
43
43
|
`var()` 自定义属性、`inset` 简写、`box-sizing`、`visibility`/`pointer-events`、
|
|
44
44
|
`@media` 查询、生成内容(`::before`/`::after`)、线性/径向多层背景、
|
|
45
45
|
`text-shadow`、`clip-path`(`inset`/`circle`/`ellipse`/`polygon`)以及 `filter`
|
|
46
|
-
的颜色函数。仍有限制:`
|
|
47
|
-
`
|
|
48
|
-
|
|
46
|
+
的颜色函数。仍有限制:`background-clip`、`background-position`/`size`/`repeat`
|
|
47
|
+
与 `backdrop-filter` 合成都未实现(`backdrop-filter` 会解析并参与层叠,但采样
|
|
48
|
+
背景需要离屏图层,因此不绘制);`mix-blend-mode` 与重复渐变已实现(见
|
|
49
|
+
[渲染](./rendering.md)),但组 `opacity` 是按图形 alpha 近似而非真正的组合成,
|
|
50
|
+
且 D3D12/DXIL 后端没有混合合成器,那里会退回普通 source-over 绘制;
|
|
49
51
|
`filter: blur()` 是按图形做九次偏移的近似,而不是真正的组模糊,重叠的子元素会加深;
|
|
50
52
|
`drop-shadow` 用盒子轮廓近似;最多只交叉两层嵌套 `clip-path`(更深时保留外侧两层);
|
|
51
53
|
图像/画布除图像着色器能携带的亮度与透明度染色外不做颜色滤镜。Canvas 纹理使用
|
|
52
|
-
最近邻采样。背景渐变仍是纯色带(线性 18 条、径向 32
|
|
53
|
-
|
|
54
|
+
最近邻采样。背景渐变仍是纯色带(线性 18 条、径向 32 个同心椭圆;重复渐变
|
|
55
|
+
64 条),柔和渐变可能出现轻微条带。
|
|
54
56
|
- **SVG 是 NanoSVG 的子集。** SVG *文件*(`<img src="logo.svg">`)与内联
|
|
55
57
|
`<svg>` 子树共用 vendored 的 NanoSVG 解析/光栅化器(`runtime/ext_gui/svg.cpp`、
|
|
56
58
|
`inline_svg.cpp`),它会渲染 `path`/`rect`/`circle`/`ellipse`/`line`/
|
|
57
59
|
`polyline`/`polygon`、组、`defs` 渐变以及基于 `class` 的 `<style>` 规则。
|
|
58
60
|
`<text>`、`<use>`、滤镜、遮罩与裁剪路径不会渲染,文档层叠的 CSS 也不会进入
|
|
59
|
-
图像内部(这里的 `fill`/`stroke` 是 SVG 表现属性,不是 CSS
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
图像内部(这里的 `fill`/`stroke` 是 SVG 表现属性,不是 CSS 属性)。因此只靠滤镜
|
|
62
|
+
出图的 SVG(`feTurbulence` 噪声、模糊等)会显示未经过滤的形状——常见情况是一个
|
|
63
|
+
纯色矩形——而不是滤镜结果。需要这些能力的矢量美术必须预先光栅化、用盒子绘制,
|
|
64
|
+
或限制在受支持的语法内。参见[媒体](./media.md#图像m6a)。
|
|
62
65
|
- **每次变更仍是整文档重算。** 一次 DOM 变更会重新计算层叠并重新布局整个文档
|
|
63
66
|
(没有增量子树失效),只影响绘制的变更(例如 hover 颜色)同样会重排。带生成
|
|
64
67
|
内容(`::before`/`::after`)的文档没有特殊处理。各阶段的实测开销由
|
package/doc/zh-CN/gui/media.md
CHANGED
|
@@ -35,6 +35,13 @@ PNG/JPEG/BMP/GIF/TGA 解码为 RGBA8,并带有解码缓存与仅头文件的
|
|
|
35
35
|
(最近最少使用),动画文档不会让它无限增长。
|
|
36
36
|
- 解析结果按路径缓存;解析与光栅化器状态在 `xt_svg_shutdown`
|
|
37
37
|
(`xt_image_shutdown`)中释放。
|
|
38
|
+
- `data:` URL 在内存中解码,凡接受路径的地方都可使用:`<img src>`、CSS
|
|
39
|
+
`background-image` 与 `canvas.drawImage(url, …)`。既支持 `;base64` 形式,也支持
|
|
40
|
+
百分号编码(或原样)的载荷,`base64` 标志大小写不敏感。MIME 类型只用于分类:
|
|
41
|
+
`image/svg+xml`(或 MIME 为通用/缺失时按内容嗅探出 SVG)走 NanoSVG——data URL
|
|
42
|
+
里的 SVG 与 SVG 文件一样按绘制尺寸光栅化;PNG/JPEG/BMP/GIF/TGA 走
|
|
43
|
+
`stb_image`。NanoSVG 的子集限制仍然生效:只靠 `<filter>`(`feTurbulence` 等)
|
|
44
|
+
出图的 SVG 能解码、能布局,但滤镜本身会被忽略。
|
|
38
45
|
|
|
39
46
|
文档 CSS 只作用于 `<svg>` **盒子**——尺寸、外边距、背景、边框、`transform`、
|
|
40
47
|
opacity、`display`——与任何其他元素一样。盒子内部的艺术内容保持 SVG 语义:
|
|
@@ -45,8 +52,8 @@ opacity、`display`——与任何其他元素一样。盒子内部的艺术内
|
|
|
45
52
|
重写艺术内容的程序会为每个变体保留一张纹理;更好的做法是用 `transform`、
|
|
46
53
|
`opacity` 给盒子做动画,或在一小组文档之间切换。
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
图像仍需完成:`object-fit`、九宫格边框。(CSS `background-image: url(...)` 与
|
|
56
|
+
`data:` URL 已支持。)
|
|
50
57
|
|
|
51
58
|
两种 SVG 都受限于 NanoSVG 的子集:`path`、`rect`、`circle`、`ellipse`、
|
|
52
59
|
`line`、`polyline`、`polygon`、组(`g`)、带线性/径向渐变的 `defs` 以及基于
|
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
顺序都能保留到渲染器。`DisplayList::dump()` 为 `paintList()`/`paintCount()`
|
|
16
16
|
提供数据。
|
|
17
17
|
|
|
18
|
-
`opacity`
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
`opacity` 会乘到该盒子及其后代绘制的每个颜色 alpha 上(沿绘制链逐层相乘),
|
|
19
|
+
因此小数 opacity 以及 CSS 解析器接受的 `opacity` 过渡都能看到效果。这是按图形
|
|
20
|
+
逐个近似的,而不是离屏分组:半透明盒子的重叠子元素会各自合成,浏览器则先把整组
|
|
21
|
+
合成一次再应用 alpha。`opacity: 0` 仍会跳过整棵子树。把隐藏态写成
|
|
21
22
|
`opacity: 0` 来隐藏/显示一层,正是 `examples/gui/pelican-bike` 游戏结束遮罩的做法。
|
|
22
23
|
|
|
23
24
|
`runtime/ext_gui/renderer.{h,cpp}` 把该列表转换为每个窗口的批处理顶点缓冲
|
|
@@ -67,14 +68,20 @@
|
|
|
67
68
|
`background-color` 与每一条 `background-image` 层都会绘制。逗号分隔的层列表按
|
|
68
69
|
从底到上的顺序绘制(声明中的第一层在最上面),两种渐变均受支持:
|
|
69
70
|
|
|
70
|
-
- **线性**渐变在 18
|
|
71
|
-
`deg`/`grad`/`rad`/`turn
|
|
71
|
+
- **线性**渐变在 18 个位置采样(重复渐变 64 个)并输出为纯色矩形;支持
|
|
72
|
+
`to <side>`、`deg`/`grad`/`rad`/`turn`、百分比或长度(`px`/`em`/…)偏移、
|
|
73
|
+
`color <start> <end>` 简写与多色标(色带沿最近轴行走,因此斜角会退化为
|
|
72
74
|
最近的轴向);
|
|
73
|
-
- **径向**渐变是 32
|
|
74
|
-
transform
|
|
75
|
-
|
|
75
|
+
- **径向**渐变是 32 个同心椭圆(重复渐变 64 个)。每个都是单位半径的圆角矩形,
|
|
76
|
+
经图形管线的 transform 槽映射,因此圆/椭圆仍保留距离场抗锯齿。支持形状/尺寸
|
|
77
|
+
关键字(`circle`/`ellipse`、`closest-side`/`farthest-side`/`closest-corner`/
|
|
76
78
|
`farthest-corner`、显式半径)与 `at <position>`。
|
|
77
79
|
|
|
80
|
+
`repeating-linear-gradient()` 与 `repeating-radial-gradient()` 会在整个盒子上
|
|
81
|
+
从 0 平铺到最后一个色标的偏移量;色标列表不会被强制结束在 100%,因此
|
|
82
|
+
`red 0%, blue 10%` 每 10% 重复一次。普通渐变的最后一个色标若小于 100%,则剩余
|
|
83
|
+
区域保持该颜色,与 CSS 一致。
|
|
84
|
+
|
|
78
85
|
背景色位于所有层下方。若渐变色标带透明度,渐变也会半透明;色带近似可能产生
|
|
79
86
|
轻微条带,非常透明的多色标渐变会出现 alpha 叠加。
|
|
80
87
|
|
|
@@ -96,9 +103,21 @@ shader 改动,但边缘是硬边(没有解析抗锯齿)。支持两层嵌
|
|
|
96
103
|
**`filter`** 的颜色函数(`brightness`、`contrast`、`grayscale`、`sepia`、
|
|
97
104
|
`invert`、`saturate`、`hue-rotate`、`opacity`)会折进每个绘制颜色,因此盒子与
|
|
98
105
|
其后代都会看到滤镜。`blur()` 与 `drop-shadow()` 是显示列表层面的近似:模糊把每个
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
图形展开成九个半透明副本,投影用盒子/文字轮廓。这样普通绘制保持单渲染通道,代价
|
|
107
|
+
是组语义:被模糊/滤镜的组内重叠后代可能加深;`backdrop-filter` 无法采样元素背后
|
|
108
|
+
的内容,因此会解析并参与层叠,但不绘制。
|
|
109
|
+
|
|
110
|
+
**`mix-blend-mode`** 是唯一真正需要离屏图层的例外。非 `normal` 的盒子会被
|
|
111
|
+
`LayerBegin`/`LayerEnd` 标记包住(在 `paintList()` 中显示为 `layer blend=…` /
|
|
112
|
+
`end-layer`),渲染器把该组绘制到全视口离屏纹理,再用一个小型 GLSL 混合
|
|
113
|
+
shader 合成到场景上。全部十六种 CSS 模式(`multiply`、`screen`、`overlay`、
|
|
114
|
+
`darken`、`lighten`、`color-dodge`、`color-burn`、`hard-light`、`soft-light`、
|
|
115
|
+
`difference`、`exclusion`、`hue`、`saturation`、`color`、`luminosity`)都按
|
|
116
|
+
合成规范公式实现。shader 在运行时由内置 glslang 编译(Vulkan 用 SPIR-V),
|
|
117
|
+
再由 SPIRV-Cross 交叉编译为 MSL(Metal);每个嵌套层级的两张场景纹理跨帧复用,
|
|
118
|
+
并通过 ping-pong 让背景在写入时仍可采样。既不吃 SPIR-V 也不吃 MSL 的后端
|
|
119
|
+
(这里的 D3D12/DXIL 路径没有 shader 编译器)仍用普通 source-over 混合绘制该组。
|
|
120
|
+
`backdrop-filter` 依旧不绘制。
|
|
102
121
|
|
|
103
122
|
## 文本技术栈(M4b)
|
|
104
123
|
|
package/doc/zh-CN/gui.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
渲染器,因此盒子(以及其中的文本、图像与子元素)可以被平移、缩放与旋转而
|
|
13
13
|
不触发任何重排——再加上 `transition: transform`,这些姿态可以动画(自绘的示例
|
|
14
14
|
`examples/gui/pelican-svg` 正是用它让矢量画动起来)。参见
|
|
15
|
-
[布局](./gui/layout.md#变换m15)与[动画](./gui/animation.md#css-transformm15)。**M17** 增加了真正的 **WebGL 1.0**,**M18** 增加了 **WebGL 2.0**:`<canvas>.getContext("webgl2")` 接受 `#version 300 es` 着色器、顶点数组对象、实例化绘制与采样器对象,全部由内置 glslang 编译为 SPIR-V(Metal 上再由内置 SPIRV-Cross 转成 MSL)并通过 SDL_GPU 渲染,几何、光照、纹理采样与混合都在 GPU 上完成,页面直接合成颜色纹理而无须读回——见 [WebGL](./gui/webgl.md)。**M19** 补齐 CSS 装饰层:`@media` 查询、生成内容(`::before`/`::after
|
|
15
|
+
[布局](./gui/layout.md#变换m15)与[动画](./gui/animation.md#css-transformm15)。**M17** 增加了真正的 **WebGL 1.0**,**M18** 增加了 **WebGL 2.0**:`<canvas>.getContext("webgl2")` 接受 `#version 300 es` 着色器、顶点数组对象、实例化绘制与采样器对象,全部由内置 glslang 编译为 SPIR-V(Metal 上再由内置 SPIRV-Cross 转成 MSL)并通过 SDL_GPU 渲染,几何、光照、纹理采样与混合都在 GPU 上完成,页面直接合成颜色纹理而无须读回——见 [WebGL](./gui/webgl.md)。**M19** 补齐 CSS 装饰层:`@media` 查询、生成内容(`::before`/`::after`)、线性/径向/重复多层背景、`text-shadow`、`clip-path`、`mix-blend-mode`(通过离屏合成器)与 `filter` 颜色/阴影效果。`backdrop-filter` 仍需要背景快照,真正的组模糊也仍是近似——见[绘制](./gui/rendering.md#背景生成内容与特效m19)。**M20** 让大文档保持可交互:`@media` 每条规则只求值一次,布局与绘制共享一个有界的整形缓存,一帧只做(也只提交)变更所要求的工作——见[性能](./gui/rendering.md#性能m20)。本总览记录了锁定决策、架构、里程碑计划与当前进度;引擎本身按功能拆分在以上各页中。
|
|
16
16
|
|
|
17
17
|
## 目录
|
|
18
18
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xbintsc",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.42",
|
|
4
4
|
"description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -72,12 +72,12 @@
|
|
|
72
72
|
"vitest": "^2.1.8"
|
|
73
73
|
},
|
|
74
74
|
"optionalDependencies": {
|
|
75
|
-
"@xbintsc/linux-x64": "0.5.
|
|
76
|
-
"@xbintsc/linux-arm64": "0.5.
|
|
77
|
-
"@xbintsc/darwin-x64": "0.5.
|
|
78
|
-
"@xbintsc/darwin-arm64": "0.5.
|
|
79
|
-
"@xbintsc/win32-x64": "0.5.
|
|
80
|
-
"@xbintsc/win32-arm64": "0.5.
|
|
75
|
+
"@xbintsc/linux-x64": "0.5.42",
|
|
76
|
+
"@xbintsc/linux-arm64": "0.5.42",
|
|
77
|
+
"@xbintsc/darwin-x64": "0.5.42",
|
|
78
|
+
"@xbintsc/darwin-arm64": "0.5.42",
|
|
79
|
+
"@xbintsc/win32-x64": "0.5.42",
|
|
80
|
+
"@xbintsc/win32-arm64": "0.5.42"
|
|
81
81
|
},
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=22"
|
|
@@ -468,41 +468,59 @@ METHOD(canvas_draw_image) {
|
|
|
468
468
|
(void)env;
|
|
469
469
|
xtgui::Canvas2D *canvas = context_canvas(self);
|
|
470
470
|
if (canvas == nullptr || argc < 3) return XT_UNDEFINED;
|
|
471
|
+
|
|
472
|
+
const bool has_destination = argc >= 5;
|
|
473
|
+
const float dx = (float)number_arg(argc, argv, 1, 0.0);
|
|
474
|
+
const float dy = (float)number_arg(argc, argv, 2, 0.0);
|
|
475
|
+
const float destination_width =
|
|
476
|
+
has_destination ? (float)number_arg(argc, argv, 3, 0.0) : 0.0f;
|
|
477
|
+
const float destination_height =
|
|
478
|
+
has_destination ? (float)number_arg(argc, argv, 4, 0.0) : 0.0f;
|
|
479
|
+
auto raster_target = [](float value) -> int {
|
|
480
|
+
return value > 0.0f ? (int)(value + 0.5f) : 0;
|
|
481
|
+
};
|
|
482
|
+
|
|
471
483
|
XtGuiWindow *win = nullptr;
|
|
472
484
|
xt_value source_value = xt_arg(argc, argv, 0);
|
|
473
485
|
const xtgui::Image *image = nullptr;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
486
|
+
if (XT_IS_STRING(source_value)) {
|
|
487
|
+
/* Engine extension: the source may be a path or a `data:` URL itself, so a
|
|
488
|
+
* data-URL bitmap/SVG can be drawn without an `<img>` element. */
|
|
489
|
+
const std::string source_url(xt_string_data(source_value));
|
|
490
|
+
image = xtgui::xt_image_load(source_url, raster_target(destination_width),
|
|
491
|
+
raster_target(destination_height));
|
|
492
|
+
} else {
|
|
493
|
+
/* An `OffscreenCanvas` (or a transferred one) has no DOM node; its bitmap
|
|
494
|
+
* is reachable through the process-wide canvas id. */
|
|
495
|
+
if (XT_IS_OBJECT(source_value)) {
|
|
496
|
+
xt_value canvas_id = xt_object_get_cstr(source_value, "__xt_gui_canvas");
|
|
497
|
+
if (XT_IS_NUMBER(canvas_id)) {
|
|
498
|
+
xtgui::Canvas2D *offscreen = xtgui::xt_canvas_by_id((uint64_t)xt_to_number(canvas_id));
|
|
499
|
+
if (offscreen != nullptr) image = &offscreen->image();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (image == nullptr) {
|
|
503
|
+
xtgui::Node *source = resolve_node(source_value, &win);
|
|
504
|
+
if (source != nullptr && source->isElement() && source->isTag("img")) {
|
|
505
|
+
const std::string *src = source->attr("src");
|
|
506
|
+
if (src != nullptr) {
|
|
507
|
+
image = xtgui::xt_image_load(*src, raster_target(destination_width),
|
|
508
|
+
raster_target(destination_height));
|
|
509
|
+
}
|
|
510
|
+
} else if (source != nullptr) {
|
|
511
|
+
xtgui::Canvas2D *other = xt_gui_canvas_find(win, source);
|
|
512
|
+
if (other != nullptr) image = &other->image();
|
|
482
513
|
}
|
|
483
514
|
}
|
|
484
|
-
}
|
|
485
|
-
xtgui::Node *source = resolve_node(source_value, &win);
|
|
486
|
-
if (source != nullptr && source->isElement() && source->isTag("img")) {
|
|
487
|
-
const std::string *src = source->attr("src");
|
|
488
|
-
if (src != nullptr) image = xtgui::xt_image_load(*src);
|
|
489
|
-
} else if (source != nullptr) {
|
|
490
|
-
xtgui::Canvas2D *other = xt_gui_canvas_find(win, source);
|
|
491
|
-
if (other != nullptr) image = &other->image();
|
|
492
515
|
}
|
|
493
516
|
if (image == nullptr || image->width <= 0 || image->height <= 0) return XT_UNDEFINED;
|
|
494
|
-
|
|
495
|
-
float
|
|
496
|
-
float
|
|
497
|
-
float dh = (float)image->height;
|
|
517
|
+
|
|
518
|
+
const float dw = has_destination ? destination_width : (float)image->width;
|
|
519
|
+
const float dh = has_destination ? destination_height : (float)image->height;
|
|
498
520
|
float sx = 0.0f;
|
|
499
521
|
float sy = 0.0f;
|
|
500
522
|
float sw = (float)image->width;
|
|
501
523
|
float sh = (float)image->height;
|
|
502
|
-
if (argc >= 5) {
|
|
503
|
-
dw = (float)number_arg(argc, argv, 3, (double)dw);
|
|
504
|
-
dh = (float)number_arg(argc, argv, 4, (double)dh);
|
|
505
|
-
}
|
|
506
524
|
if (argc >= 9) {
|
|
507
525
|
sx = (float)number_arg(argc, argv, 5, 0.0);
|
|
508
526
|
sy = (float)number_arg(argc, argv, 6, 0.0);
|
|
@@ -134,67 +134,59 @@ bool parseGradientColor(const std::string &token, Color *color, std::string *res
|
|
|
134
134
|
return true;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
/** Parse one `
|
|
138
|
-
|
|
137
|
+
/** Parse one gradient stop offset: a percentage, a length (`px`, `em`, …) or a
|
|
138
|
+
* bare number (0.5 = 50%, 50 = 50%). */
|
|
139
|
+
bool parseGradientOffset(const std::string &raw, Length *out) {
|
|
139
140
|
std::string text = xt_css_trim(raw);
|
|
140
141
|
if (text.empty()) return false;
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
if (text.back() == '%') {
|
|
143
|
+
float value = 0;
|
|
144
|
+
if (!xt_css_parse_number(text.substr(0, text.size() - 1), &value)) return false;
|
|
145
|
+
*out = Length{value, Unit::Percent};
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
143
148
|
float value = 0;
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
if (xt_css_parse_number(text, &value)) {
|
|
150
|
+
/* Unitless offsets are fractions of the gradient line, matching the old
|
|
151
|
+
* parser: `0.5` is 50% and `50` is also 50%. */
|
|
152
|
+
if (value > 1.0f) *out = Length{value, Unit::Percent};
|
|
153
|
+
else *out = Length{value * 100.0f, Unit::Percent};
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
Length length = xt_css_parse_length(text);
|
|
157
|
+
if (length.unit != Unit::Invalid && length.unit != Unit::Auto &&
|
|
158
|
+
length.unit != Unit::None && length.unit != Unit::Number) {
|
|
159
|
+
*out = length;
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
151
163
|
}
|
|
152
164
|
|
|
153
|
-
/** Parse a comma-separated `[color [offset [offset]]]` stop list.
|
|
165
|
+
/** Parse a comma-separated `[color [offset [offset]]]` stop list. Offsets stay
|
|
166
|
+
* unresolved here: `px` is resolved against the gradient line in paint. */
|
|
154
167
|
bool parseStops(const std::string &text, std::vector<BackgroundGradientStop> *stops) {
|
|
155
168
|
std::vector<std::string> parts = splitCommas(text);
|
|
156
169
|
if (parts.size() < 2) return false;
|
|
157
|
-
float last = -1.0f;
|
|
158
170
|
for (const std::string &part : parts) {
|
|
159
171
|
Color color;
|
|
160
172
|
std::string rest;
|
|
161
173
|
if (!parseGradientColor(part, &color, &rest)) continue;
|
|
162
174
|
BackgroundGradientStop stop;
|
|
163
175
|
stop.color = color;
|
|
164
|
-
|
|
165
|
-
if (!
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
last = stop.offset;
|
|
173
|
-
}
|
|
176
|
+
std::vector<std::string> offsets = splitTokens(rest);
|
|
177
|
+
if (!offsets.empty() && parseGradientOffset(offsets[0], &stop.offset)) {
|
|
178
|
+
/* `color <start> <end>` is shorthand for the same colour at both offsets
|
|
179
|
+
* (a hard band), so emit the second stop too. */
|
|
180
|
+
stops->push_back(stop);
|
|
181
|
+
if (offsets.size() > 1) {
|
|
182
|
+
BackgroundGradientStop end = stop;
|
|
183
|
+
if (parseGradientOffset(offsets[1], &end.offset)) stops->push_back(end);
|
|
174
184
|
}
|
|
185
|
+
} else {
|
|
186
|
+
stops->push_back(stop);
|
|
175
187
|
}
|
|
176
|
-
stops->push_back(stop);
|
|
177
|
-
}
|
|
178
|
-
if (stops->size() < 2) return false;
|
|
179
|
-
/* Fill implicit offsets by distributing them between their neighbours. */
|
|
180
|
-
size_t i = 0;
|
|
181
|
-
while (i < stops->size()) {
|
|
182
|
-
if ((*stops)[i].offset >= 0) {
|
|
183
|
-
i++;
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
size_t j = i;
|
|
187
|
-
while (j < stops->size() && (*stops)[j].offset < 0) j++;
|
|
188
|
-
float before = (i == 0) ? 0.0f : (*stops)[i - 1].offset;
|
|
189
|
-
float after = (j >= stops->size()) ? 1.0f : (*stops)[j].offset;
|
|
190
|
-
size_t count = j - i;
|
|
191
|
-
for (size_t k = 0; k < count; k++)
|
|
192
|
-
(*stops)[i + k].offset = before + (after - before) * (float)(k + 1) / (float)(count + 1);
|
|
193
|
-
i = j;
|
|
194
188
|
}
|
|
195
|
-
stops->
|
|
196
|
-
stops->back().offset = 1.0f;
|
|
197
|
-
return true;
|
|
189
|
+
return stops->size() >= 2;
|
|
198
190
|
}
|
|
199
191
|
|
|
200
192
|
/** CSS gradient direction/angle. Returns true when `text` was a direction. */
|
|
@@ -395,14 +387,18 @@ bool parseRadialGradient(const std::string &body, BackgroundLayer *layer) {
|
|
|
395
387
|
/** Parse one layer, appending it when it is a supported gradient or image. */
|
|
396
388
|
bool parseLayer(const std::string &part, BackgroundLayer *layer) {
|
|
397
389
|
std::string body;
|
|
398
|
-
|
|
399
|
-
|
|
390
|
+
/* The repeating prefix is checked first: a plain substring search for
|
|
391
|
+
* `linear-gradient(` would also match inside `repeating-linear-gradient(`. */
|
|
392
|
+
if (findCall(part, "repeating-linear-gradient", &body)) {
|
|
393
|
+
layer->repeating = true;
|
|
400
394
|
return parseLinearGradient(body, layer);
|
|
401
395
|
}
|
|
402
|
-
if (findCall(part, "
|
|
403
|
-
|
|
396
|
+
if (findCall(part, "linear-gradient", &body)) return parseLinearGradient(body, layer);
|
|
397
|
+
if (findCall(part, "repeating-radial-gradient", &body)) {
|
|
398
|
+
layer->repeating = true;
|
|
404
399
|
return parseRadialGradient(body, layer);
|
|
405
400
|
}
|
|
401
|
+
if (findCall(part, "radial-gradient", &body)) return parseRadialGradient(body, layer);
|
|
406
402
|
if (findCall(part, "url", &body)) {
|
|
407
403
|
std::string source = xt_css_trim(body);
|
|
408
404
|
if (source.size() >= 2 && (source.front() == '"' || source.front() == '\'') &&
|
|
@@ -471,23 +471,26 @@ void xt_gui_render_window(XtGuiWindow *win) {
|
|
|
471
471
|
}
|
|
472
472
|
win->present_deferred = 0;
|
|
473
473
|
xtgui::g_perf_frame.presents += 1;
|
|
474
|
-
SDL_GPUColorTargetInfo target;
|
|
475
|
-
memset(&target, 0, sizeof(target));
|
|
476
|
-
target.texture = swapchain;
|
|
477
|
-
target.clear_color.r = win->background[0];
|
|
478
|
-
target.clear_color.g = win->background[1];
|
|
479
|
-
target.clear_color.b = win->background[2];
|
|
480
|
-
target.clear_color.a = win->background[3];
|
|
481
|
-
target.load_op = SDL_GPU_LOADOP_CLEAR;
|
|
482
|
-
target.store_op = SDL_GPU_STOREOP_STORE;
|
|
483
|
-
SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(cmd, &target, 1, NULL);
|
|
484
474
|
if (can_paint) {
|
|
485
475
|
float density = SDL_GetWindowPixelDensity(win->window);
|
|
486
476
|
if (density <= 0.0f) density = 1.0f;
|
|
487
|
-
xt_gui_renderer_draw(cmd,
|
|
488
|
-
&win->
|
|
477
|
+
xt_gui_renderer_draw(cmd, swapchain, win->background, &win->geometry,
|
|
478
|
+
&win->text_geometry, &win->image_geometry,
|
|
479
|
+
win->display_list, viewport_width, viewport_height, density);
|
|
480
|
+
} else {
|
|
481
|
+
/* No renderer (no usable shader backend): still clear the window. */
|
|
482
|
+
SDL_GPUColorTargetInfo target;
|
|
483
|
+
memset(&target, 0, sizeof(target));
|
|
484
|
+
target.texture = swapchain;
|
|
485
|
+
target.clear_color.r = win->background[0];
|
|
486
|
+
target.clear_color.g = win->background[1];
|
|
487
|
+
target.clear_color.b = win->background[2];
|
|
488
|
+
target.clear_color.a = win->background[3];
|
|
489
|
+
target.load_op = SDL_GPU_LOADOP_CLEAR;
|
|
490
|
+
target.store_op = SDL_GPU_STOREOP_STORE;
|
|
491
|
+
SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(cmd, &target, 1, NULL);
|
|
492
|
+
if (pass != NULL) SDL_EndGPURenderPass(pass);
|
|
489
493
|
}
|
|
490
|
-
SDL_EndGPURenderPass(pass);
|
|
491
494
|
firstFrame = !win->ready;
|
|
492
495
|
win->ready = 1;
|
|
493
496
|
} else {
|