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
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — CSS transitions and `@keyframes` animations.
|
|
3
|
+
*
|
|
4
|
+
* The animatable value switch lives in `animation_values.cpp`; this unit owns
|
|
5
|
+
* the document clock: which transitions/animations are active, how local time
|
|
6
|
+
* maps to an iteration progress, and how the displayed style map is rebuilt
|
|
7
|
+
* before layout.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "document.h"
|
|
11
|
+
|
|
12
|
+
#include <algorithm>
|
|
13
|
+
#include <cmath>
|
|
14
|
+
#include <string>
|
|
15
|
+
#include <vector>
|
|
16
|
+
|
|
17
|
+
#include "style_internal.h"
|
|
18
|
+
|
|
19
|
+
namespace xtgui {
|
|
20
|
+
|
|
21
|
+
namespace {
|
|
22
|
+
|
|
23
|
+
/** A cubic Bézier with endpoints (0,0) and (1,1), evaluated at `t` on the *x*
|
|
24
|
+
* axis. CSS uses this for `cubic-bezier()` and for its `ease*` keywords. The
|
|
25
|
+
* solver is a few bisection steps: timing functions are evaluated at most once
|
|
26
|
+
* per animated property per frame, so a closed form is not worth the edge cases
|
|
27
|
+
* of a degenerate curve. */
|
|
28
|
+
float cubicBezier(float t, float x1, float y1, float x2, float y2) {
|
|
29
|
+
if (t <= 0.0f) return 0.0f;
|
|
30
|
+
if (t >= 1.0f) return 1.0f;
|
|
31
|
+
auto sample = [](float u, float p1, float p2) {
|
|
32
|
+
float v = 1.0f - u;
|
|
33
|
+
return 3.0f * v * v * u * p1 + 3.0f * v * u * u * p2 + u * u * u;
|
|
34
|
+
};
|
|
35
|
+
float low = 0.0f;
|
|
36
|
+
float high = 1.0f;
|
|
37
|
+
float u = t;
|
|
38
|
+
for (int i = 0; i < 24; i++) {
|
|
39
|
+
float x = sample(u, x1, x2);
|
|
40
|
+
if (x < t) low = u;
|
|
41
|
+
else high = u;
|
|
42
|
+
u = (low + high) * 0.5f;
|
|
43
|
+
}
|
|
44
|
+
return sample(u, y1, y2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
float easeProgress(float t, const TimingFunction &timing) {
|
|
48
|
+
if (t <= 0.0f) {
|
|
49
|
+
if (timing.kind == TimingFunction::Kind::StepsStart) {
|
|
50
|
+
float steps = timing.steps > 0 ? (float)timing.steps : 1.0f;
|
|
51
|
+
return 1.0f / steps;
|
|
52
|
+
}
|
|
53
|
+
return 0.0f;
|
|
54
|
+
}
|
|
55
|
+
if (t >= 1.0f) return 1.0f;
|
|
56
|
+
switch (timing.kind) {
|
|
57
|
+
case TimingFunction::Kind::Linear:
|
|
58
|
+
return t;
|
|
59
|
+
case TimingFunction::Kind::EaseIn:
|
|
60
|
+
return cubicBezier(t, 0.42f, 0.0f, 1.0f, 1.0f);
|
|
61
|
+
case TimingFunction::Kind::EaseOut:
|
|
62
|
+
return cubicBezier(t, 0.0f, 0.0f, 0.58f, 1.0f);
|
|
63
|
+
case TimingFunction::Kind::EaseInOut:
|
|
64
|
+
return cubicBezier(t, 0.42f, 0.0f, 0.58f, 1.0f);
|
|
65
|
+
case TimingFunction::Kind::CubicBezier:
|
|
66
|
+
return cubicBezier(t, timing.x1, timing.y1, timing.x2, timing.y2);
|
|
67
|
+
case TimingFunction::Kind::StepsStart: {
|
|
68
|
+
float steps = timing.steps > 0 ? (float)timing.steps : 1.0f;
|
|
69
|
+
float value = std::ceil(t * steps) / steps;
|
|
70
|
+
return value > 1.0f ? 1.0f : value;
|
|
71
|
+
}
|
|
72
|
+
case TimingFunction::Kind::StepsEnd: {
|
|
73
|
+
float steps = timing.steps > 0 ? (float)timing.steps : 1.0f;
|
|
74
|
+
if (t >= 1.0f) return 1.0f;
|
|
75
|
+
return std::floor(t * steps) / steps;
|
|
76
|
+
}
|
|
77
|
+
case TimingFunction::Kind::Ease:
|
|
78
|
+
default:
|
|
79
|
+
return cubicBezier(t, 0.25f, 0.1f, 0.25f, 1.0f);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
bool timingEqual(const TimingFunction &a, const TimingFunction &b) {
|
|
84
|
+
return a.kind == b.kind && a.x1 == b.x1 && a.y1 == b.y1 && a.x2 == b.x2 && a.y2 == b.y2 &&
|
|
85
|
+
a.steps == b.steps;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
bool animationSpecEqual(const AnimationSpec &a, const AnimationSpec &b) {
|
|
89
|
+
return a.name == b.name && a.duration == b.duration && a.delay == b.delay &&
|
|
90
|
+
a.iterations == b.iterations && a.direction == b.direction && a.fill == b.fill &&
|
|
91
|
+
a.paused == b.paused && timingEqual(a.timing, b.timing);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
bool specMatches(const std::string &spec, const std::string &property) {
|
|
95
|
+
return spec == "all" || spec == property;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
bool directionReversed(const AnimationSpec &spec, float iteration) {
|
|
99
|
+
int parity = (int)std::fmod(std::floor(iteration), 2.0f);
|
|
100
|
+
switch (spec.direction) {
|
|
101
|
+
case AnimationDirection::Normal:
|
|
102
|
+
return false;
|
|
103
|
+
case AnimationDirection::Reverse:
|
|
104
|
+
return true;
|
|
105
|
+
case AnimationDirection::Alternate:
|
|
106
|
+
return parity != 0;
|
|
107
|
+
case AnimationDirection::AlternateReverse:
|
|
108
|
+
return parity == 0;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
float directionProgress(const AnimationSpec &spec, float iteration, float fraction) {
|
|
114
|
+
return directionReversed(spec, iteration) ? 1.0f - fraction : fraction;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
struct AnimationProgress {
|
|
118
|
+
bool apply = false;
|
|
119
|
+
float progress = 0.0f;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Resolve the animation's local time to a progress in [0,1], honouring the
|
|
123
|
+
* iteration count, direction and fill mode. The timing function is applied per
|
|
124
|
+
* keyframe interval by the caller, as CSS specifies. */
|
|
125
|
+
AnimationProgress animationProgress(const AnimationSpec &spec, float local_ms) {
|
|
126
|
+
AnimationProgress result;
|
|
127
|
+
if (spec.name == "none" || spec.iterations == 0.0f) return result;
|
|
128
|
+
const float duration = spec.duration * 1000.0f;
|
|
129
|
+
const bool backwards =
|
|
130
|
+
spec.fill == AnimationFillMode::Backwards || spec.fill == AnimationFillMode::Both;
|
|
131
|
+
const bool forwards =
|
|
132
|
+
spec.fill == AnimationFillMode::Forwards || spec.fill == AnimationFillMode::Both;
|
|
133
|
+
if (local_ms < 0.0f) {
|
|
134
|
+
if (!backwards) return result;
|
|
135
|
+
result.apply = true;
|
|
136
|
+
result.progress = directionProgress(spec, 0.0f, 0.0f);
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
if (duration <= 0.0f) {
|
|
140
|
+
if (!forwards) return result;
|
|
141
|
+
result.apply = true;
|
|
142
|
+
result.progress = directionProgress(spec, 0.0f, 1.0f);
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
const bool infinite = spec.iterations < 0.0f;
|
|
146
|
+
const float iterations = infinite ? 0.0f : spec.iterations;
|
|
147
|
+
float t = local_ms / duration;
|
|
148
|
+
if (!infinite && t >= iterations) {
|
|
149
|
+
if (!forwards) return result;
|
|
150
|
+
float last = std::ceil(iterations) - 1.0f;
|
|
151
|
+
if (last < 0.0f) last = 0.0f;
|
|
152
|
+
float fraction = iterations - last;
|
|
153
|
+
result.apply = true;
|
|
154
|
+
result.progress = directionProgress(spec, last, fraction);
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
if (spec.paused) t = 0.0f;
|
|
158
|
+
float index = std::floor(t);
|
|
159
|
+
float fraction = t - index;
|
|
160
|
+
result.apply = true;
|
|
161
|
+
result.progress = directionProgress(spec, index, fraction);
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
void applyKeyframeDeclarations(XtStyle *style, const std::vector<Declaration> &declarations,
|
|
166
|
+
const ApplyContext &context) {
|
|
167
|
+
for (const Declaration &declaration : declarations) {
|
|
168
|
+
/* Custom properties and animation bookkeeping cannot change a keyframe
|
|
169
|
+
* result; the engine's own value parser handles everything else. */
|
|
170
|
+
if (declaration.property.size() > 2 && declaration.property[0] == '-' &&
|
|
171
|
+
declaration.property[1] == '-')
|
|
172
|
+
continue;
|
|
173
|
+
if (declaration.property.compare(0, 9, "animation") == 0) continue;
|
|
174
|
+
xt_style_apply_declaration(style, declaration.property, declaration.value, context);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
} // namespace
|
|
179
|
+
|
|
180
|
+
void XtDocument::startTransitions() {
|
|
181
|
+
if (display_.empty()) return; // first style computation: nothing to animate from
|
|
182
|
+
/* Without a `transition` declaration anywhere in the sheet (or in an inline
|
|
183
|
+
* style) no transition can start, so skip the O(nodes) scan `restyle` would
|
|
184
|
+
* otherwise do every time. */
|
|
185
|
+
if (!has_transitions_) return;
|
|
186
|
+
const std::vector<std::string> &properties = animatableProperties();
|
|
187
|
+
for (auto &entry : styles_) {
|
|
188
|
+
const Node *node = entry.first;
|
|
189
|
+
const XtStyle &target = entry.second;
|
|
190
|
+
/* Generated boxes are rebuilt on every restyle; a transition keyed by one
|
|
191
|
+
* would address a node that no longer exists. */
|
|
192
|
+
if (node->is_pseudo) continue;
|
|
193
|
+
if (target.transitions.empty()) continue;
|
|
194
|
+
auto prev_it = display_.find(node);
|
|
195
|
+
if (prev_it == display_.end()) continue;
|
|
196
|
+
const XtStyle ¤t = prev_it->second;
|
|
197
|
+
for (const TransitionSpec &spec : target.transitions) {
|
|
198
|
+
if (spec.duration <= 0.0f) continue;
|
|
199
|
+
for (const std::string &property : properties) {
|
|
200
|
+
if (!specMatches(spec.property, property)) continue;
|
|
201
|
+
AnimatableValue from;
|
|
202
|
+
AnimatableValue to;
|
|
203
|
+
if (!readAnimatable(current, property, &from)) continue;
|
|
204
|
+
if (!readAnimatable(target, property, &to)) continue;
|
|
205
|
+
if (animatableEqual(from, to)) continue;
|
|
206
|
+
AnimatableValue probe;
|
|
207
|
+
/* Values that cannot be mixed (a unit change, a different shadow list
|
|
208
|
+
* shape) snap instead of animating, as in CSS's discrete fallback. */
|
|
209
|
+
if (!interpolateAnimatable(from, to, 0.5f, &probe)) continue;
|
|
210
|
+
ActiveTransition running;
|
|
211
|
+
running.node = node;
|
|
212
|
+
running.property = property;
|
|
213
|
+
running.from = from;
|
|
214
|
+
running.to = to;
|
|
215
|
+
running.timing = spec.timing;
|
|
216
|
+
running.duration_ms = spec.duration * 1000.0f;
|
|
217
|
+
running.delay_ms = spec.delay * 1000.0f;
|
|
218
|
+
running.start_ms = (float)clock_ms_ + running.delay_ms;
|
|
219
|
+
/* Retarget an existing transition (mid-flight) instead of stacking. */
|
|
220
|
+
bool replaced = false;
|
|
221
|
+
for (ActiveTransition &existing : transitions_) {
|
|
222
|
+
if (existing.node == node && existing.property == property) {
|
|
223
|
+
existing = running;
|
|
224
|
+
replaced = true;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (!replaced) transitions_.push_back(running);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
void XtDocument::startAnimations() {
|
|
235
|
+
if (!has_animations_) {
|
|
236
|
+
animations_.clear();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
std::vector<ActiveAnimation> active;
|
|
240
|
+
for (auto &entry : styles_) {
|
|
241
|
+
const Node *node = entry.first;
|
|
242
|
+
for (const AnimationSpec &spec : entry.second.animations) {
|
|
243
|
+
if (spec.name.empty() || spec.name == "none") continue;
|
|
244
|
+
const ActiveAnimation *existing = nullptr;
|
|
245
|
+
for (const ActiveAnimation &candidate : animations_) {
|
|
246
|
+
if (candidate.node == node && candidate.name == spec.name) {
|
|
247
|
+
existing = &candidate;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
ActiveAnimation running;
|
|
252
|
+
running.node = node;
|
|
253
|
+
running.name = spec.name;
|
|
254
|
+
running.spec = spec;
|
|
255
|
+
running.start_ms =
|
|
256
|
+
(existing != nullptr && animationSpecEqual(existing->spec, spec)) ? existing->start_ms
|
|
257
|
+
: (float)clock_ms_;
|
|
258
|
+
active.push_back(std::move(running));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
animations_ = std::move(active);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
bool declarationsAffect(const std::vector<Declaration> &declarations,
|
|
265
|
+
const std::string &property) {
|
|
266
|
+
for (const Declaration &declaration : declarations) {
|
|
267
|
+
const std::string &name = declaration.property;
|
|
268
|
+
if (name == property) return true;
|
|
269
|
+
if (name == "background" && property.compare(0, 11, "background-") == 0) return true;
|
|
270
|
+
if (name == "border" && property.compare(0, 7, "border-") == 0) return true;
|
|
271
|
+
if (name == "margin" && property.compare(0, 7, "margin-") == 0) return true;
|
|
272
|
+
if (name == "padding" && property.compare(0, 8, "padding-") == 0) return true;
|
|
273
|
+
if (name == "inset" &&
|
|
274
|
+
(property == "top" || property == "right" || property == "bottom" ||
|
|
275
|
+
property == "left"))
|
|
276
|
+
return true;
|
|
277
|
+
if (name == "gap" && (property == "row-gap" || property == "column-gap")) return true;
|
|
278
|
+
if (name == "flex" && property == "flex-basis") return true;
|
|
279
|
+
if (name == "font" && property == "font-size") return true;
|
|
280
|
+
}
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
void XtDocument::applyAnimations(std::unordered_map<const Node *, XtStyle> *out) {
|
|
285
|
+
if (animations_.empty() || out == nullptr) return;
|
|
286
|
+
const std::vector<std::string> &properties = animatableProperties();
|
|
287
|
+
for (const ActiveAnimation &animation : animations_) {
|
|
288
|
+
auto node_it = out->find(animation.node);
|
|
289
|
+
if (node_it == out->end()) continue;
|
|
290
|
+
const KeyframesRule *rule = xt_css_find_keyframes(sheet_, animation.name);
|
|
291
|
+
if (rule == nullptr || rule->keyframes.empty()) continue;
|
|
292
|
+
AnimationProgress progress = animationProgress(
|
|
293
|
+
animation.spec, (float)clock_ms_ - animation.start_ms - animation.spec.delay * 1000.0f);
|
|
294
|
+
if (!progress.apply) continue;
|
|
295
|
+
|
|
296
|
+
ApplyContext context;
|
|
297
|
+
const XtStyle *base_style = &node_it->second;
|
|
298
|
+
auto target_it = styles_.find(animation.node);
|
|
299
|
+
if (target_it != styles_.end()) base_style = &target_it->second;
|
|
300
|
+
context.rootFontSize = 16.0f;
|
|
301
|
+
context.parentFontSize = base_style->font_size;
|
|
302
|
+
context.viewportWidth = viewportWidth_;
|
|
303
|
+
context.viewportHeight = viewportHeight_;
|
|
304
|
+
/* One style per keyframe step: applying the declarations once lets the
|
|
305
|
+
* per-property search below see through shorthands such as `background`.
|
|
306
|
+
* Missing keyframe values come from the cascade target, not from a
|
|
307
|
+
* transition that may be mid-flight on another property. */
|
|
308
|
+
std::vector<XtStyle> step_styles(rule->keyframes.size(), *base_style);
|
|
309
|
+
for (size_t i = 0; i < rule->keyframes.size(); i++) {
|
|
310
|
+
applyKeyframeDeclarations(&step_styles[i], rule->keyframes[i].declarations, context);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
for (const std::string &property : properties) {
|
|
314
|
+
/* Missing keyframes are the underlying style, not the adjacent step that
|
|
315
|
+
* happens to declare other properties. A transform declared at 0% and
|
|
316
|
+
* 100% therefore animates across the whole run even if 50% only changes
|
|
317
|
+
* `opacity`. */
|
|
318
|
+
long lower = -1;
|
|
319
|
+
long upper = -1;
|
|
320
|
+
for (size_t i = 0; i < rule->keyframes.size(); i++) {
|
|
321
|
+
if (!declarationsAffect(rule->keyframes[i].declarations, property)) continue;
|
|
322
|
+
if (rule->keyframes[i].offset <= progress.progress) lower = (long)i;
|
|
323
|
+
if (upper < 0 && rule->keyframes[i].offset >= progress.progress) upper = (long)i;
|
|
324
|
+
}
|
|
325
|
+
if (lower < 0 && upper < 0) continue;
|
|
326
|
+
const XtStyle &from_style = lower >= 0 ? step_styles[(size_t)lower] : *base_style;
|
|
327
|
+
const XtStyle &to_style = upper >= 0 ? step_styles[(size_t)upper] : *base_style;
|
|
328
|
+
float segment_start = lower >= 0 ? rule->keyframes[(size_t)lower].offset : 0.0f;
|
|
329
|
+
float segment_end = upper >= 0 ? rule->keyframes[(size_t)upper].offset : 1.0f;
|
|
330
|
+
float segment = 1.0f;
|
|
331
|
+
float span = segment_end - segment_start;
|
|
332
|
+
if (span > 0.00001f) {
|
|
333
|
+
segment = (progress.progress - segment_start) / span;
|
|
334
|
+
if (segment < 0.0f) segment = 0.0f;
|
|
335
|
+
if (segment > 1.0f) segment = 1.0f;
|
|
336
|
+
}
|
|
337
|
+
float eased = easeProgress(segment, animation.spec.timing);
|
|
338
|
+
|
|
339
|
+
AnimatableValue from;
|
|
340
|
+
AnimatableValue to;
|
|
341
|
+
if (!readAnimatable(from_style, property, &from)) continue;
|
|
342
|
+
if (!readAnimatable(to_style, property, &to)) continue;
|
|
343
|
+
AnimatableValue current;
|
|
344
|
+
bool has_current = readAnimatable(node_it->second, property, ¤t);
|
|
345
|
+
if (animatableEqual(from, to) && has_current && animatableEqual(current, from)) continue;
|
|
346
|
+
AnimatableValue mixed;
|
|
347
|
+
if (!interpolateAnimatable(from, to, eased, &mixed)) {
|
|
348
|
+
mixed = eased < 0.5f ? from : to;
|
|
349
|
+
}
|
|
350
|
+
writeAnimatable(&node_it->second, property, mixed);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** True while an animation can still change the painted style. A finished
|
|
356
|
+
* `forwards`/`both` animation stays in `animations_` (its value must survive a
|
|
357
|
+
* later restyle) but no longer makes every frame dirty. */
|
|
358
|
+
bool animationRunning(const ActiveAnimation &animation, double clock_ms) {
|
|
359
|
+
if (animation.spec.paused) return false;
|
|
360
|
+
if (animation.spec.name == "none" || animation.spec.iterations == 0.0f) return false;
|
|
361
|
+
if (animation.spec.iterations < 0.0f) return true;
|
|
362
|
+
double end = (double)animation.start_ms + (double)animation.spec.delay * 1000.0 +
|
|
363
|
+
(double)animation.spec.duration * 1000.0 * (double)animation.spec.iterations;
|
|
364
|
+
return clock_ms < end;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
void XtDocument::updateDisplay() {
|
|
368
|
+
/* Without a transition or animation the displayed style *is* the cascade
|
|
369
|
+
* result: skip the per-node deep copy of the whole style map (and keep
|
|
370
|
+
* `display_` empty, which `styleOf` already falls back from). */
|
|
371
|
+
if (!has_transitions_ && !has_animations_) {
|
|
372
|
+
if (!transitions_.empty()) transitions_.clear();
|
|
373
|
+
if (!animations_.empty()) animations_.clear();
|
|
374
|
+
if (!display_.empty()) display_.clear();
|
|
375
|
+
layout_.compute(root_.get(), styles_, viewportWidth_, viewportHeight_, &mediaSizes_,
|
|
376
|
+
&canvasInfo_);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
display_ = styles_;
|
|
380
|
+
for (size_t i = 0; i < transitions_.size();) {
|
|
381
|
+
ActiveTransition &transition = transitions_[i];
|
|
382
|
+
auto node_it = display_.find(transition.node);
|
|
383
|
+
if (node_it == display_.end()) {
|
|
384
|
+
transitions_.erase(transitions_.begin() + (long)i);
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
float elapsed = (float)clock_ms_ - transition.start_ms;
|
|
388
|
+
float progress = transition.duration_ms > 0.0f ? elapsed / transition.duration_ms : 1.0f;
|
|
389
|
+
if (progress > 1.0f) progress = 1.0f;
|
|
390
|
+
float eased = easeProgress(progress, transition.timing);
|
|
391
|
+
AnimatableValue mixed;
|
|
392
|
+
if (interpolateAnimatable(transition.from, transition.to, eased, &mixed)) {
|
|
393
|
+
writeAnimatable(&node_it->second, transition.property, mixed);
|
|
394
|
+
}
|
|
395
|
+
if (progress >= 1.0f) {
|
|
396
|
+
transitions_.erase(transitions_.begin() + (long)i);
|
|
397
|
+
} else {
|
|
398
|
+
i++;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
applyAnimations(&display_);
|
|
402
|
+
layout_.compute(root_.get(), display_, viewportWidth_, viewportHeight_, &mediaSizes_,
|
|
403
|
+
&canvasInfo_);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
bool XtDocument::advance(double delta_ms) {
|
|
407
|
+
const double previous = clock_ms_;
|
|
408
|
+
clock_ms_ += delta_ms;
|
|
409
|
+
if (transitions_.empty() && animations_.empty()) return false;
|
|
410
|
+
bool running = !transitions_.empty();
|
|
411
|
+
bool just_finished = false;
|
|
412
|
+
for (const ActiveAnimation &animation : animations_) {
|
|
413
|
+
if (animationRunning(animation, clock_ms_)) {
|
|
414
|
+
running = true;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (animation.spec.paused || animation.spec.iterations < 0.0f) continue;
|
|
418
|
+
double end = (double)animation.start_ms + (double)animation.spec.delay * 1000.0 +
|
|
419
|
+
(double)animation.spec.duration * 1000.0 * (double)animation.spec.iterations;
|
|
420
|
+
if (previous < end && clock_ms_ >= end) just_finished = true;
|
|
421
|
+
}
|
|
422
|
+
if (!running && !just_finished) return false;
|
|
423
|
+
updateDisplay();
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
} // namespace xtgui
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
#include "canvas.h"
|
|
17
17
|
#include "css.h"
|
|
18
|
+
#include "image_api.h"
|
|
18
19
|
|
|
19
20
|
#define METHOD(name) xt_value name(xt_value self, xt_value env, int32_t argc, xt_value *argv)
|
|
20
21
|
|
|
@@ -87,6 +88,8 @@ METHOD(node_get_context) {
|
|
|
87
88
|
xtgui::Node *node = resolve_node(self, &win);
|
|
88
89
|
if (mode == "webgl" || mode == "experimental-webgl" || mode == "webgl2") {
|
|
89
90
|
const bool webgl2 = mode == "webgl2";
|
|
91
|
+
const XtWebglApi *webgl = xt_webgl_api();
|
|
92
|
+
if (webgl == nullptr) return XT_NULL;
|
|
90
93
|
if (node == nullptr) return XT_NULL;
|
|
91
94
|
if (!node->isElement() || !node->isTag("canvas")) return XT_NULL;
|
|
92
95
|
if (xt_gui_canvas_find(win, node) != nullptr) return XT_NULL; // already a 2D canvas
|
|
@@ -95,13 +98,14 @@ METHOD(node_get_context) {
|
|
|
95
98
|
const bool cached_webgl2 = value_truthy(xt_object_get_cstr(cached, "__xt_gl_webgl2"));
|
|
96
99
|
return cached_webgl2 == webgl2 ? cached : XT_NULL;
|
|
97
100
|
}
|
|
98
|
-
|
|
99
|
-
return
|
|
101
|
+
void *context = webgl->get(win, node, webgl2);
|
|
102
|
+
return webgl->handle(self, context);
|
|
100
103
|
}
|
|
101
104
|
if (mode != "2d") return XT_NULL;
|
|
102
105
|
if (node == nullptr) return xt_canvas_pending_context(self, win);
|
|
103
106
|
if (!node->isElement() || !node->isTag("canvas")) return XT_NULL;
|
|
104
|
-
|
|
107
|
+
const XtWebglApi *webgl_api = xt_webgl_api();
|
|
108
|
+
if (webgl_api != nullptr && webgl_api->find(win, node) != nullptr) return XT_NULL; // already WebGL
|
|
105
109
|
xt_value cached = xt_object_get_cstr(self, "__xt_gui_ctx");
|
|
106
110
|
if (XT_IS_OBJECT(cached)) return cached;
|
|
107
111
|
xtgui::Canvas2D *canvas = xt_gui_canvas_get(win, node);
|
|
@@ -483,12 +487,15 @@ METHOD(canvas_draw_image) {
|
|
|
483
487
|
XtGuiWindow *win = nullptr;
|
|
484
488
|
xt_value source_value = xt_arg(argc, argv, 0);
|
|
485
489
|
const xtgui::Image *image = nullptr;
|
|
490
|
+
const XtImageApi *images = xt_image_api();
|
|
486
491
|
if (XT_IS_STRING(source_value)) {
|
|
487
492
|
/* Engine extension: the source may be a path or a `data:` URL itself, so a
|
|
488
493
|
* data-URL bitmap/SVG can be drawn without an `<img>` element. */
|
|
489
494
|
const std::string source_url(xt_string_data(source_value));
|
|
490
|
-
|
|
491
|
-
|
|
495
|
+
if (images != nullptr) {
|
|
496
|
+
image = images->load(source_url, raster_target(destination_width),
|
|
497
|
+
raster_target(destination_height));
|
|
498
|
+
}
|
|
492
499
|
} else {
|
|
493
500
|
/* An `OffscreenCanvas` (or a transferred one) has no DOM node; its bitmap
|
|
494
501
|
* is reachable through the process-wide canvas id. */
|
|
@@ -503,9 +510,9 @@ METHOD(canvas_draw_image) {
|
|
|
503
510
|
xtgui::Node *source = resolve_node(source_value, &win);
|
|
504
511
|
if (source != nullptr && source->isElement() && source->isTag("img")) {
|
|
505
512
|
const std::string *src = source->attr("src");
|
|
506
|
-
if (src != nullptr) {
|
|
507
|
-
image =
|
|
508
|
-
|
|
513
|
+
if (src != nullptr && images != nullptr) {
|
|
514
|
+
image = images->load(*src, raster_target(destination_width),
|
|
515
|
+
raster_target(destination_height));
|
|
509
516
|
}
|
|
510
517
|
} else if (source != nullptr) {
|
|
511
518
|
xtgui::Canvas2D *other = xt_gui_canvas_find(win, source);
|
|
@@ -290,8 +290,14 @@ METHOD(node_width_get) {
|
|
|
290
290
|
xtgui::Node *node = resolve_node(self, &win);
|
|
291
291
|
if (node == nullptr || !node->isElement()) return xt_number(0);
|
|
292
292
|
if (node->isTag("canvas")) {
|
|
293
|
-
if (
|
|
294
|
-
|
|
293
|
+
if (const XtWebglApi *webgl = xt_webgl_api()) {
|
|
294
|
+
if (void *context = webgl->find(win, node)) {
|
|
295
|
+
float width = 0.0f;
|
|
296
|
+
float height = 0.0f;
|
|
297
|
+
uint64_t id = 0;
|
|
298
|
+
webgl->info(context, &width, &height, &id);
|
|
299
|
+
return xt_number(width);
|
|
300
|
+
}
|
|
295
301
|
}
|
|
296
302
|
xtgui::Canvas2D *canvas = xt_gui_canvas_find(win, node);
|
|
297
303
|
if (canvas != nullptr) return xt_number(canvas->width());
|
|
@@ -310,16 +316,22 @@ METHOD(node_width_set) {
|
|
|
310
316
|
if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
|
|
311
317
|
double value = canvas_number_arg(argc, argv, 0, 0.0);
|
|
312
318
|
if (node->isTag("canvas")) {
|
|
313
|
-
if (
|
|
314
|
-
webgl->
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
if (const XtWebglApi *webgl = xt_webgl_api()) {
|
|
320
|
+
if (void *context = webgl->find(win, node)) {
|
|
321
|
+
float width = 0.0f;
|
|
322
|
+
float height = 0.0f;
|
|
323
|
+
uint64_t id = 0;
|
|
324
|
+
webgl->info(context, &width, &height, &id);
|
|
325
|
+
webgl->resize(context, (int)value, (int)height);
|
|
326
|
+
/* A resize replaces the colour texture; the page has to re-upload the
|
|
327
|
+
* image op even when the box size did not change. */
|
|
328
|
+
mark_dirty(win);
|
|
329
|
+
if (win->document != nullptr) {
|
|
330
|
+
webgl->info(context, &width, &height, &id);
|
|
331
|
+
win->document->setCanvasInfo(node, width, height, id);
|
|
332
|
+
}
|
|
333
|
+
return XT_UNDEFINED;
|
|
321
334
|
}
|
|
322
|
-
return XT_UNDEFINED;
|
|
323
335
|
}
|
|
324
336
|
xtgui::Canvas2D *canvas = xt_gui_canvas_find(win, node);
|
|
325
337
|
if (canvas != nullptr) {
|
|
@@ -355,8 +367,14 @@ METHOD(node_height_get) {
|
|
|
355
367
|
xtgui::Node *node = resolve_node(self, &win);
|
|
356
368
|
if (node == nullptr || !node->isElement()) return xt_number(0);
|
|
357
369
|
if (node->isTag("canvas")) {
|
|
358
|
-
if (
|
|
359
|
-
|
|
370
|
+
if (const XtWebglApi *webgl = xt_webgl_api()) {
|
|
371
|
+
if (void *context = webgl->find(win, node)) {
|
|
372
|
+
float width = 0.0f;
|
|
373
|
+
float height = 0.0f;
|
|
374
|
+
uint64_t id = 0;
|
|
375
|
+
webgl->info(context, &width, &height, &id);
|
|
376
|
+
return xt_number(height);
|
|
377
|
+
}
|
|
360
378
|
}
|
|
361
379
|
xtgui::Canvas2D *canvas = xt_gui_canvas_find(win, node);
|
|
362
380
|
if (canvas != nullptr) return xt_number(canvas->height());
|
|
@@ -374,14 +392,20 @@ METHOD(node_height_set) {
|
|
|
374
392
|
if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
|
|
375
393
|
double value = canvas_number_arg(argc, argv, 0, 0.0);
|
|
376
394
|
if (node->isTag("canvas")) {
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
395
|
+
if (const XtWebglApi *webgl = xt_webgl_api()) {
|
|
396
|
+
if (void *context = webgl->find(win, node)) {
|
|
397
|
+
float width = 0.0f;
|
|
398
|
+
float height = 0.0f;
|
|
399
|
+
uint64_t id = 0;
|
|
400
|
+
webgl->info(context, &width, &height, &id);
|
|
401
|
+
webgl->resize(context, (int)width, (int)value);
|
|
402
|
+
mark_dirty(win);
|
|
403
|
+
if (win->document != nullptr) {
|
|
404
|
+
webgl->info(context, &width, &height, &id);
|
|
405
|
+
win->document->setCanvasInfo(node, width, height, id);
|
|
406
|
+
}
|
|
407
|
+
return XT_UNDEFINED;
|
|
383
408
|
}
|
|
384
|
-
return XT_UNDEFINED;
|
|
385
409
|
}
|
|
386
410
|
xtgui::Canvas2D *canvas = xt_gui_canvas_find(win, node);
|
|
387
411
|
if (canvas != nullptr) {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
#include "canvas.h"
|
|
17
17
|
#include "image.h"
|
|
18
|
+
#include "image_api.h"
|
|
18
19
|
|
|
19
20
|
namespace domapi {
|
|
20
21
|
|
|
@@ -68,7 +69,8 @@ bool source_image(XtGuiWindow *win, xt_value value, xtgui::Image *out) {
|
|
|
68
69
|
if (node->isTag("img")) {
|
|
69
70
|
const std::string *src = node->attr("src");
|
|
70
71
|
if (src == nullptr) return false;
|
|
71
|
-
const
|
|
72
|
+
const XtImageApi *images = xt_image_api();
|
|
73
|
+
const xtgui::Image *image = images != nullptr ? images->load(*src, 0, 0) : nullptr;
|
|
72
74
|
if (image == nullptr || image->width <= 0 || image->height <= 0) return false;
|
|
73
75
|
*out = *image;
|
|
74
76
|
return true;
|
|
@@ -137,6 +137,16 @@ METHOD(node_text_content_set) {
|
|
|
137
137
|
xtgui::Node *node = resolve_node(self, &win);
|
|
138
138
|
if (node == nullptr || !node->isElement()) return XT_UNDEFINED;
|
|
139
139
|
std::string text = argc >= 1 ? to_string_value(xt_arg(argc, argv, 0)) : "";
|
|
140
|
+
/* Writing the value the node already shows is a no-op. Rebuilding the subtree
|
|
141
|
+
* would drop and re-allocate a text node, dirty the document and force a full
|
|
142
|
+
* restyle — and HUD-style code assigns the same string every frame (a clock
|
|
143
|
+
* that only changes once a second, a health label that does not move). */
|
|
144
|
+
if (node->children.empty()) {
|
|
145
|
+
if (text.empty()) return XT_UNDEFINED;
|
|
146
|
+
} else if (node->children.size() == 1 && node->children[0]->isText() &&
|
|
147
|
+
node->children[0]->text == text) {
|
|
148
|
+
return XT_UNDEFINED;
|
|
149
|
+
}
|
|
140
150
|
std::vector<xtgui::Node *> existing;
|
|
141
151
|
for (const std::unique_ptr<xtgui::Node> &child : node->children) existing.push_back(child.get());
|
|
142
152
|
for (xtgui::Node *child : existing) win->document->removeChild(node, child);
|
|
@@ -200,7 +200,9 @@ METHOD(canvas_transfer_control_to_offscreen) {
|
|
|
200
200
|
if (win == nullptr || node == nullptr || !node->isElement() || !node->isTag("canvas")) {
|
|
201
201
|
return XT_NULL;
|
|
202
202
|
}
|
|
203
|
-
if (
|
|
203
|
+
if (const XtWebglApi *webgl = xt_webgl_api()) {
|
|
204
|
+
if (webgl->find(win, node) != nullptr) return XT_NULL;
|
|
205
|
+
}
|
|
204
206
|
xtgui::Canvas2D *canvas = xt_gui_canvas_get(win, node);
|
|
205
207
|
if (canvas == nullptr) return XT_NULL;
|
|
206
208
|
xt_value handle = make_offscreen(canvas, win->object);
|
|
@@ -56,6 +56,11 @@ static void set_style_property(XtGuiWindow *win, xtgui::Node *node, const std::s
|
|
|
56
56
|
bool found = false;
|
|
57
57
|
for (xtgui::Declaration &decl : decls) {
|
|
58
58
|
if (decl.property == name) {
|
|
59
|
+
/* Assigning the value that is already there must not dirty the document:
|
|
60
|
+
* a restyle costs O(nodes × rules) and HUD code rewrites the same width /
|
|
61
|
+
* transform / offset every frame. Rewriting the attribute is also a
|
|
62
|
+
* parse-and-rebuild of the whole inline style string. */
|
|
63
|
+
if (decl.value == value && decl.important == important) return;
|
|
59
64
|
decl.value = value;
|
|
60
65
|
decl.important = important;
|
|
61
66
|
found = true;
|
|
@@ -102,6 +107,8 @@ static void remove_style_property(XtGuiWindow *win, xtgui::Node *node, const std
|
|
|
102
107
|
for (const xtgui::Declaration &decl : decls) {
|
|
103
108
|
if (decl.property != name) kept.push_back(decl);
|
|
104
109
|
}
|
|
110
|
+
/* Removing a property that was never set is a no-op, exactly like the CSSOM. */
|
|
111
|
+
if (kept.size() == decls.size()) return;
|
|
105
112
|
write_declarations(node, kept);
|
|
106
113
|
mark_dirty(win);
|
|
107
114
|
}
|