xbintsc 0.5.45 → 0.5.52
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 +1 -1
- package/README.md +5 -4
- package/README.zh-CN.md +4 -3
- package/dist/src/cli/extensions.d.ts +24 -0
- package/dist/src/cli/extensions.js +62 -0
- package/dist/src/cli/extensions.js.map +1 -0
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/cli/help.js +3 -1
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hints.js +8 -3
- package/dist/src/cli/hints.js.map +1 -1
- package/dist/src/cli/main.js +21 -28
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/diagnostics/diagnostic.d.ts +3 -1
- package/dist/src/diagnostics/diagnostic.js +2 -0
- package/dist/src/diagnostics/diagnostic.js.map +1 -1
- package/dist/src/driver/build-key.d.ts +2 -0
- package/dist/src/driver/build-key.js +1 -0
- package/dist/src/driver/build-key.js.map +1 -1
- package/dist/src/driver/compiler.js +61 -8
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/config.d.ts +4 -0
- package/dist/src/driver/config.js +2 -0
- package/dist/src/driver/config.js.map +1 -1
- package/dist/src/driver/link-features.d.ts +37 -0
- package/dist/src/driver/link-features.js +123 -0
- package/dist/src/driver/link-features.js.map +1 -0
- package/dist/src/driver/options.d.ts +10 -0
- package/dist/src/driver/runtime-objects.d.ts +5 -0
- package/dist/src/driver/runtime-objects.js +20 -1
- package/dist/src/driver/runtime-objects.js.map +1 -1
- package/dist/src/driver/usage.d.ts +23 -0
- package/dist/src/driver/usage.js +353 -0
- package/dist/src/driver/usage.js.map +1 -0
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/catalog.js +1 -2
- package/dist/src/extensions/catalog.js.map +1 -1
- package/dist/src/extensions/gui/features.d.ts +34 -0
- package/dist/src/extensions/gui/features.js +165 -0
- package/dist/src/extensions/gui/features.js.map +1 -0
- package/dist/src/extensions/gui/index.js +10 -4
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/link-feature.d.ts +70 -0
- package/dist/src/extensions/link-feature.js +18 -0
- package/dist/src/extensions/link-feature.js.map +1 -0
- package/dist/src/extensions/registry.d.ts +24 -4
- package/dist/src/extensions/registry.js +26 -3
- package/dist/src/extensions/registry.js.map +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +0 -1
- package/dist/src/index.js.map +1 -1
- package/dist/tests/cli/hints.test.js +5 -7
- package/dist/tests/cli/hints.test.js.map +1 -1
- package/dist/tests/driver/link-features.test.js +105 -0
- package/dist/tests/driver/link-features.test.js.map +1 -0
- package/dist/tests/driver/usage.test.d.ts +1 -0
- package/dist/tests/driver/usage.test.js +88 -0
- package/dist/tests/driver/usage.test.js.map +1 -0
- package/dist/tests/e2e/gui-audio.test.js +4 -4
- package/dist/tests/e2e/gui-audio.test.js.map +1 -1
- package/dist/tests/e2e/gui-css-coverage.test.d.ts +9 -0
- package/dist/tests/e2e/gui-css-coverage.test.js +333 -0
- package/dist/tests/e2e/gui-css-coverage.test.js.map +1 -0
- package/dist/tests/e2e/gui-css-features.test.d.ts +6 -0
- package/dist/tests/e2e/gui-css-features.test.js +270 -0
- package/dist/tests/e2e/gui-css-features.test.js.map +1 -0
- package/dist/tests/e2e/gui-effects.test.js +92 -11
- package/dist/tests/e2e/gui-effects.test.js.map +1 -1
- package/dist/tests/e2e/gui-feature-linking.test.d.ts +9 -0
- package/dist/tests/e2e/gui-feature-linking.test.js +68 -0
- package/dist/tests/e2e/gui-feature-linking.test.js.map +1 -0
- package/dist/tests/e2e/gui-font-face.test.d.ts +10 -0
- package/dist/tests/e2e/gui-font-face.test.js +142 -0
- package/dist/tests/e2e/gui-font-face.test.js.map +1 -0
- package/dist/tests/e2e/gui-fonts.test.d.ts +10 -0
- package/dist/tests/e2e/gui-fonts.test.js +150 -0
- package/dist/tests/e2e/gui-fonts.test.js.map +1 -0
- package/dist/tests/e2e/gui-helpers.d.ts +10 -1
- package/dist/tests/e2e/gui-helpers.js +6 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-layout.test.js +7 -3
- package/dist/tests/e2e/gui-layout.test.js.map +1 -1
- package/dist/tests/e2e/gui-media-disabled.test.d.ts +5 -4
- package/dist/tests/e2e/gui-media-disabled.test.js +7 -6
- package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.d.ts +5 -4
- package/dist/tests/e2e/gui-media.test.js +9 -9
- package/dist/tests/e2e/gui-media.test.js.map +1 -1
- package/dist/tests/e2e/gui-paint.test.js +5 -2
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/dist/tests/e2e/gui-shaping.test.d.ts +5 -0
- package/dist/tests/e2e/gui-shaping.test.js +47 -0
- package/dist/tests/e2e/gui-shaping.test.js.map +1 -0
- package/dist/tests/e2e/gui-track-sizing.test.d.ts +6 -0
- package/dist/tests/e2e/gui-track-sizing.test.js +173 -0
- package/dist/tests/e2e/gui-track-sizing.test.js.map +1 -0
- package/dist/tests/e2e/gui-transform.test.js +3 -2
- package/dist/tests/e2e/gui-transform.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl-pipeline-cache.test.d.ts +18 -0
- package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js +120 -0
- package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js.map +1 -0
- package/dist/tests/extensions/gui-features.test.d.ts +1 -0
- package/dist/tests/extensions/gui-features.test.js +175 -0
- package/dist/tests/extensions/gui-features.test.js.map +1 -0
- package/doc/ai/cli.md +5 -1
- package/doc/ai/extensions.md +56 -26
- package/doc/ai/troubleshooting.md +10 -7
- package/doc/ai/zh-CN/cli.md +5 -1
- package/doc/ai/zh-CN/extensions.md +48 -22
- package/doc/ai/zh-CN/troubleshooting.md +6 -5
- package/doc/gui/animation.md +39 -9
- package/doc/gui/api.md +2 -1
- package/doc/gui/build.md +137 -19
- package/doc/gui/canvas.md +1 -1
- package/doc/gui/dom.md +20 -4
- package/doc/gui/known-issues.md +63 -26
- package/doc/gui/layout.md +31 -8
- package/doc/gui/media.md +13 -7
- package/doc/gui/rendering.md +114 -39
- package/doc/gui/runtime.md +5 -0
- package/doc/gui/webgl.md +24 -6
- package/doc/gui.md +110 -47
- package/doc/implemented/cli.md +2 -0
- package/doc/implemented/extensions.md +9 -3
- package/doc/npm-package.md +4 -4
- package/doc/xbintsc.config.schema.json +10 -0
- package/doc/zh-CN/gui/animation.md +33 -10
- package/doc/zh-CN/gui/api.md +2 -1
- package/doc/zh-CN/gui/build.md +110 -16
- package/doc/zh-CN/gui/canvas.md +1 -1
- package/doc/zh-CN/gui/dom.md +16 -3
- package/doc/zh-CN/gui/known-issues.md +45 -15
- package/doc/zh-CN/gui/layout.md +23 -6
- package/doc/zh-CN/gui/media.md +11 -6
- package/doc/zh-CN/gui/rendering.md +84 -26
- package/doc/zh-CN/gui/runtime.md +3 -0
- package/doc/zh-CN/gui/webgl.md +3 -3
- package/doc/zh-CN/gui.md +92 -41
- package/doc/zh-CN/implemented/cli.md +2 -0
- package/doc/zh-CN/implemented/extensions.md +9 -3
- package/doc/zh-CN/npm-package.md +4 -3
- package/package.json +7 -7
- package/runtime/ext_gui/animation_values.cpp +425 -0
- package/runtime/ext_gui/animation_values.h +64 -0
- package/runtime/ext_gui/browser.cpp +22 -15
- package/runtime/ext_gui/browser_loop.cpp +13 -1
- package/runtime/ext_gui/canvas.h +1 -1
- package/runtime/ext_gui/canvas_raster.cpp +1 -1
- package/runtime/ext_gui/canvas_text.cpp +1 -1
- package/runtime/ext_gui/css.cpp +172 -21
- package/runtime/ext_gui/css.h +75 -4
- package/runtime/ext_gui/css_match.cpp +4 -0
- package/runtime/ext_gui/css_sheet.cpp +218 -2
- package/runtime/ext_gui/document.cpp +42 -192
- package/runtime/ext_gui/document.h +25 -12
- package/runtime/ext_gui/document_animation.cpp +428 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +15 -8
- package/runtime/ext_gui/dom_api_canvas_extra.cpp +44 -20
- package/runtime/ext_gui/dom_api_canvas_image.cpp +3 -1
- package/runtime/ext_gui/dom_api_node_read.cpp +10 -0
- package/runtime/ext_gui/dom_api_offscreen.cpp +3 -1
- package/runtime/ext_gui/dom_api_style.cpp +7 -0
- package/runtime/ext_gui/dom_api_webgl_internal.h +4 -0
- package/runtime/ext_gui/dom_api_webgl_source.cpp +5 -2
- package/runtime/ext_gui/effect_values.cpp +245 -21
- package/runtime/ext_gui/gpu_bridge.c +28 -0
- package/runtime/ext_gui/gpu_feature.cpp +30 -0
- package/runtime/ext_gui/gpu_shader.cpp +22 -0
- package/runtime/ext_gui/gpu_shader_api.h +50 -0
- package/runtime/ext_gui/gradient_values.cpp +24 -0
- package/runtime/ext_gui/grid_values.cpp +23 -0
- package/runtime/ext_gui/gui.cpp +6 -37
- package/runtime/ext_gui/gui_canvas.cpp +14 -6
- package/runtime/ext_gui/gui_cursor.cpp +50 -0
- package/runtime/ext_gui/gui_engine.h +1 -18
- package/runtime/ext_gui/gui_events.cpp +19 -6
- package/runtime/ext_gui/gui_internal.h +3 -0
- package/runtime/ext_gui/gui_webgl.cpp +25 -10
- package/runtime/ext_gui/image_api.cpp +18 -0
- package/runtime/ext_gui/image_api.h +43 -0
- package/runtime/ext_gui/image_bitmap_browser.cpp +20 -11
- package/runtime/ext_gui/image_bridge.c +22 -0
- package/runtime/ext_gui/image_feature.cpp +107 -0
- package/runtime/ext_gui/inline_svg.cpp +95 -8
- package/runtime/ext_gui/inline_svg.h +5 -1
- package/runtime/ext_gui/layout.cpp +15 -9
- package/runtime/ext_gui/layout.h +8 -2
- package/runtime/ext_gui/layout_flex.cpp +134 -29
- package/runtime/ext_gui/layout_flow.cpp +58 -7
- package/runtime/ext_gui/layout_flow_internal.h +39 -4
- package/runtime/ext_gui/layout_grid.cpp +31 -0
- package/runtime/ext_gui/layout_inline.cpp +26 -2
- package/runtime/ext_gui/layout_vertical.cpp +196 -0
- package/runtime/ext_gui/media.cpp +4 -2
- package/runtime/ext_gui/media.h +3 -3
- package/runtime/ext_gui/media_api.h +7 -6
- package/runtime/ext_gui/paint.cpp +67 -9
- package/runtime/ext_gui/paint.h +61 -8
- package/runtime/ext_gui/paint_backdrop.cpp +211 -0
- package/runtime/ext_gui/paint_background.cpp +175 -97
- package/runtime/ext_gui/paint_effects.cpp +12 -6
- package/runtime/ext_gui/paint_internal.h +27 -0
- package/runtime/ext_gui/paint_outline.cpp +44 -0
- package/runtime/ext_gui/paint_shadow.cpp +368 -0
- package/runtime/ext_gui/renderer.cpp +15 -7
- package/runtime/ext_gui/renderer.h +11 -2
- package/runtime/ext_gui/renderer_backdrop.cpp +374 -0
- package/runtime/ext_gui/renderer_bitmap.cpp +82 -0
- package/runtime/ext_gui/renderer_gradient.cpp +51 -17
- package/runtime/ext_gui/renderer_image.cpp +19 -7
- package/runtime/ext_gui/renderer_internal.h +21 -0
- package/runtime/ext_gui/renderer_layer.cpp +37 -6
- package/runtime/ext_gui/renderer_text.cpp +2 -1
- package/runtime/ext_gui/stb_truetype.h +5077 -0
- package/runtime/ext_gui/style.cpp +62 -8
- package/runtime/ext_gui/style.h +111 -4
- package/runtime/ext_gui/style_cascade.cpp +83 -0
- package/runtime/ext_gui/style_internal.h +13 -1
- package/runtime/ext_gui/style_length.cpp +92 -0
- package/runtime/ext_gui/style_print.cpp +83 -2
- package/runtime/ext_gui/style_svg.cpp +40 -0
- package/runtime/ext_gui/style_text.cpp +302 -0
- package/runtime/ext_gui/style_ui.cpp +150 -0
- package/runtime/ext_gui/style_values.cpp +76 -62
- package/runtime/ext_gui/style_values_internal.h +23 -0
- package/runtime/ext_gui/text.cpp +27 -499
- package/runtime/ext_gui/text.h +18 -9
- package/runtime/ext_gui/text_api.h +45 -0
- package/runtime/ext_gui/text_bridge.c +23 -0
- package/runtime/ext_gui/text_cache.cpp +12 -3
- package/runtime/ext_gui/text_font_db.cpp +427 -0
- package/runtime/ext_gui/text_font_paths.cpp +250 -0
- package/runtime/ext_gui/text_font_paths.h +39 -0
- package/runtime/ext_gui/text_font_sfnt.cpp +396 -0
- package/runtime/ext_gui/text_font_sfnt.h +44 -0
- package/runtime/ext_gui/text_fonts.cpp +348 -0
- package/runtime/ext_gui/text_fonts.h +78 -0
- package/runtime/ext_gui/text_fonts_internal.h +77 -0
- package/runtime/ext_gui/text_glyph_cache.cpp +2 -2
- package/runtime/ext_gui/text_hb.cpp +438 -0
- package/runtime/ext_gui/text_stb.cpp +369 -0
- package/runtime/ext_gui/transform.cpp +107 -9
- package/runtime/ext_gui/transform.h +5 -0
- package/runtime/ext_gui/webgl_api.cpp +19 -0
- package/runtime/ext_gui/webgl_api.h +53 -0
- package/runtime/ext_gui/webgl_bridge.c +25 -0
- package/runtime/ext_gui/webgl_draw_setup.cpp +42 -1
- package/runtime/ext_gui/webgl_feature.cpp +82 -0
- package/runtime/ext_gui/webgl_feature.h +28 -0
- package/runtime/ext_gui/webgl_msl.cpp +41 -0
- package/runtime/ext_gui/webgl_msl.h +6 -0
- package/runtime/ext_gui/webgl_shader.cpp +40 -32
- package/runtime/ext_gui/window.cpp +7 -7
- package/runtime/ext_media/decode_audio.cpp +1 -1
- package/runtime/ext_media/media.cpp +1 -1
- package/runtime/ext_media/media_bridge.c +7 -7
- package/scripts/build-gui.ts +97 -98
- package/scripts/check-file-length.ts +1 -0
- package/scripts/gui-build-options.ts +122 -0
- package/src/cli/extensions.ts +72 -0
- package/src/cli/help.ts +3 -1
- package/src/cli/hints.ts +9 -3
- package/src/cli/main.ts +21 -27
- package/src/diagnostics/diagnostic.ts +2 -0
- package/src/driver/build-key.ts +3 -0
- package/src/driver/compiler.ts +63 -8
- package/src/driver/config.ts +6 -0
- package/src/driver/link-features.ts +163 -0
- package/src/driver/options.ts +10 -0
- package/src/driver/runtime-objects.ts +26 -1
- package/src/driver/usage.ts +376 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/catalog.ts +1 -2
- package/src/extensions/gui/features.ts +166 -0
- package/src/extensions/gui/index.ts +10 -4
- package/src/extensions/link-feature.ts +72 -0
- package/src/extensions/registry.ts +45 -4
- package/src/index.ts +3 -2
- package/dist/src/extensions/media/index.d.ts +0 -28
- package/dist/src/extensions/media/index.js +0 -59
- package/dist/src/extensions/media/index.js.map +0 -1
- package/dist/tests/extensions/media.test.js +0 -63
- package/dist/tests/extensions/media.test.js.map +0 -1
- package/src/extensions/media/index.ts +0 -65
- /package/dist/tests/{extensions/media.test.d.ts → driver/link-features.test.d.ts} +0 -0
|
@@ -77,7 +77,8 @@ xtgui::Canvas2D *xt_gui_canvas_get(XtGuiWindow *win, const xtgui::Node *node) {
|
|
|
77
77
|
|
|
78
78
|
void xt_gui_canvas_forget(XtGuiWindow *win, const xtgui::Node *node) {
|
|
79
79
|
if (win == nullptr || node == nullptr) return;
|
|
80
|
-
|
|
80
|
+
const XtWebglApi *webgl = xt_webgl_api();
|
|
81
|
+
if (webgl != nullptr) webgl->forget(win, node);
|
|
81
82
|
auto found = win->canvases.find(node);
|
|
82
83
|
if (found == win->canvases.end()) return;
|
|
83
84
|
releaseCanvasTexture(found->second.get());
|
|
@@ -88,11 +89,17 @@ void xt_gui_canvas_sync(XtGuiWindow *win) {
|
|
|
88
89
|
if (win == nullptr || win->document == nullptr) return;
|
|
89
90
|
std::vector<const xtgui::Node *> nodes;
|
|
90
91
|
collectCanvases(win->document->root(), &nodes);
|
|
92
|
+
const XtWebglApi *webgl = xt_webgl_api();
|
|
91
93
|
for (const xtgui::Node *node : nodes) {
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
if (webgl != nullptr) {
|
|
95
|
+
if (void *context = webgl->find(win, node)) {
|
|
96
|
+
float width = 0.0f;
|
|
97
|
+
float height = 0.0f;
|
|
98
|
+
uint64_t id = 0;
|
|
99
|
+
webgl->info(context, &width, &height, &id);
|
|
100
|
+
win->document->setCanvasInfo(node, width, height, id);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
96
103
|
}
|
|
97
104
|
xtgui::Canvas2D *canvas = xt_gui_canvas_find(win, node);
|
|
98
105
|
if (canvas != nullptr) {
|
|
@@ -136,7 +143,8 @@ void xt_gui_canvas_bind_pending(XtGuiWindow *win, const xtgui::Node *node, xt_va
|
|
|
136
143
|
|
|
137
144
|
void xt_gui_canvas_shutdown(XtGuiWindow *win) {
|
|
138
145
|
if (win == nullptr) return;
|
|
139
|
-
|
|
146
|
+
const XtWebglApi *webgl = xt_webgl_api();
|
|
147
|
+
if (webgl != nullptr) webgl->shutdown(win);
|
|
140
148
|
for (auto &entry : win->canvases) releaseCanvasTexture(entry.second.get());
|
|
141
149
|
win->canvases.clear();
|
|
142
150
|
for (auto &entry : win->offscreen_canvases) releaseCanvasTexture(entry.second.get());
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — CSS `cursor` → SDL system cursor.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from `gui_events.cpp` (which owns the input dispatch) so both
|
|
5
|
+
* translation units stay under the repository's per-file line budget.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "gui_internal.h"
|
|
9
|
+
|
|
10
|
+
namespace guidev {
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
|
|
14
|
+
/** Lazily created SDL system cursors for the CSS cursor values we map. */
|
|
15
|
+
SDL_Cursor *g_system_cursors[9] = {};
|
|
16
|
+
|
|
17
|
+
SDL_SystemCursor sdlCursorFor(xtgui::CursorShape shape) {
|
|
18
|
+
switch (shape) {
|
|
19
|
+
case xtgui::CursorShape::Pointer: return SDL_SYSTEM_CURSOR_POINTER;
|
|
20
|
+
case xtgui::CursorShape::Text: return SDL_SYSTEM_CURSOR_TEXT;
|
|
21
|
+
case xtgui::CursorShape::Move: return SDL_SYSTEM_CURSOR_MOVE;
|
|
22
|
+
case xtgui::CursorShape::Wait: return SDL_SYSTEM_CURSOR_WAIT;
|
|
23
|
+
case xtgui::CursorShape::Help: return SDL_SYSTEM_CURSOR_DEFAULT;
|
|
24
|
+
case xtgui::CursorShape::NotAllowed: return SDL_SYSTEM_CURSOR_NOT_ALLOWED;
|
|
25
|
+
case xtgui::CursorShape::Crosshair: return SDL_SYSTEM_CURSOR_CROSSHAIR;
|
|
26
|
+
case xtgui::CursorShape::Auto:
|
|
27
|
+
case xtgui::CursorShape::Default: return SDL_SYSTEM_CURSOR_DEFAULT;
|
|
28
|
+
}
|
|
29
|
+
return SDL_SYSTEM_CURSOR_DEFAULT;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
} // namespace
|
|
33
|
+
|
|
34
|
+
/** Apply the hovered element's computed `cursor` to the window. */
|
|
35
|
+
void applyCursor(XtGuiWindow *win, const xtgui::Node *node) {
|
|
36
|
+
if (win == nullptr || win->window == nullptr) return;
|
|
37
|
+
xtgui::CursorShape shape = xtgui::CursorShape::Default;
|
|
38
|
+
if (node != nullptr && win->document != nullptr) {
|
|
39
|
+
const xtgui::XtStyle *style = win->document->styleOf(node);
|
|
40
|
+
if (style != nullptr) shape = style->cursor;
|
|
41
|
+
}
|
|
42
|
+
int index = (int)shape;
|
|
43
|
+
if (index < 0 || index >= 9) index = 0;
|
|
44
|
+
if (g_system_cursors[index] == nullptr) {
|
|
45
|
+
g_system_cursors[index] = SDL_CreateSystemCursor(sdlCursorFor(shape));
|
|
46
|
+
}
|
|
47
|
+
if (g_system_cursors[index] != nullptr) SDL_SetCursor(g_system_cursors[index]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
} // namespace guidev
|
|
@@ -94,7 +94,7 @@ xt_value xt_gui_audio_create(int32_t argc, xt_value *argv);
|
|
|
94
94
|
#include "media.h"
|
|
95
95
|
#include "paint.h"
|
|
96
96
|
#include "renderer.h"
|
|
97
|
-
#include "
|
|
97
|
+
#include "webgl_api.h"
|
|
98
98
|
|
|
99
99
|
/** One element-level event listener registered through `addEventListener`. */
|
|
100
100
|
struct XtGuiNodeListener {
|
|
@@ -225,9 +225,6 @@ struct XtGuiWindow {
|
|
|
225
225
|
* lazily by `getContext("2d")` (or by `xt_gui_canvas_sync` for a live canvas),
|
|
226
226
|
* released when the node is forgotten or the document reloads. */
|
|
227
227
|
std::unordered_map<const xtgui::Node *, std::unique_ptr<xtgui::Canvas2D>> canvases;
|
|
228
|
-
/** One WebGL context per `<canvas>` that asked for `getContext("webgl")`.
|
|
229
|
-
* A canvas uses either this map or `canvases`, never both. */
|
|
230
|
-
std::unordered_map<const xtgui::Node *, std::unique_ptr<xtgui::WebGLContext>> webgl_canvases;
|
|
231
228
|
/** Canvases whose element handle was created before `loadHTML` (a module
|
|
232
229
|
* that calls `getElementById("scene").getContext("2d")` while it
|
|
233
230
|
* initialises). Keyed by canvas id; moved into `canvases` when the pending
|
|
@@ -355,20 +352,6 @@ void xt_gui_canvas_sync(XtGuiWindow *win);
|
|
|
355
352
|
/** Release every canvas of `win` (window close / `loadHTML`). */
|
|
356
353
|
void xt_gui_canvas_shutdown(XtGuiWindow *win);
|
|
357
354
|
|
|
358
|
-
/** The WebGL context of a `<canvas>`, creating it on first use. `webgl2`
|
|
359
|
-
* selects the WebGL 2 API/prototype; a canvas keeps the first context kind it
|
|
360
|
-
* asked for, so a later request for the other one answers null. */
|
|
361
|
-
xtgui::WebGLContext *xt_gui_webgl_get(XtGuiWindow *win, const xtgui::Node *node, bool webgl2);
|
|
362
|
-
/** The existing WebGL context of `node`, or null (no side effects). */
|
|
363
|
-
xtgui::WebGLContext *xt_gui_webgl_find(XtGuiWindow *win, const xtgui::Node *node);
|
|
364
|
-
/** Drop a node's WebGL context (called when its handle is forgotten). */
|
|
365
|
-
void xt_gui_webgl_forget(XtGuiWindow *win, const xtgui::Node *node);
|
|
366
|
-
/** Release every WebGL context of `win`. */
|
|
367
|
-
void xt_gui_webgl_shutdown(XtGuiWindow *win);
|
|
368
|
-
/** Submit every live WebGL context's queued commands. Called once per frame
|
|
369
|
-
* before the page's own render pass samples a canvas texture. */
|
|
370
|
-
void xt_gui_webgl_submit_all();
|
|
371
|
-
|
|
372
355
|
#endif /* __cplusplus */
|
|
373
356
|
|
|
374
357
|
#endif /* XT_GUI_ENGINE_H */
|
|
@@ -157,6 +157,7 @@ static void xt_gui_update_hover(XtGuiWindow *win, const xtgui::Node *node) {
|
|
|
157
157
|
win->geometry.dirty = 1;
|
|
158
158
|
win->present_dirty = 1;
|
|
159
159
|
}
|
|
160
|
+
guidev::applyCursor(win, node);
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
/** Pointer payload. `node` is the hit-test result the caller already resolved,
|
|
@@ -187,10 +188,21 @@ void xt_gui_dispatch_pointer(XtGuiWindow *win, const char *type, float x, float
|
|
|
187
188
|
if (win == NULL || !win->open) return;
|
|
188
189
|
const xtgui::Node *node = xt_gui_hit_node(win, x, y);
|
|
189
190
|
xt_gui_update_hover(win, node);
|
|
190
|
-
if (
|
|
191
|
-
if (
|
|
192
|
-
win->
|
|
193
|
-
|
|
191
|
+
if (win->document != nullptr) {
|
|
192
|
+
if (strcmp(type, "mousedown") == 0) {
|
|
193
|
+
if (win->document->setFocus(node)) {
|
|
194
|
+
win->geometry.dirty = 1;
|
|
195
|
+
win->present_dirty = 1;
|
|
196
|
+
}
|
|
197
|
+
if (win->document->setActive(node)) {
|
|
198
|
+
win->geometry.dirty = 1;
|
|
199
|
+
win->present_dirty = 1;
|
|
200
|
+
}
|
|
201
|
+
} else if (strcmp(type, "mouseup") == 0) {
|
|
202
|
+
if (win->document->setActive(nullptr)) {
|
|
203
|
+
win->geometry.dirty = 1;
|
|
204
|
+
win->present_dirty = 1;
|
|
205
|
+
}
|
|
194
206
|
}
|
|
195
207
|
}
|
|
196
208
|
xt_value payload = xt_gui_pointer_payload(type, win, x, y, button, clicks, node);
|
|
@@ -475,8 +487,9 @@ void xt_gui_render_window(XtGuiWindow *win) {
|
|
|
475
487
|
xt_gui_upload_end(&uploads);
|
|
476
488
|
/* WebGL contexts record into their own command buffers; submit them first so
|
|
477
489
|
* the page's render pass samples this frame's drawing-buffer contents. */
|
|
478
|
-
|
|
479
|
-
|
|
490
|
+
const XtWebglApi *webgl = xt_webgl_api();
|
|
491
|
+
const bool webgl_live = webgl != nullptr && webgl->submit_all();
|
|
492
|
+
if (uploaded || webgl_live) win->present_dirty = 1;
|
|
480
493
|
|
|
481
494
|
/* Present only when something happened. A static document otherwise burned a
|
|
482
495
|
* swapchain acquire and a full-scene draw every frame. `XT_GUI_ALWAYS_PRESENT`
|
|
@@ -24,6 +24,9 @@ extern int g_window_count;
|
|
|
24
24
|
/** Translate one SDL event into engine callbacks (defined in gui_events.cpp). */
|
|
25
25
|
void handle_event(const SDL_Event *event);
|
|
26
26
|
|
|
27
|
+
/** Apply the hovered element's CSS `cursor` to the window. */
|
|
28
|
+
void applyCursor(XtGuiWindow *win, const xtgui::Node *node);
|
|
29
|
+
|
|
27
30
|
} // namespace guidev
|
|
28
31
|
|
|
29
32
|
#endif /* XT_GUI_INTERNAL_H */
|
|
@@ -7,12 +7,21 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
#include <memory>
|
|
10
|
+
#include <unordered_map>
|
|
10
11
|
|
|
11
12
|
#include "gui_engine.h"
|
|
12
13
|
#include "gui_internal.h"
|
|
14
|
+
#include "webgl_feature.h"
|
|
13
15
|
|
|
14
16
|
namespace {
|
|
15
17
|
|
|
18
|
+
/** WebGL contexts are owned here rather than on `XtGuiWindow` so the core
|
|
19
|
+
* window type does not need `webgl.h`. Entries are dropped by
|
|
20
|
+
* `xt_gui_webgl_shutdown` when a window closes or reloads its document. */
|
|
21
|
+
std::unordered_map<XtGuiWindow *,
|
|
22
|
+
std::unordered_map<const xtgui::Node *, std::unique_ptr<xtgui::WebGLContext>>>
|
|
23
|
+
g_webgl;
|
|
24
|
+
|
|
16
25
|
bool isCanvasElement(const xtgui::Node *node) {
|
|
17
26
|
return node != nullptr && node->isElement() && node->isTag("canvas");
|
|
18
27
|
}
|
|
@@ -29,8 +38,10 @@ uint64_t next_webgl_id() {
|
|
|
29
38
|
|
|
30
39
|
xtgui::WebGLContext *xt_gui_webgl_find(XtGuiWindow *win, const xtgui::Node *node) {
|
|
31
40
|
if (win == nullptr || node == nullptr) return nullptr;
|
|
32
|
-
auto found =
|
|
33
|
-
|
|
41
|
+
auto found = g_webgl.find(win);
|
|
42
|
+
if (found == g_webgl.end()) return nullptr;
|
|
43
|
+
auto entry = found->second.find(node);
|
|
44
|
+
return entry == found->second.end() ? nullptr : entry->second.get();
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
xtgui::WebGLContext *xt_gui_webgl_get(XtGuiWindow *win, const xtgui::Node *node, bool webgl2) {
|
|
@@ -57,7 +68,7 @@ xtgui::WebGLContext *xt_gui_webgl_get(XtGuiWindow *win, const xtgui::Node *node,
|
|
|
57
68
|
auto context = std::make_unique<xtgui::WebGLContext>(
|
|
58
69
|
next_webgl_id(), (int)(width + 0.5f), (int)(height + 0.5f), webgl2);
|
|
59
70
|
xtgui::WebGLContext *raw = context.get();
|
|
60
|
-
win
|
|
71
|
+
g_webgl[win][node] = std::move(context);
|
|
61
72
|
if (win->document != nullptr) {
|
|
62
73
|
win->document->setCanvasInfo(node, (float)raw->width(), (float)raw->height(), raw->id());
|
|
63
74
|
}
|
|
@@ -66,18 +77,22 @@ xtgui::WebGLContext *xt_gui_webgl_get(XtGuiWindow *win, const xtgui::Node *node,
|
|
|
66
77
|
|
|
67
78
|
void xt_gui_webgl_forget(XtGuiWindow *win, const xtgui::Node *node) {
|
|
68
79
|
if (win == nullptr || node == nullptr) return;
|
|
69
|
-
|
|
80
|
+
auto found = g_webgl.find(win);
|
|
81
|
+
if (found != g_webgl.end()) found->second.erase(node);
|
|
70
82
|
}
|
|
71
83
|
|
|
72
84
|
void xt_gui_webgl_shutdown(XtGuiWindow *win) {
|
|
73
85
|
if (win == nullptr) return;
|
|
74
|
-
|
|
86
|
+
g_webgl.erase(win);
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
bool xt_gui_webgl_submit_all() {
|
|
90
|
+
bool any = false;
|
|
91
|
+
for (auto &window : g_webgl) {
|
|
92
|
+
for (auto &entry : window.second) {
|
|
93
|
+
entry.second->submit();
|
|
94
|
+
any = true;
|
|
95
|
+
}
|
|
82
96
|
}
|
|
97
|
+
return any;
|
|
83
98
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — core-side registry for the optional image/SVG table.
|
|
3
|
+
*
|
|
4
|
+
* Kept in `gui.a`, free of stb_image/NanoSVG: the table is installed by the
|
|
5
|
+
* `images` feature bridge when that feature is linked, and stays null otherwise.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#include "image_api.h"
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
const XtImageApi *g_api = nullptr;
|
|
13
|
+
|
|
14
|
+
} // namespace
|
|
15
|
+
|
|
16
|
+
void xt_image_api_install(const XtImageApi *api) { g_api = api; }
|
|
17
|
+
|
|
18
|
+
const XtImageApi *xt_image_api() { return g_api; }
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — optional image/SVG decoding table.
|
|
3
|
+
*
|
|
4
|
+
* `<img>`, CSS `background-image`, `drawImage` and inline `<svg>` all need
|
|
5
|
+
* stb_image/NanoSVG, which together are a noticeable part of a DOM-only binary.
|
|
6
|
+
* The `images` link feature installs this table from a bridge; without it, the
|
|
7
|
+
* core's image/SVG entry points are no-ops and the page simply draws nothing for
|
|
8
|
+
* those sources (the element and its layout still exist).
|
|
9
|
+
*/
|
|
10
|
+
#ifndef XT_GUI_IMAGE_API_H
|
|
11
|
+
#define XT_GUI_IMAGE_API_H
|
|
12
|
+
|
|
13
|
+
#include <cstddef>
|
|
14
|
+
#include <string>
|
|
15
|
+
|
|
16
|
+
#include "image.h"
|
|
17
|
+
|
|
18
|
+
struct XtImageApi {
|
|
19
|
+
const xtgui::Image *(*load)(const std::string &path, int target_width, int target_height);
|
|
20
|
+
bool (*size)(const std::string &path, int *width, int *height);
|
|
21
|
+
bool (*decode)(const unsigned char *data, size_t size, xtgui::Image *out);
|
|
22
|
+
bool (*is_vector)(const std::string &path);
|
|
23
|
+
void (*shutdown)();
|
|
24
|
+
bool (*svg_size)(const std::string &path, int *width, int *height);
|
|
25
|
+
bool (*svg_render)(const std::string &path, int width, int height, xtgui::Image *out);
|
|
26
|
+
bool (*svg_markup_size)(const std::string &markup, int *width, int *height);
|
|
27
|
+
bool (*svg_markup_render)(const std::string &markup, int width, int height, xtgui::Image *out);
|
|
28
|
+
int (*svg_markup_shapes)(const std::string &markup);
|
|
29
|
+
bool (*inline_is_root)(const void *node);
|
|
30
|
+
/** Serialize an inline `<svg>`; `styles` is the document's computed-style map
|
|
31
|
+
* (`const std::unordered_map<const Node*, XtStyle>*`, or null). */
|
|
32
|
+
std::string (*inline_serialize)(const void *node, const void *styles);
|
|
33
|
+
/** Set the window icon from the embedded PNG (a no-op without an icon). */
|
|
34
|
+
void (*apply_icon)(void *window);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Install the table. Called by the `images` feature bridge before `main`. */
|
|
38
|
+
void xt_image_api_install(const XtImageApi *api);
|
|
39
|
+
|
|
40
|
+
/** The installed table, or null when the `images` feature was not linked. */
|
|
41
|
+
const XtImageApi *xt_image_api();
|
|
42
|
+
|
|
43
|
+
#endif /* XT_GUI_IMAGE_API_H */
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
|
|
17
17
|
#include "image_bitmap.h"
|
|
18
18
|
#include "canvas.h"
|
|
19
|
-
#include "
|
|
19
|
+
#include "image_api.h"
|
|
20
|
+
#include "webgl_api.h"
|
|
20
21
|
|
|
21
22
|
namespace {
|
|
22
23
|
|
|
@@ -63,7 +64,9 @@ bool source_pixels(xt_value source, xtgui::Image *out) {
|
|
|
63
64
|
if (node == nullptr || !node->isElement()) return false;
|
|
64
65
|
if (node->isTag("img")) {
|
|
65
66
|
const std::string *src = node->attr("src");
|
|
66
|
-
const
|
|
67
|
+
const XtImageApi *images = xt_image_api();
|
|
68
|
+
const xtgui::Image *image =
|
|
69
|
+
src != nullptr && images != nullptr ? images->load(*src, 0, 0) : nullptr;
|
|
67
70
|
if (image == nullptr) return false;
|
|
68
71
|
*out = *image;
|
|
69
72
|
return true;
|
|
@@ -73,16 +76,22 @@ bool source_pixels(xt_value source, xtgui::Image *out) {
|
|
|
73
76
|
*out = canvas->image();
|
|
74
77
|
return true;
|
|
75
78
|
}
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
if (const XtWebglApi *webgl = xt_webgl_api()) {
|
|
80
|
+
if (void *context = webgl->find(win, node)) {
|
|
81
|
+
float width = 0.0f;
|
|
82
|
+
float height = 0.0f;
|
|
83
|
+
uint64_t id = 0;
|
|
84
|
+
webgl->info(context, &width, &height, &id);
|
|
85
|
+
out->width = (int)width;
|
|
86
|
+
out->height = (int)height;
|
|
87
|
+
out->pixels.resize((size_t)out->width * out->height * 4);
|
|
88
|
+
if (!webgl->read_pixels(context, 0, 0, out->width, out->height, out->pixels.data())) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
/* readPixels is GL-ordered (bottom-up); bitmaps are stored top-down. */
|
|
92
|
+
flip_rows(&out->pixels, out->width, out->height);
|
|
93
|
+
return true;
|
|
82
94
|
}
|
|
83
|
-
/* readPixels is GL-ordered (bottom-up); bitmaps are stored top-down. */
|
|
84
|
-
flip_rows(&out->pixels, out->width, out->height);
|
|
85
|
-
return true;
|
|
86
95
|
}
|
|
87
96
|
return false;
|
|
88
97
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — `images` link feature registration bridge.
|
|
3
|
+
*
|
|
4
|
+
* Compiled and linked only when the compiler detects that the program may draw
|
|
5
|
+
* an image or SVG. Its constructor installs `XtImageApi` and references the
|
|
6
|
+
* stb_image/NanoSVG members, pulling them out of `gui.a`; without it they are
|
|
7
|
+
* never linked.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
extern void xt_image_feature_register(void);
|
|
11
|
+
|
|
12
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
13
|
+
__attribute__((constructor)) static void xt_image_feature_boot(void) {
|
|
14
|
+
xt_image_feature_register();
|
|
15
|
+
}
|
|
16
|
+
#elif defined(_MSC_VER)
|
|
17
|
+
void xt_image_feature_boot(void);
|
|
18
|
+
#pragma section(".CRT$XCU", read)
|
|
19
|
+
__declspec(allocate(".CRT$XCU")) static void (*const xt_image_feature_ctor)(void) =
|
|
20
|
+
xt_image_feature_boot;
|
|
21
|
+
void xt_image_feature_boot(void) { xt_image_feature_register(); }
|
|
22
|
+
#endif
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — `images` link feature implementation.
|
|
3
|
+
*
|
|
4
|
+
* The only core-side place that references stb_image/NanoSVG and the inline-SVG
|
|
5
|
+
* serializer. `image_bridge.c` (linked only when the feature is active) calls
|
|
6
|
+
* `xt_image_feature_register`, which installs `XtImageApi`; without it the core
|
|
7
|
+
* draws no images or SVG and those members stay out of the binary.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "image_api.h"
|
|
11
|
+
|
|
12
|
+
#include <SDL3/SDL.h>
|
|
13
|
+
|
|
14
|
+
#include <cstring>
|
|
15
|
+
|
|
16
|
+
#include "inline_svg.h"
|
|
17
|
+
#include "svg.h"
|
|
18
|
+
|
|
19
|
+
/* Embedded by the driver's icon object; a zero size means "no icon". */
|
|
20
|
+
extern "C" {
|
|
21
|
+
extern const unsigned char xt_app_icon_data[];
|
|
22
|
+
extern const unsigned long long xt_app_icon_size;
|
|
23
|
+
extern const char xt_app_icon_format[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
namespace {
|
|
27
|
+
|
|
28
|
+
const xtgui::Image *load(const std::string &path, int target_width, int target_height) {
|
|
29
|
+
return xtgui::xt_image_load(path, target_width, target_height);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
bool size(const std::string &path, int *width, int *height) {
|
|
33
|
+
return xtgui::xt_image_size(path, width, height);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
bool decode(const unsigned char *data, size_t size, xtgui::Image *out) {
|
|
37
|
+
return xtgui::xt_image_decode(data, size, out);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
bool is_vector(const std::string &path) { return xtgui::xt_image_is_vector(path); }
|
|
41
|
+
|
|
42
|
+
void shutdown() { xtgui::xt_image_shutdown(); }
|
|
43
|
+
|
|
44
|
+
bool svg_size(const std::string &path, int *width, int *height) {
|
|
45
|
+
return xtgui::xt_svg_size(path, width, height);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
bool svg_render(const std::string &path, int width, int height, xtgui::Image *out) {
|
|
49
|
+
return xtgui::xt_svg_render(path, width, height, out);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
bool svg_markup_size(const std::string &markup, int *width, int *height) {
|
|
53
|
+
return xtgui::xt_svg_markup_size(markup, width, height);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
bool svg_markup_render(const std::string &markup, int width, int height, xtgui::Image *out) {
|
|
57
|
+
return xtgui::xt_svg_markup_render(markup, width, height, out);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
int svg_markup_shapes(const std::string &markup) {
|
|
61
|
+
return xtgui::xt_svg_markup_shapes(markup);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
bool inline_is_root(const void *node) {
|
|
65
|
+
return xtgui::xt_inline_svg_is_root(static_cast<const xtgui::Node *>(node));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
std::string inline_serialize(const void *node, const void *styles) {
|
|
69
|
+
using StyleMap = std::unordered_map<const xtgui::Node *, xtgui::XtStyle>;
|
|
70
|
+
return xtgui::xt_inline_svg_serialize(static_cast<const xtgui::Node *>(node),
|
|
71
|
+
static_cast<const StyleMap *>(styles));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
void apply_icon(void *window) {
|
|
75
|
+
SDL_Window *sdl_window = static_cast<SDL_Window *>(window);
|
|
76
|
+
const size_t size = (size_t)xt_app_icon_size;
|
|
77
|
+
if (sdl_window == nullptr || size == 0) return;
|
|
78
|
+
if (strcmp(xt_app_icon_format, "png") != 0) return;
|
|
79
|
+
xtgui::Image image;
|
|
80
|
+
if (!xtgui::xt_image_decode(xt_app_icon_data, size, &image)) return;
|
|
81
|
+
if (image.width <= 0 || image.height <= 0) return;
|
|
82
|
+
SDL_Surface *surface = SDL_CreateSurfaceFrom(
|
|
83
|
+
image.width, image.height, SDL_PIXELFORMAT_RGBA32, image.pixels.data(), image.width * 4);
|
|
84
|
+
if (surface == nullptr) return;
|
|
85
|
+
SDL_SetWindowIcon(sdl_window, surface);
|
|
86
|
+
SDL_DestroySurface(surface);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const XtImageApi kImageApi = {
|
|
90
|
+
load,
|
|
91
|
+
size,
|
|
92
|
+
decode,
|
|
93
|
+
is_vector,
|
|
94
|
+
shutdown,
|
|
95
|
+
svg_size,
|
|
96
|
+
svg_render,
|
|
97
|
+
svg_markup_size,
|
|
98
|
+
svg_markup_render,
|
|
99
|
+
svg_markup_shapes,
|
|
100
|
+
inline_is_root,
|
|
101
|
+
inline_serialize,
|
|
102
|
+
apply_icon,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
} // namespace
|
|
106
|
+
|
|
107
|
+
extern "C" void xt_image_feature_register(void) { xt_image_api_install(&kImageApi); }
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
#include "inline_svg.h"
|
|
6
6
|
|
|
7
7
|
#include <cctype>
|
|
8
|
+
#include <cstdio>
|
|
8
9
|
#include <string>
|
|
9
10
|
#include <unordered_map>
|
|
11
|
+
#include <utility>
|
|
12
|
+
#include <vector>
|
|
10
13
|
|
|
11
14
|
namespace xtgui {
|
|
12
15
|
namespace {
|
|
@@ -161,26 +164,99 @@ bool isHtmlAttr(const std::string &name) {
|
|
|
161
164
|
return name == "tabindex" || name == "role" || name == "contenteditable";
|
|
162
165
|
}
|
|
163
166
|
|
|
164
|
-
|
|
167
|
+
const std::vector<const char *> &svgStyleOrder() {
|
|
168
|
+
static const std::vector<const char *> order = {
|
|
169
|
+
"fill", "fill-opacity", "fill-rule", "stroke",
|
|
170
|
+
"stroke-opacity", "stroke-width", "stroke-dasharray", "stroke-dashoffset",
|
|
171
|
+
"stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stop-color",
|
|
172
|
+
"stop-opacity", "paint-order"};
|
|
173
|
+
return order;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
bool isColorStyleProperty(const std::string &name) {
|
|
177
|
+
return name == "fill" || name == "stroke" || name == "stop-color";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const char *opacityFor(const std::string &name) {
|
|
181
|
+
if (name == "fill") return "fill-opacity";
|
|
182
|
+
if (name == "stroke") return "stroke-opacity";
|
|
183
|
+
if (name == "stop-color") return "stop-opacity";
|
|
184
|
+
return nullptr;
|
|
185
|
+
}
|
|
165
186
|
|
|
166
|
-
|
|
187
|
+
/** Build the inline `style="…"` attribute for one SVG element from the
|
|
188
|
+
* document cascade. Colours are rewritten as `#rrggbb` plus a `*-opacity`
|
|
189
|
+
* because NanoSVG's reader has no `rgba()` syntax. */
|
|
190
|
+
std::string computedSvgStyle(const XtStyle &style) {
|
|
191
|
+
std::vector<std::pair<std::string, std::string>> declarations;
|
|
192
|
+
for (const char *name : svgStyleOrder()) {
|
|
193
|
+
auto found = style.svg_props.find(name);
|
|
194
|
+
if (found == style.svg_props.end()) continue;
|
|
195
|
+
const std::string &value = found->second;
|
|
196
|
+
if (isColorStyleProperty(name)) {
|
|
197
|
+
Color color = xt_css_parse_color(value);
|
|
198
|
+
if (color.valid) {
|
|
199
|
+
char hex[16];
|
|
200
|
+
std::snprintf(hex, sizeof(hex), "#%02x%02x%02x", (int)(color.r * 255.0f + 0.5f),
|
|
201
|
+
(int)(color.g * 255.0f + 0.5f), (int)(color.b * 255.0f + 0.5f));
|
|
202
|
+
declarations.emplace_back(name, hex);
|
|
203
|
+
const char *opacity_name = opacityFor(name);
|
|
204
|
+
bool opacity_set = style.svg_props.find(opacity_name) != style.svg_props.end();
|
|
205
|
+
if (!opacity_set && color.a < 0.999f) {
|
|
206
|
+
char alpha[32];
|
|
207
|
+
std::snprintf(alpha, sizeof(alpha), "%.3f", (double)color.a);
|
|
208
|
+
declarations.emplace_back(opacity_name, alpha);
|
|
209
|
+
}
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
declarations.emplace_back(name, value);
|
|
214
|
+
}
|
|
215
|
+
std::string text;
|
|
216
|
+
for (const auto &declaration : declarations) {
|
|
217
|
+
if (!text.empty()) text.push_back(';');
|
|
218
|
+
text += declaration.first;
|
|
219
|
+
text.push_back(':');
|
|
220
|
+
text += declaration.second;
|
|
221
|
+
}
|
|
222
|
+
return text;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
void serializeNode(const Node *node, const std::unordered_map<const Node *, XtStyle> *styles,
|
|
226
|
+
std::string *out);
|
|
227
|
+
|
|
228
|
+
void serializeStyleText(const Node *node, const std::unordered_map<const Node *, XtStyle> *styles,
|
|
229
|
+
std::string *out) {
|
|
167
230
|
for (const std::unique_ptr<Node> &child : node->children) {
|
|
168
231
|
if (child->isText()) {
|
|
169
232
|
out->append(child->text);
|
|
170
233
|
} else if (child->isElement()) {
|
|
171
|
-
serializeNode(child.get(), out);
|
|
234
|
+
serializeNode(child.get(), styles, out);
|
|
172
235
|
}
|
|
173
236
|
}
|
|
174
237
|
}
|
|
175
238
|
|
|
176
|
-
void serializeNode(const Node *node, std::
|
|
239
|
+
void serializeNode(const Node *node, const std::unordered_map<const Node *, XtStyle> *styles,
|
|
240
|
+
std::string *out) {
|
|
177
241
|
if (node == nullptr || !node->isElement()) return;
|
|
242
|
+
const XtStyle *computed = nullptr;
|
|
243
|
+
if (styles != nullptr) {
|
|
244
|
+
auto found = styles->find(node);
|
|
245
|
+
if (found != styles->end()) computed = &found->second;
|
|
246
|
+
}
|
|
247
|
+
std::string injected;
|
|
248
|
+
if (computed != nullptr && !computed->svg_props.empty()) injected = computedSvgStyle(*computed);
|
|
249
|
+
const std::string *raw_style = node->attr("style");
|
|
250
|
+
|
|
178
251
|
std::string tag = canonicalTag(node->tag);
|
|
179
252
|
out->push_back('<');
|
|
180
253
|
out->append(tag);
|
|
181
254
|
if (node->isTag("svg")) out->append(" xmlns=\"http://www.w3.org/2000/svg\"");
|
|
182
255
|
for (const Attr &attr : node->attrs) {
|
|
183
256
|
if (isNamespaceAttr(attr.name) || isHtmlAttr(attr.name)) continue;
|
|
257
|
+
/* The computed style already includes the element's own `style="…"`
|
|
258
|
+
* declarations; emitting both would be a duplicate XML attribute. */
|
|
259
|
+
if (!injected.empty() && attr.name == "style") continue;
|
|
184
260
|
/* The root's xmlns is emitted above, so a second copy would be an XML
|
|
185
261
|
* duplicate-attribute error. */
|
|
186
262
|
const char *name = canonicalAttr(attr.name);
|
|
@@ -190,12 +266,22 @@ void serializeNode(const Node *node, std::string *out) {
|
|
|
190
266
|
escapeAttribute(attr.value, out);
|
|
191
267
|
out->push_back('"');
|
|
192
268
|
}
|
|
269
|
+
if (!injected.empty()) {
|
|
270
|
+
/* The computed declarations include the element's own inline ones; merge
|
|
271
|
+
* them so unrelated inline properties (display, opacity, …) survive. */
|
|
272
|
+
std::string merged = (raw_style != nullptr) ? *raw_style : std::string();
|
|
273
|
+
if (!merged.empty() && merged.back() != ';') merged.push_back(';');
|
|
274
|
+
merged += injected;
|
|
275
|
+
out->append(" style=\"");
|
|
276
|
+
escapeAttribute(merged, out);
|
|
277
|
+
out->push_back('"');
|
|
278
|
+
}
|
|
193
279
|
out->push_back('>');
|
|
194
280
|
if (node->isTag("style")) {
|
|
195
|
-
serializeStyleText(node, out);
|
|
281
|
+
serializeStyleText(node, styles, out);
|
|
196
282
|
} else {
|
|
197
283
|
for (const std::unique_ptr<Node> &child : node->children) {
|
|
198
|
-
if (child->isElement()) serializeNode(child.get(), out);
|
|
284
|
+
if (child->isElement()) serializeNode(child.get(), styles, out);
|
|
199
285
|
}
|
|
200
286
|
}
|
|
201
287
|
out->append("</");
|
|
@@ -209,10 +295,11 @@ bool xt_inline_svg_is_root(const Node *node) {
|
|
|
209
295
|
return node != nullptr && node->isElement() && node->isTag("svg");
|
|
210
296
|
}
|
|
211
297
|
|
|
212
|
-
std::string xt_inline_svg_serialize(
|
|
298
|
+
std::string xt_inline_svg_serialize(
|
|
299
|
+
const Node *node, const std::unordered_map<const Node *, XtStyle> *styles) {
|
|
213
300
|
if (!xt_inline_svg_is_root(node)) return std::string();
|
|
214
301
|
std::string out;
|
|
215
|
-
serializeNode(node, &out);
|
|
302
|
+
serializeNode(node, styles, &out);
|
|
216
303
|
return out;
|
|
217
304
|
}
|
|
218
305
|
|