xbintsc 0.5.42 → 0.5.45
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-audio.test.js +22 -0
- package/dist/tests/e2e/gui-audio.test.js.map +1 -1
- package/dist/tests/e2e/gui-browser.test.js +3 -0
- package/dist/tests/e2e/gui-browser.test.js.map +1 -1
- package/dist/tests/e2e/gui-effects.test.js +48 -4
- package/dist/tests/e2e/gui-effects.test.js.map +1 -1
- package/dist/tests/e2e/gui-events.test.js +67 -0
- package/dist/tests/e2e/gui-events.test.js.map +1 -1
- package/doc/ai/extensions.md +5 -3
- package/doc/ai/zh-CN/extensions.md +4 -3
- package/doc/gui/api.md +13 -5
- package/doc/gui/input.md +7 -0
- package/doc/gui/known-issues.md +5 -3
- package/doc/gui/rendering.md +12 -8
- package/doc/gui-scripts.md +2 -1
- package/doc/gui.md +7 -4
- package/doc/zh-CN/gui/api.md +12 -5
- package/doc/zh-CN/gui/input.md +5 -0
- package/doc/zh-CN/gui/known-issues.md +3 -2
- package/doc/zh-CN/gui/rendering.md +9 -6
- package/doc/zh-CN/gui-scripts.md +2 -2
- package/doc/zh-CN/gui.md +6 -5
- package/package.json +7 -7
- package/runtime/ext_gui/audio.cpp +18 -0
- package/runtime/ext_gui/audio_device.cpp +12 -2
- package/runtime/ext_gui/browser.cpp +2 -2
- package/runtime/ext_gui/dom_api_events.cpp +2 -1
- package/runtime/ext_gui/gui.cpp +7 -0
- package/runtime/ext_gui/gui_engine.h +9 -0
- package/runtime/ext_gui/gui_events.cpp +52 -10
- package/runtime/ext_gui/gui_keymap.cpp +254 -0
- package/runtime/ext_gui/gui_keymap.h +31 -0
- package/runtime/ext_gui/paint.cpp +16 -1
- package/runtime/ext_gui/paint.h +46 -0
- package/runtime/ext_gui/paint_background.cpp +139 -64
- package/runtime/ext_gui/renderer_gradient.cpp +156 -0
- package/runtime/ext_gui/renderer_image.cpp +6 -1
- package/runtime/ext_gui/renderer_internal.h +22 -0
- package/runtime/ext_gui/window.cpp +10 -1
- package/src/driver/version.ts +1 -1
|
@@ -72,18 +72,21 @@
|
|
|
72
72
|
`to <side>`、`deg`/`grad`/`rad`/`turn`、百分比或长度(`px`/`em`/…)偏移、
|
|
73
73
|
`color <start> <end>` 简写与多色标(色带沿最近轴行走,因此斜角会退化为
|
|
74
74
|
最近的轴向);
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
- **径向**渐变由 `renderer_gradient.cpp` 逐像素解算成一张 RGBA 纹理,作为单个
|
|
76
|
+
图像四边形绘制(`PaintGradient`)。若沿用嵌套色带,由于每条色带都会覆盖整个
|
|
77
|
+
内部区域,alpha 会叠加多次——淡出到 `transparent` 的暗角会把整个场景涂成实心
|
|
78
|
+
黑盘。支持形状/尺寸关键字(`circle`/`ellipse`、`closest-side`/`farthest-side`/
|
|
79
|
+
`closest-corner`/`farthest-corner`、显式半径)与 `at <position>`;关键字尺寸
|
|
80
|
+
的椭圆保持盒子的宽高比并缩放到刚好接触对应边或角,与 CSS 一致。色标在预乘
|
|
81
|
+
alpha 空间中插值,最后一个色标会延伸覆盖结束形状之外的剩余盒子。
|
|
79
82
|
|
|
80
83
|
`repeating-linear-gradient()` 与 `repeating-radial-gradient()` 会在整个盒子上
|
|
81
84
|
从 0 平铺到最后一个色标的偏移量;色标列表不会被强制结束在 100%,因此
|
|
82
85
|
`red 0%, blue 10%` 每 10% 重复一次。普通渐变的最后一个色标若小于 100%,则剩余
|
|
83
86
|
区域保持该颜色,与 CSS 一致。
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
背景色位于所有层下方。若渐变色标带透明度,渐变也会半透明;径向渐变逐像素精确,
|
|
89
|
+
线性渐变仍采样 18 条色带,较大的柔和渐变可能出现条带。
|
|
87
90
|
|
|
88
91
|
**生成内容**不需要新的显示列表类型:`::before`/`::after` 是 `style.cpp` 中
|
|
89
92
|
每次重算样式时重建的普通布局盒,因此其背景、边框、文本与 transform 都走同一条
|
package/doc/zh-CN/gui-scripts.md
CHANGED
|
@@ -314,8 +314,8 @@ Event 的 `target` 是句柄,其描述符(`toString`)与同一 `div#id.cla
|
|
|
314
314
|
导入的资产会)。如有需要,M9d 会处理。
|
|
315
315
|
- **外部 `<script src>` 文件之间的 import 绑定冲突。** 会被检测并报告
|
|
316
316
|
(给 import 起别名);之后可以做到带引用重写的按脚本重命名。
|
|
317
|
-
- **资产。** `<script src>` 相对 HTML 资产解析;URL `src`
|
|
318
|
-
fetch
|
|
317
|
+
- **资产。** `<script src>` 相对 HTML 资产解析;URL `src` 值被忽略。
|
|
318
|
+
加载器不会请求网络 URL;运行时全局 `fetch` 是另一套 API。
|
|
319
319
|
|
|
320
320
|
## 测试
|
|
321
321
|
|
package/doc/zh-CN/gui.md
CHANGED
|
@@ -262,8 +262,8 @@ FFmpeg `7.1`(供可选的 `media` 归档使用;可用 `SDL3_TAG` /
|
|
|
262
262
|
特性);`matchMedia()` 使用同一个求值器。
|
|
263
263
|
- 生成内容:`::before`/`::after` 选择器、继承的伪样式、`content` 字符串/
|
|
264
264
|
转义/`attr()`,以及不会泄漏到 DOM 遍历的合成布局盒。
|
|
265
|
-
- 多层 `background-image
|
|
266
|
-
|
|
265
|
+
- 多层 `background-image`:线性渐变自下而上按纯色带绘制(18 条),径向渐变
|
|
266
|
+
逐像素光栅化成一张图像纹理(`renderer_gradient.cpp`)。
|
|
267
267
|
- `text-shadow` 作为阴影文本 run,并带九次采样模糊近似。
|
|
268
268
|
- `clip-path` 多边形(`inset`/`circle`/`ellipse`/`polygon`)解析到设备空间,
|
|
269
269
|
用耳切法三角化,并在 `renderer_clip.cpp` 中与图形几何求交——不需要模板/
|
|
@@ -408,9 +408,10 @@ FFmpeg `7.1`(供可选的 `media` 归档使用;可用 `SDL3_TAG` /
|
|
|
408
408
|
识别 `::before`/`::after`。`style.cpp` 把伪样式算进每次重算样式时重建的
|
|
409
409
|
arena、解析 `content`,布局在真实子元素前后插入它们的盒子。
|
|
410
410
|
`gradient_values.cpp` 建模背景层列表(线性/径向、形状/尺寸/位置/色标),
|
|
411
|
-
`paint_background.cpp`
|
|
412
|
-
|
|
413
|
-
|
|
411
|
+
`paint_background.cpp` 解算并绘制它(径向层变成一个 `PaintGradient`,由
|
|
412
|
+
`renderer_gradient.cpp` 光栅化成纹理),`paint_effects.cpp` 应用滤镜颜色函数
|
|
413
|
+
与九次采样模糊/投影并生成 `text-shadow` run,`paint_clip.cpp`/
|
|
414
|
+
`renderer_clip.cpp` 对三角化的 `clip-path` 多边形裁剪几何。e2e 见
|
|
414
415
|
`tests/e2e/gui-effects.test.ts`。
|
|
415
416
|
- **M20** ✅ 渲染性能。`@media` 每次重算样式时每条规则只求值一次;没有
|
|
416
417
|
`::before`/`::after` 的样式表完全跳过生成内容;只有样式表包含
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xbintsc",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.45",
|
|
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.45",
|
|
76
|
+
"@xbintsc/linux-arm64": "0.5.45",
|
|
77
|
+
"@xbintsc/darwin-x64": "0.5.45",
|
|
78
|
+
"@xbintsc/darwin-arm64": "0.5.45",
|
|
79
|
+
"@xbintsc/win32-x64": "0.5.45",
|
|
80
|
+
"@xbintsc/win32-arm64": "0.5.45"
|
|
81
81
|
},
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=22"
|
|
@@ -500,6 +500,24 @@ uint64_t audio_context_id(xt_value object) {
|
|
|
500
500
|
|
|
501
501
|
xt_value xt_gui_audio_proto(void) { return audio_proto(); }
|
|
502
502
|
|
|
503
|
+
/** Close every live context before `SDL_Quit`. The mixers hold SDL audio
|
|
504
|
+
* streams whose destructors must not run after the audio subsystem (and its
|
|
505
|
+
* device objects) were torn down; keeping the registry alive until static
|
|
506
|
+
* destruction was a crash on exit for every program that played a sound. */
|
|
507
|
+
void xt_gui_audio_shutdown(void) {
|
|
508
|
+
std::vector<std::shared_ptr<AudioMixer>> mixers;
|
|
509
|
+
{
|
|
510
|
+
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
|
511
|
+
mixers.reserve(g_mixers.size());
|
|
512
|
+
for (const auto &entry : g_mixers) mixers.push_back(entry.second);
|
|
513
|
+
g_mixers.clear();
|
|
514
|
+
}
|
|
515
|
+
for (const std::shared_ptr<AudioMixer> &mixer : mixers) {
|
|
516
|
+
mixer->close_device();
|
|
517
|
+
mixer->shutdown();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
503
521
|
xt_value xt_gui_audio_create(int32_t argc, xt_value *argv) {
|
|
504
522
|
bool offline = false;
|
|
505
523
|
xt_value options = xt_arg(argc, argv, 0);
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
#include <SDL3/SDL.h>
|
|
15
15
|
|
|
16
|
+
#include <cstdio>
|
|
16
17
|
#include <vector>
|
|
17
18
|
|
|
18
19
|
namespace xtgui {
|
|
@@ -39,14 +40,23 @@ bool AudioMixer::start_device() {
|
|
|
39
40
|
if (stream_ != nullptr) return true;
|
|
40
41
|
if (device_tried_) return false;
|
|
41
42
|
device_tried_ = true;
|
|
42
|
-
if (!SDL_InitSubSystem(SDL_INIT_AUDIO))
|
|
43
|
+
if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
|
44
|
+
/* A missing device is not fatal (the page keeps its graph and clock), but
|
|
45
|
+
* silently staying quiet is indistinguishable from a broken mixer. */
|
|
46
|
+
std::fprintf(stderr, "xt_gui: audio subsystem unavailable: %s\n", SDL_GetError());
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
43
49
|
SDL_AudioSpec spec;
|
|
44
50
|
spec.format = SDL_AUDIO_F32;
|
|
45
51
|
spec.channels = kAudioMixChannels;
|
|
46
52
|
spec.freq = kAudioMixRate;
|
|
47
53
|
stream_ = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, stream_callback,
|
|
48
54
|
this);
|
|
49
|
-
if (stream_ == nullptr)
|
|
55
|
+
if (stream_ == nullptr) {
|
|
56
|
+
std::fprintf(stderr, "xt_gui: could not open the default audio device: %s\n",
|
|
57
|
+
SDL_GetError());
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
50
60
|
SDL_ResumeAudioStreamDevice(stream_);
|
|
51
61
|
return true;
|
|
52
62
|
}
|
|
@@ -393,7 +393,7 @@ xt_value host_type_construct(xt_value self, xt_value env, int32_t argc, xt_value
|
|
|
393
393
|
/** Host constructors are window members too — a page writes
|
|
394
394
|
* `new window.AudioContext()` as often as `new AudioContext()`. */
|
|
395
395
|
void install_window_globals(xt_value win) {
|
|
396
|
-
if (xt_seq(win, g_installed_window)) return;
|
|
396
|
+
if (xt_truthy(xt_seq(win, g_installed_window))) return;
|
|
397
397
|
g_installed_window = win;
|
|
398
398
|
xt_gc_add_root(&g_installed_window);
|
|
399
399
|
if (XT_IS_OBJECT(g_navigator)) set_prop(win, "navigator", g_navigator);
|
|
@@ -479,7 +479,7 @@ xt_value xt_browser_document(int32_t argc, xt_value *argv) {
|
|
|
479
479
|
xt_value win = xt_browser_window(0, NULL);
|
|
480
480
|
if (XT_IS_UNDEFINED(win)) return XT_UNDEFINED;
|
|
481
481
|
xt_value doc = xt_gui_document_handle(xt_gui_window_from_this(win));
|
|
482
|
-
if (!xt_seq(doc, g_document_global)) {
|
|
482
|
+
if (!xt_truthy(xt_seq(doc, g_document_global))) {
|
|
483
483
|
g_document_global = doc;
|
|
484
484
|
xt_gc_add_root(&g_document_global);
|
|
485
485
|
}
|
|
@@ -28,7 +28,8 @@ static xt_value make_dom_event(XtGuiWindow *win, const char *type, const xtgui::
|
|
|
28
28
|
static const char *const fields[] = {
|
|
29
29
|
"x", "y", "clientX", "clientY", "button", "clicks",
|
|
30
30
|
"deltaX", "deltaY", "key", "code", "repeat", "ctrl",
|
|
31
|
-
"shift", "alt", "meta",
|
|
31
|
+
"shift", "alt", "meta", "ctrlKey", "shiftKey", "altKey",
|
|
32
|
+
"metaKey",
|
|
32
33
|
};
|
|
33
34
|
for (const char *field : fields) {
|
|
34
35
|
xt_value value = xt_object_get_cstr(legacy, field);
|
package/runtime/ext_gui/gui.cpp
CHANGED
|
@@ -460,6 +460,10 @@ extern "C" xt_value xt_gui_run(int32_t argc, xt_value *argv) {
|
|
|
460
460
|
SDL_DestroyGPUDevice(device);
|
|
461
461
|
g_device = NULL;
|
|
462
462
|
}
|
|
463
|
+
/* Audio streams belong to SDL, so close them before quitting the audio
|
|
464
|
+
* subsystem; otherwise a still-live AudioMixer's destructor runs after
|
|
465
|
+
* `SDL_Quit` and touches freed SDL state. */
|
|
466
|
+
xt_gui_audio_shutdown();
|
|
463
467
|
if (g_sdl_initialized) {
|
|
464
468
|
SDL_Quit();
|
|
465
469
|
g_sdl_initialized = 0;
|
|
@@ -478,5 +482,8 @@ extern "C" xt_value xt_gui_quit(int32_t argc, xt_value *argv) {
|
|
|
478
482
|
for (int i = 0; i < g_window_count; i++) {
|
|
479
483
|
if (g_windows[i].open) xt_gui_quit_window(&g_windows[i]);
|
|
480
484
|
}
|
|
485
|
+
/* A browser-style page never enters `xt_gui_run`, so stop its audio here
|
|
486
|
+
* too instead of leaving the device open until process exit. */
|
|
487
|
+
xt_gui_audio_shutdown();
|
|
481
488
|
return XT_UNDEFINED;
|
|
482
489
|
}
|
|
@@ -314,6 +314,15 @@ void xt_gui_dispatch_pointer(XtGuiWindow *win, const char *type, float x, float
|
|
|
314
314
|
void xt_gui_dispatch_wheel(XtGuiWindow *win, float x, float y, float delta_x, float delta_y);
|
|
315
315
|
/** Synthesise a key event (`keydown`/`keyup`). */
|
|
316
316
|
void xt_gui_dispatch_key(XtGuiWindow *win, const char *type, const char *key, const char *code);
|
|
317
|
+
/** Translate one physical key (SDL scancode/keycode + modifiers) to the browser
|
|
318
|
+
* `key`/`code` names and dispatch it to the DOM and window listeners. */
|
|
319
|
+
void xt_gui_dispatch_physical_key(XtGuiWindow *win, const char *type, int scancode, int keycode,
|
|
320
|
+
bool repeat, bool ctrl, bool shift, bool alt, bool meta,
|
|
321
|
+
bool caps_lock);
|
|
322
|
+
/** Close every AudioContext's device and drop its graph. Called before
|
|
323
|
+
* `SDL_Quit` so an AudioMixer's destructor never touches an SDL stream after
|
|
324
|
+
* the audio subsystem was shut down. */
|
|
325
|
+
void xt_gui_audio_shutdown(void);
|
|
317
326
|
|
|
318
327
|
/** The decoder for one media element, opening it (and its `src`) on first use.
|
|
319
328
|
* Returns `nullptr` for a non-media element or an unreadable source. */
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
#include "gui_internal.h"
|
|
7
|
+
#include "gui_keymap.h"
|
|
7
8
|
#include "listener_options.h"
|
|
8
9
|
|
|
9
10
|
#include "paint.h"
|
|
@@ -216,21 +217,64 @@ static xt_value xt_gui_key_payload(const char *type, const char *key, const char
|
|
|
216
217
|
xt_object_set(payload, xt_string_from_cstr("key"), xt_string_from_cstr(key));
|
|
217
218
|
xt_object_set(payload, xt_string_from_cstr("code"), xt_string_from_cstr(code));
|
|
218
219
|
xt_object_set(payload, xt_string_from_cstr("repeat"), repeat ? XT_TRUE : XT_FALSE);
|
|
220
|
+
/* Legacy DOM fields and their current `*Key` spellings: a page may read
|
|
221
|
+
* either, and WheelEvent/MouseEvent already expose both where they apply. */
|
|
219
222
|
xt_object_set(payload, xt_string_from_cstr("ctrl"), ctrl ? XT_TRUE : XT_FALSE);
|
|
220
223
|
xt_object_set(payload, xt_string_from_cstr("shift"), shift ? XT_TRUE : XT_FALSE);
|
|
221
224
|
xt_object_set(payload, xt_string_from_cstr("alt"), alt ? XT_TRUE : XT_FALSE);
|
|
222
225
|
xt_object_set(payload, xt_string_from_cstr("meta"), meta ? XT_TRUE : XT_FALSE);
|
|
226
|
+
xt_object_set(payload, xt_string_from_cstr("ctrlKey"), ctrl ? XT_TRUE : XT_FALSE);
|
|
227
|
+
xt_object_set(payload, xt_string_from_cstr("shiftKey"), shift ? XT_TRUE : XT_FALSE);
|
|
228
|
+
xt_object_set(payload, xt_string_from_cstr("altKey"), alt ? XT_TRUE : XT_FALSE);
|
|
229
|
+
xt_object_set(payload, xt_string_from_cstr("metaKey"), meta ? XT_TRUE : XT_FALSE);
|
|
223
230
|
return payload;
|
|
224
231
|
}
|
|
225
232
|
|
|
226
|
-
|
|
233
|
+
static const xtgui::Node *xt_gui_find_body(const xtgui::Node *node) {
|
|
234
|
+
if (node == nullptr) return nullptr;
|
|
235
|
+
if (node->isElement() && node->tag == "body") return node;
|
|
236
|
+
for (const std::unique_ptr<xtgui::Node> &child : node->children) {
|
|
237
|
+
if (const xtgui::Node *found = xt_gui_find_body(child.get())) return found;
|
|
238
|
+
}
|
|
239
|
+
return nullptr;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** The element a keyboard event targets when nothing is focused: `<body>`,
|
|
243
|
+
* like a browser, or the document root for a page without one. */
|
|
244
|
+
static const xtgui::Node *xt_gui_key_target(XtGuiWindow *win) {
|
|
245
|
+
if (win->document == nullptr) return nullptr;
|
|
246
|
+
if (const xtgui::Node *focused = win->document->focus()) return focused;
|
|
247
|
+
const xtgui::Node *root = win->document->root();
|
|
248
|
+
if (const xtgui::Node *body = xt_gui_find_body(root)) return body;
|
|
249
|
+
return root;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Deliver one fully built keyboard payload to the DOM (bubbling from the
|
|
253
|
+
* focused element / body to `document`) and then to `window` listeners. */
|
|
254
|
+
static void xt_gui_dispatch_key_payload(XtGuiWindow *win, const char *type, xt_value payload) {
|
|
227
255
|
if (win == NULL || !win->open) return;
|
|
228
|
-
|
|
229
|
-
const xtgui::Node *node = win->document != nullptr ? win->document->focus() : nullptr;
|
|
256
|
+
const xtgui::Node *node = xt_gui_key_target(win);
|
|
230
257
|
if (node != nullptr) xt_gui_emit_dom_event(win, type, node, payload);
|
|
231
258
|
xt_gui_emit_payload(win, type, payload);
|
|
232
259
|
}
|
|
233
260
|
|
|
261
|
+
void xt_gui_dispatch_key(XtGuiWindow *win, const char *type, const char *key, const char *code) {
|
|
262
|
+
xt_value payload = xt_gui_key_payload(type, key, code, false, false, false, false, false);
|
|
263
|
+
xt_gui_dispatch_key_payload(win, type, payload);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
void xt_gui_dispatch_physical_key(XtGuiWindow *win, const char *type, int scancode, int keycode,
|
|
267
|
+
bool repeat, bool ctrl, bool shift, bool alt, bool meta,
|
|
268
|
+
bool caps_lock) {
|
|
269
|
+
std::string key;
|
|
270
|
+
std::string code;
|
|
271
|
+
xt_gui_key_names(static_cast<SDL_Scancode>(scancode), static_cast<SDL_Keycode>(keycode), shift,
|
|
272
|
+
caps_lock, &key, &code);
|
|
273
|
+
xt_value payload = xt_gui_key_payload(type, key.c_str(), code.c_str(), repeat, ctrl, shift, alt,
|
|
274
|
+
meta);
|
|
275
|
+
xt_gui_dispatch_key_payload(win, type, payload);
|
|
276
|
+
}
|
|
277
|
+
|
|
234
278
|
namespace guidev {
|
|
235
279
|
|
|
236
280
|
void handle_event(const SDL_Event *event) {
|
|
@@ -306,13 +350,11 @@ void handle_event(const SDL_Event *event) {
|
|
|
306
350
|
XtGuiWindow *win = xt_gui_find_by_window_id(event->key.windowID);
|
|
307
351
|
if (win == NULL || !win->open) return;
|
|
308
352
|
const char *type = event->type == SDL_EVENT_KEY_DOWN ? "keydown" : "keyup";
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
(event->key.mod & SDL_KMOD_ALT) != 0,
|
|
315
|
-
(event->key.mod & SDL_KMOD_GUI) != 0));
|
|
353
|
+
xt_gui_dispatch_physical_key(
|
|
354
|
+
win, type, static_cast<int>(event->key.scancode), static_cast<int>(event->key.key),
|
|
355
|
+
event->key.repeat, (event->key.mod & SDL_KMOD_CTRL) != 0,
|
|
356
|
+
(event->key.mod & SDL_KMOD_SHIFT) != 0, (event->key.mod & SDL_KMOD_ALT) != 0,
|
|
357
|
+
(event->key.mod & SDL_KMOD_GUI) != 0, (event->key.mod & SDL_KMOD_CAPS) != 0);
|
|
316
358
|
return;
|
|
317
359
|
}
|
|
318
360
|
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — browser KeyboardEvent names from SDL key events.
|
|
3
|
+
*
|
|
4
|
+
* `code` is the *physical* key, which is what the DOM's `KeyboardEvent.code`
|
|
5
|
+
* reports and what games normally bind to. `key` is the produced value, so it
|
|
6
|
+
* follows Shift/Caps Lock for letters and Shift for the US punctuation row.
|
|
7
|
+
* The mapping is intentionally the US layout: SDL's key names are US-based,
|
|
8
|
+
* and a page that needs another layout should use `code` anyway.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#include "gui_keymap.h"
|
|
12
|
+
|
|
13
|
+
#include <cstdio>
|
|
14
|
+
|
|
15
|
+
namespace guidev {
|
|
16
|
+
|
|
17
|
+
namespace {
|
|
18
|
+
|
|
19
|
+
void set_names(std::string *key, std::string *code, const char *key_name, const char *code_name) {
|
|
20
|
+
*key = key_name;
|
|
21
|
+
*code = code_name;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Digits and their shifted symbols, indexed 0..9 for keys 1..0. */
|
|
25
|
+
const char *const kDigitKeys = "1234567890";
|
|
26
|
+
const char *const kDigitShifted = "!@#$%^&*()";
|
|
27
|
+
|
|
28
|
+
/** Keypad digits, indexed 0..9 for KP_1..KP_0. */
|
|
29
|
+
const char *const kKeypadDigits = "1234567890";
|
|
30
|
+
|
|
31
|
+
} // namespace
|
|
32
|
+
|
|
33
|
+
void xt_gui_key_names(SDL_Scancode scancode, SDL_Keycode keycode, bool shift, bool caps_lock,
|
|
34
|
+
std::string *key, std::string *code) {
|
|
35
|
+
key->clear();
|
|
36
|
+
code->clear();
|
|
37
|
+
|
|
38
|
+
/* Letters: SDL numbers A..Z consecutively. Caps Lock and Shift cancel out. */
|
|
39
|
+
if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) {
|
|
40
|
+
char letter = static_cast<char>('A' + (scancode - SDL_SCANCODE_A));
|
|
41
|
+
*code = "Key";
|
|
42
|
+
code->push_back(letter);
|
|
43
|
+
bool upper = shift != caps_lock;
|
|
44
|
+
key->push_back(upper ? letter : static_cast<char>(letter + ('a' - 'A')));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Number row. */
|
|
49
|
+
if (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0) {
|
|
50
|
+
int index = static_cast<int>(scancode - SDL_SCANCODE_1);
|
|
51
|
+
*code = "Digit";
|
|
52
|
+
code->push_back(kDigitKeys[index]);
|
|
53
|
+
key->push_back(shift ? kDigitShifted[index] : kDigitKeys[index]);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Function keys: F1..F12 and F13..F24 are two contiguous runs; the gap
|
|
58
|
+
* between F12 and F13 contains PrintScreen, arrows and the keypad. */
|
|
59
|
+
if ((scancode >= SDL_SCANCODE_F1 && scancode <= SDL_SCANCODE_F12) ||
|
|
60
|
+
(scancode >= SDL_SCANCODE_F13 && scancode <= SDL_SCANCODE_F24)) {
|
|
61
|
+
int number;
|
|
62
|
+
if (scancode <= SDL_SCANCODE_F12) {
|
|
63
|
+
number = static_cast<int>(scancode - SDL_SCANCODE_F1) + 1;
|
|
64
|
+
} else {
|
|
65
|
+
number = static_cast<int>(scancode - SDL_SCANCODE_F13) + 13;
|
|
66
|
+
}
|
|
67
|
+
char name[8];
|
|
68
|
+
std::snprintf(name, sizeof(name), "F%d", number);
|
|
69
|
+
*key = name;
|
|
70
|
+
*code = name;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Keypad digits, same 1..9,0 order. */
|
|
75
|
+
if (scancode >= SDL_SCANCODE_KP_1 && scancode <= SDL_SCANCODE_KP_0) {
|
|
76
|
+
int index = static_cast<int>(scancode - SDL_SCANCODE_KP_1);
|
|
77
|
+
*code = "Numpad";
|
|
78
|
+
code->push_back(kKeypadDigits[index]);
|
|
79
|
+
key->push_back(kKeypadDigits[index]);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
switch (scancode) {
|
|
84
|
+
case SDL_SCANCODE_RETURN:
|
|
85
|
+
case SDL_SCANCODE_RETURN2:
|
|
86
|
+
set_names(key, code, "Enter", "Enter");
|
|
87
|
+
return;
|
|
88
|
+
case SDL_SCANCODE_ESCAPE:
|
|
89
|
+
set_names(key, code, "Escape", "Escape");
|
|
90
|
+
return;
|
|
91
|
+
case SDL_SCANCODE_BACKSPACE:
|
|
92
|
+
set_names(key, code, "Backspace", "Backspace");
|
|
93
|
+
return;
|
|
94
|
+
case SDL_SCANCODE_TAB:
|
|
95
|
+
set_names(key, code, "Tab", "Tab");
|
|
96
|
+
return;
|
|
97
|
+
case SDL_SCANCODE_SPACE:
|
|
98
|
+
set_names(key, code, " ", "Space");
|
|
99
|
+
return;
|
|
100
|
+
case SDL_SCANCODE_CAPSLOCK:
|
|
101
|
+
set_names(key, code, "CapsLock", "CapsLock");
|
|
102
|
+
return;
|
|
103
|
+
case SDL_SCANCODE_PRINTSCREEN:
|
|
104
|
+
set_names(key, code, "PrintScreen", "PrintScreen");
|
|
105
|
+
return;
|
|
106
|
+
case SDL_SCANCODE_SCROLLLOCK:
|
|
107
|
+
set_names(key, code, "ScrollLock", "ScrollLock");
|
|
108
|
+
return;
|
|
109
|
+
case SDL_SCANCODE_PAUSE:
|
|
110
|
+
set_names(key, code, "Pause", "Pause");
|
|
111
|
+
return;
|
|
112
|
+
case SDL_SCANCODE_INSERT:
|
|
113
|
+
set_names(key, code, "Insert", "Insert");
|
|
114
|
+
return;
|
|
115
|
+
case SDL_SCANCODE_HOME:
|
|
116
|
+
set_names(key, code, "Home", "Home");
|
|
117
|
+
return;
|
|
118
|
+
case SDL_SCANCODE_PAGEUP:
|
|
119
|
+
set_names(key, code, "PageUp", "PageUp");
|
|
120
|
+
return;
|
|
121
|
+
case SDL_SCANCODE_DELETE:
|
|
122
|
+
set_names(key, code, "Delete", "Delete");
|
|
123
|
+
return;
|
|
124
|
+
case SDL_SCANCODE_END:
|
|
125
|
+
set_names(key, code, "End", "End");
|
|
126
|
+
return;
|
|
127
|
+
case SDL_SCANCODE_PAGEDOWN:
|
|
128
|
+
set_names(key, code, "PageDown", "PageDown");
|
|
129
|
+
return;
|
|
130
|
+
case SDL_SCANCODE_RIGHT:
|
|
131
|
+
set_names(key, code, "ArrowRight", "ArrowRight");
|
|
132
|
+
return;
|
|
133
|
+
case SDL_SCANCODE_LEFT:
|
|
134
|
+
set_names(key, code, "ArrowLeft", "ArrowLeft");
|
|
135
|
+
return;
|
|
136
|
+
case SDL_SCANCODE_DOWN:
|
|
137
|
+
set_names(key, code, "ArrowDown", "ArrowDown");
|
|
138
|
+
return;
|
|
139
|
+
case SDL_SCANCODE_UP:
|
|
140
|
+
set_names(key, code, "ArrowUp", "ArrowUp");
|
|
141
|
+
return;
|
|
142
|
+
case SDL_SCANCODE_NUMLOCKCLEAR:
|
|
143
|
+
set_names(key, code, "NumLock", "NumLock");
|
|
144
|
+
return;
|
|
145
|
+
case SDL_SCANCODE_KP_ENTER:
|
|
146
|
+
set_names(key, code, "Enter", "NumpadEnter");
|
|
147
|
+
return;
|
|
148
|
+
case SDL_SCANCODE_KP_DIVIDE:
|
|
149
|
+
set_names(key, code, "/", "NumpadDivide");
|
|
150
|
+
return;
|
|
151
|
+
case SDL_SCANCODE_KP_MULTIPLY:
|
|
152
|
+
set_names(key, code, "*", "NumpadMultiply");
|
|
153
|
+
return;
|
|
154
|
+
case SDL_SCANCODE_KP_MINUS:
|
|
155
|
+
set_names(key, code, "-", "NumpadSubtract");
|
|
156
|
+
return;
|
|
157
|
+
case SDL_SCANCODE_KP_PLUS:
|
|
158
|
+
set_names(key, code, "+", "NumpadAdd");
|
|
159
|
+
return;
|
|
160
|
+
case SDL_SCANCODE_KP_PERIOD:
|
|
161
|
+
case SDL_SCANCODE_KP_DECIMAL:
|
|
162
|
+
set_names(key, code, ".", "NumpadDecimal");
|
|
163
|
+
return;
|
|
164
|
+
case SDL_SCANCODE_KP_EQUALS:
|
|
165
|
+
set_names(key, code, "=", "NumpadEqual");
|
|
166
|
+
return;
|
|
167
|
+
case SDL_SCANCODE_KP_COMMA:
|
|
168
|
+
set_names(key, code, ",", "NumpadComma");
|
|
169
|
+
return;
|
|
170
|
+
case SDL_SCANCODE_APPLICATION:
|
|
171
|
+
case SDL_SCANCODE_MENU:
|
|
172
|
+
set_names(key, code, "ContextMenu", "ContextMenu");
|
|
173
|
+
return;
|
|
174
|
+
case SDL_SCANCODE_LCTRL:
|
|
175
|
+
set_names(key, code, "Control", "ControlLeft");
|
|
176
|
+
return;
|
|
177
|
+
case SDL_SCANCODE_RCTRL:
|
|
178
|
+
set_names(key, code, "Control", "ControlRight");
|
|
179
|
+
return;
|
|
180
|
+
case SDL_SCANCODE_LSHIFT:
|
|
181
|
+
set_names(key, code, "Shift", "ShiftLeft");
|
|
182
|
+
return;
|
|
183
|
+
case SDL_SCANCODE_RSHIFT:
|
|
184
|
+
set_names(key, code, "Shift", "ShiftRight");
|
|
185
|
+
return;
|
|
186
|
+
case SDL_SCANCODE_LALT:
|
|
187
|
+
set_names(key, code, "Alt", "AltLeft");
|
|
188
|
+
return;
|
|
189
|
+
case SDL_SCANCODE_RALT:
|
|
190
|
+
set_names(key, code, "Alt", "AltRight");
|
|
191
|
+
return;
|
|
192
|
+
case SDL_SCANCODE_LGUI:
|
|
193
|
+
set_names(key, code, "Meta", "MetaLeft");
|
|
194
|
+
return;
|
|
195
|
+
case SDL_SCANCODE_RGUI:
|
|
196
|
+
set_names(key, code, "Meta", "MetaRight");
|
|
197
|
+
return;
|
|
198
|
+
default:
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Remaining US punctuation, keyed by scancode. */
|
|
203
|
+
switch (scancode) {
|
|
204
|
+
case SDL_SCANCODE_MINUS:
|
|
205
|
+
set_names(key, code, shift ? "_" : "-", "Minus");
|
|
206
|
+
return;
|
|
207
|
+
case SDL_SCANCODE_EQUALS:
|
|
208
|
+
set_names(key, code, shift ? "+" : "=", "Equal");
|
|
209
|
+
return;
|
|
210
|
+
case SDL_SCANCODE_LEFTBRACKET:
|
|
211
|
+
set_names(key, code, shift ? "{" : "[", "BracketLeft");
|
|
212
|
+
return;
|
|
213
|
+
case SDL_SCANCODE_RIGHTBRACKET:
|
|
214
|
+
set_names(key, code, shift ? "}" : "]", "BracketRight");
|
|
215
|
+
return;
|
|
216
|
+
case SDL_SCANCODE_BACKSLASH:
|
|
217
|
+
case SDL_SCANCODE_NONUSHASH:
|
|
218
|
+
set_names(key, code, shift ? "|" : "\\", "Backslash");
|
|
219
|
+
return;
|
|
220
|
+
case SDL_SCANCODE_SEMICOLON:
|
|
221
|
+
set_names(key, code, shift ? ":" : ";", "Semicolon");
|
|
222
|
+
return;
|
|
223
|
+
case SDL_SCANCODE_APOSTROPHE:
|
|
224
|
+
set_names(key, code, shift ? "\"" : "'", "Quote");
|
|
225
|
+
return;
|
|
226
|
+
case SDL_SCANCODE_GRAVE:
|
|
227
|
+
set_names(key, code, shift ? "~" : "`", "Backquote");
|
|
228
|
+
return;
|
|
229
|
+
case SDL_SCANCODE_COMMA:
|
|
230
|
+
set_names(key, code, shift ? "<" : ",", "Comma");
|
|
231
|
+
return;
|
|
232
|
+
case SDL_SCANCODE_PERIOD:
|
|
233
|
+
set_names(key, code, shift ? ">" : ".", "Period");
|
|
234
|
+
return;
|
|
235
|
+
case SDL_SCANCODE_SLASH:
|
|
236
|
+
set_names(key, code, shift ? "?" : "/", "Slash");
|
|
237
|
+
return;
|
|
238
|
+
case SDL_SCANCODE_NONUSBACKSLASH:
|
|
239
|
+
set_names(key, code, "\\", "IntlBackslash");
|
|
240
|
+
return;
|
|
241
|
+
default:
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Unknown key: keep SDL's human-readable names. `key` comes from the
|
|
246
|
+
* keycode so a consumer matching "Up" still sees it on a layout whose
|
|
247
|
+
* scancode name differs. */
|
|
248
|
+
const char *key_name = SDL_GetKeyName(keycode);
|
|
249
|
+
const char *code_name = SDL_GetScancodeName(scancode);
|
|
250
|
+
set_names(key, code, key_name != nullptr ? key_name : "",
|
|
251
|
+
code_name != nullptr ? code_name : "");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
} // namespace guidev
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — SDL key event to browser KeyboardEvent names.
|
|
3
|
+
*
|
|
4
|
+
* The DOM exposes the browser spellings `KeyboardEvent.key` (the produced
|
|
5
|
+
* character / action name) and `code` (the physical key), while SDL reports a
|
|
6
|
+
* scancode and keycode. A page written for a browser matches on `code`
|
|
7
|
+
* ("KeyW", "ArrowUp", …), so translating `SDL_GetScancodeName` directly
|
|
8
|
+
* ("W", "Up") is not enough.
|
|
9
|
+
*/
|
|
10
|
+
#ifndef XT_GUI_KEYMAP_H
|
|
11
|
+
#define XT_GUI_KEYMAP_H
|
|
12
|
+
|
|
13
|
+
#include <SDL3/SDL.h>
|
|
14
|
+
|
|
15
|
+
#include <string>
|
|
16
|
+
|
|
17
|
+
namespace guidev {
|
|
18
|
+
|
|
19
|
+
/** Fill `key` and `code` with the browser names for one physical key press.
|
|
20
|
+
*
|
|
21
|
+
* `scancode` selects the physical `code`; `keycode` and the modifier state
|
|
22
|
+
* select the produced `key` (letters honour Shift and Caps Lock, digits and
|
|
23
|
+
* punctuation use the US layout, which matches SDL's default key names).
|
|
24
|
+
* Unrecognised keys fall back to SDL's own names rather than an empty string,
|
|
25
|
+
* so a page that only logs events still sees something useful. */
|
|
26
|
+
void xt_gui_key_names(SDL_Scancode scancode, SDL_Keycode keycode, bool shift, bool caps_lock,
|
|
27
|
+
std::string *key, std::string *code);
|
|
28
|
+
|
|
29
|
+
} // namespace guidev
|
|
30
|
+
|
|
31
|
+
#endif /* XT_GUI_KEYMAP_H */
|
|
@@ -380,7 +380,22 @@ std::string DisplayList::dump() const {
|
|
|
380
380
|
} else {
|
|
381
381
|
const PaintImage &image = images[op.index];
|
|
382
382
|
suffix = transformSuffix(image.transform);
|
|
383
|
-
if (image.
|
|
383
|
+
if (image.gradient != nullptr) {
|
|
384
|
+
const PaintGradient &gradient = *image.gradient;
|
|
385
|
+
std::snprintf(buffer, sizeof(buffer),
|
|
386
|
+
"image x=%.1f y=%.1f w=%.1f h=%.1f gradient=%s center=%.1f,%.1f "
|
|
387
|
+
"radius=%.1f,%.1f stops=",
|
|
388
|
+
image.x, image.y, image.width, image.height,
|
|
389
|
+
gradient.repeating ? "repeating-radial" : "radial", gradient.center_x,
|
|
390
|
+
gradient.center_y, gradient.radius_x, gradient.radius_y);
|
|
391
|
+
text += prefix + buffer;
|
|
392
|
+
for (size_t stop = 0; stop < gradient.stops.size(); stop++) {
|
|
393
|
+
std::snprintf(buffer, sizeof(buffer), "%s%s@%.4f", stop == 0 ? "" : ",",
|
|
394
|
+
formatColor(gradient.stops[stop].color).c_str(),
|
|
395
|
+
gradient.stops[stop].offset);
|
|
396
|
+
text += buffer;
|
|
397
|
+
}
|
|
398
|
+
} else if (image.media_id != 0) {
|
|
384
399
|
std::snprintf(buffer, sizeof(buffer), "image x=%.1f y=%.1f w=%.1f h=%.1f media=%llu",
|
|
385
400
|
image.x, image.y, image.width, image.height,
|
|
386
401
|
(unsigned long long)image.media_id);
|