ranger-compiler 3.5.0 → 3.5.2
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/CHANGELOG.md +1160 -19
- package/LICENSE +3 -1
- package/README.md +124 -29
- package/dist/Lang.rgr +1188 -290
- package/dist/api.d.ts +2343 -879
- package/dist/api.js +79924 -56922
- package/dist/lib/JSON.rgr +102 -91
- package/dist/lib/Shell.rgr +4 -4
- package/dist/lib/apple/AppleToolchain.rgr +4 -4
- package/dist/lib/apple/README.md +5 -4
- package/dist/lib/apple/apple_test.rgr +1 -1
- package/dist/lib/core/README.md +1 -1
- package/dist/lib/evg/EVG.rgr +12 -0
- package/dist/lib/evg/EVGA11yFromTree.rgr +302 -0
- package/dist/lib/evg/EVGA11yTree.rgr +894 -0
- package/dist/lib/evg/EVGBox.rgr +267 -0
- package/dist/lib/evg/EVGBoxShorthandTest.rgr +220 -0
- package/dist/lib/evg/EVGCodepoint.rgr +316 -0
- package/dist/lib/evg/EVGColor.rgr +700 -0
- package/dist/lib/evg/EVGCommands.rgr +177 -0
- package/dist/lib/evg/EVGComponent.rgr +331 -0
- package/dist/lib/evg/EVGComponentTest.rgr +387 -0
- package/dist/lib/evg/EVGConnector.rgr +541 -0
- package/dist/lib/evg/EVGConnectorTest.rgr +306 -0
- package/dist/lib/evg/EVGDisplayList.rgr +4186 -0
- package/dist/lib/evg/EVGEasing.rgr +370 -0
- package/dist/lib/evg/EVGEffectTest.rgr +253 -0
- package/dist/lib/evg/EVGElement.rgr +4914 -0
- package/dist/lib/evg/EVGFixedTest.rgr +289 -0
- package/dist/lib/evg/EVGFlexRulesTest.rgr +317 -0
- package/dist/lib/evg/EVGFlexWrapTest.rgr +246 -0
- package/dist/lib/evg/EVGFling.rgr +244 -0
- package/dist/lib/evg/EVGFocus.rgr +400 -0
- package/dist/lib/evg/EVGFocusTest.rgr +399 -0
- package/dist/lib/evg/EVGGradient.rgr +322 -0
- package/dist/lib/evg/EVGGrapheme.rgr +190 -0
- package/dist/lib/evg/EVGGrid.rgr +975 -0
- package/dist/lib/evg/EVGHitTest.rgr +208 -0
- package/dist/lib/evg/EVGHoles.rgr +294 -0
- package/dist/lib/evg/EVGHostMeasurerTest.rgr +281 -0
- package/dist/lib/evg/EVGHostTextMeasurer.rgr +331 -0
- package/dist/lib/evg/EVGHostTree.rgr +834 -0
- package/dist/lib/evg/EVGHostTreeTest.rgr +447 -0
- package/dist/lib/evg/EVGImageDecode.rgr +117 -0
- package/dist/lib/evg/EVGImageMeasurer.rgr +86 -0
- package/dist/lib/evg/EVGInspect.rgr +883 -0
- package/dist/lib/evg/EVGInvalidateTest.rgr +432 -0
- package/dist/lib/evg/EVGJsonTest.rgr +535 -0
- package/dist/lib/evg/EVGLayout.rgr +4220 -0
- package/dist/lib/evg/EVGMeasure.rgr +1267 -0
- package/dist/lib/evg/EVGOverlayTest.rgr +626 -0
- package/dist/lib/evg/EVGPatch.rgr +1721 -0
- package/dist/lib/evg/EVGPatchTest.rgr +692 -0
- package/dist/lib/evg/EVGPopoverTest.rgr +395 -0
- package/dist/lib/evg/EVGReconcile.rgr +226 -0
- package/dist/lib/evg/EVGReconcileTest.rgr +421 -0
- package/dist/lib/evg/EVGReject.rgr +126 -0
- package/dist/lib/evg/EVGRelayoutTest.rgr +375 -0
- package/dist/lib/evg/EVGRtlLayoutTest.rgr +319 -0
- package/dist/lib/evg/EVGRuler.rgr +232 -0
- package/dist/lib/evg/EVGRulerTest.rgr +172 -0
- package/dist/lib/evg/EVGSelectChrome.rgr +178 -0
- package/dist/lib/evg/EVGStyleCacheTest.rgr +419 -0
- package/dist/lib/evg/EVGStyleSheet.rgr +2148 -0
- package/dist/lib/evg/EVGStyleStateTest.rgr +407 -0
- package/dist/lib/evg/EVGStyleVarTest.rgr +471 -0
- package/dist/lib/evg/EVGText.rgr +105 -0
- package/dist/lib/evg/EVGTextEngine.rgr +536 -0
- package/dist/lib/evg/EVGTextMeasurer.rgr +720 -0
- package/dist/lib/evg/EVGTimingTest.rgr +1417 -0
- package/dist/lib/evg/EVGToolbar.rgr +1369 -0
- package/dist/lib/evg/EVGTransition.rgr +728 -0
- package/dist/lib/evg/EVGTreeJson.rgr +721 -0
- package/dist/lib/evg/EVGUnit.rgr +554 -0
- package/dist/lib/evg/EVGViewportUnitTest.rgr +236 -0
- package/dist/lib/evg/EvgApp.rgr +189 -0
- package/dist/lib/evg/EvgBitmapTracer.rgr +4277 -0
- package/dist/lib/evg/EvgBitmapTracerTest.rgr +2119 -0
- package/dist/lib/evg/EvgHost.rgr +451 -0
- package/dist/lib/evg/EvgTest.rgr +91 -0
- package/dist/lib/evg/EvgTraceColor.rgr +236 -0
- package/dist/lib/evg/EvgTraceCurve.rgr +648 -0
- package/dist/lib/evg/EvgTraceFit.rgr +767 -0
- package/dist/lib/evg/EvgTracePath.rgr +435 -0
- package/dist/lib/evg/EvgTraceTypes.rgr +559 -0
- package/dist/lib/evg/EvgViewport.rgr +313 -0
- package/dist/lib/evg/FxDemoDoc.rgr +185 -0
- package/dist/lib/evg/HOSTS.md +227 -0
- package/dist/lib/evg/ISSUES.md +835 -0
- package/dist/lib/evg/PLAN_ACCESSIBILITY.md +650 -0
- package/dist/lib/evg/PLAN_CSS_LAYOUT_AND_FONTS.md +1124 -0
- package/dist/lib/evg/PLAN_EFFECTS.md +431 -0
- package/dist/lib/evg/PLAN_EVG.md +518 -0
- package/dist/lib/evg/PLAN_EVG_RENDERER.md +1674 -0
- package/dist/lib/evg/PLAN_INSPECTOR.md +788 -0
- package/dist/lib/evg/PLAN_LINKS_AND_FORMS.md +157 -0
- package/dist/lib/evg/PLAN_NATIVE_HOSTS.md +671 -0
- package/dist/lib/evg/PLAN_VECTOR_IR.md +664 -0
- package/dist/lib/evg/PLAN_VIEW_TRANSFORM.md +322 -0
- package/dist/lib/evg/PathBuilder.rgr +333 -0
- package/dist/lib/evg/README.md +1236 -0
- package/dist/lib/evg/SPEC.md +1252 -0
- package/dist/lib/evg/SVGPathParser.rgr +1199 -0
- package/dist/lib/evg/SvgParser.rgr +1736 -0
- package/dist/lib/evg/TODO_EVG_RENDERER.md +595 -0
- package/dist/lib/evg/VectorShapes.rgr +331 -0
- package/dist/lib/evg/VectorStroke.rgr +107 -0
- package/dist/lib/evg/VectorViewBox.rgr +380 -0
- package/dist/lib/evg/agent/README.md +290 -0
- package/dist/lib/evg/agent/evg_agent.rgr +899 -0
- package/dist/lib/evg/agent/fixtures/broken.evg.json +7 -0
- package/dist/lib/evg/agent/fixtures/card.evg.json +9 -0
- package/dist/lib/evg/agent/fixtures/connector.css +63 -0
- package/dist/lib/evg/agent/fixtures/connector.evg.json +14 -0
- package/dist/lib/evg/agent/fixtures/drawn.evg.json +129 -0
- package/dist/lib/evg/agent/fixtures/gradient.evg.json +4 -0
- package/dist/lib/evg/agent/fixtures/popover.css +99 -0
- package/dist/lib/evg/agent/fixtures/popover.evg.json +30 -0
- package/dist/lib/evg/agent/roundtrip.sh +74 -0
- package/dist/lib/evg/agent/smoke.sh +230 -0
- package/dist/lib/evg/android/README.md +97 -0
- package/dist/lib/evg/android/androidstubs/AndroidStubs.kt +175 -0
- package/dist/lib/evg/android/androidstubs/Annotation.kt +10 -0
- package/dist/lib/evg/android/androidstubs/App.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Content.kt +41 -0
- package/dist/lib/evg/android/androidstubs/ContentRes.kt +12 -0
- package/dist/lib/evg/android/androidstubs/InputMethod.kt +46 -0
- package/dist/lib/evg/android/androidstubs/Net.kt +6 -0
- package/dist/lib/evg/android/androidstubs/Os.kt +30 -0
- package/dist/lib/evg/android/androidstubs/Util.kt +15 -0
- package/dist/lib/evg/android/androidstubs/View.kt +113 -0
- package/dist/lib/evg/android/androidstubs/Widget.kt +16 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidEvgSurface.kt +323 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/AndroidTextMeasurer.kt +56 -0
- package/dist/lib/evg/android/src/android/kotlin/fi/ranger/evg/RippleEffect.kt +262 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtEvgSurface.kt +238 -0
- package/dist/lib/evg/android/src/awt/kotlin/fi/ranger/evg/AwtTextMeasurer.kt +85 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgEngineThread.kt +115 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgPainter.kt +231 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/EvgSurface.kt +117 -0
- package/dist/lib/evg/android/src/main/kotlin/fi/ranger/evg/RecordingSurface.kt +94 -0
- package/dist/lib/evg/apple/README.md +128 -0
- package/dist/lib/evg/apple/Sources/CoreGraphicsEvgSurface.swift +329 -0
- package/dist/lib/evg/apple/Sources/CoreTextMeasurer.swift +70 -0
- package/dist/lib/evg/apple/Sources/EvgEngineQueue.swift +105 -0
- package/dist/lib/evg/apple/Sources/EvgPainter.swift +225 -0
- package/dist/lib/evg/apple/Sources/EvgSurface.swift +163 -0
- package/dist/lib/evg/apple/Sources/RecordingSurface.swift +110 -0
- package/dist/lib/evg/bench/EvgLayoutBench.rgr +34 -0
- package/dist/lib/evg/bench/README.md +64 -0
- package/dist/lib/evg/bench/layout-bench.mjs +316 -0
- package/dist/lib/evg/bench/layout-cases.mjs +510 -0
- package/dist/lib/evg/bench/layout-conformance.mjs +254 -0
- package/dist/lib/evg/bin/.gitignore +17 -0
- package/dist/lib/evg/evg_test.rgr +313 -0
- package/dist/lib/evg/gl/README.md +198 -0
- package/dist/lib/evg/gl/a11y-paint-check.mjs +73 -0
- package/dist/lib/evg/gl/blur-check.mjs +399 -0
- package/dist/lib/evg/gl/boxmodel.json +1 -0
- package/dist/lib/evg/gl/demo.html +46 -0
- package/dist/lib/evg/gl/effect-presets.css +285 -0
- package/dist/lib/evg/gl/effect-presets.js +63 -0
- package/dist/lib/evg/gl/effect-shots.mjs +135 -0
- package/dist/lib/evg/gl/evg-a11y.js +566 -0
- package/dist/lib/evg/gl/evg-binary.js +160 -0
- package/dist/lib/evg/gl/evg-engine.js +296 -0
- package/dist/lib/evg/gl/evg-fx.js +237 -0
- package/dist/lib/evg/gl/evg-gestures.js +209 -0
- package/dist/lib/evg/gl/evg-list.js +167 -0
- package/dist/lib/evg/gl/evg-measure.js +186 -0
- package/dist/lib/evg/gl/evg-textinput.js +303 -0
- package/dist/lib/evg/gl/evg-view.js +144 -0
- package/dist/lib/evg/gl/evg-webgl.js +3942 -0
- package/dist/lib/evg/gl/fx-check.mjs +998 -0
- package/dist/lib/evg/gl/fx-demo.html +166 -0
- package/dist/lib/evg/gl/fx-demo.js +2 -0
- package/dist/lib/evg/gl/fx-serve.mjs +47 -0
- package/dist/lib/evg/gl/gestures-check.mjs +189 -0
- package/dist/lib/evg/gl/list-binary-check.mjs +152 -0
- package/dist/lib/evg/gl/measure-check.mjs +135 -0
- package/dist/lib/evg/gl/rotation-check.mjs +207 -0
- package/dist/lib/evg/gl/shift-check.mjs +113 -0
- package/dist/lib/evg/gl/stroke-check.mjs +168 -0
- package/dist/lib/evg/gl/text-snap-check.mjs +139 -0
- package/dist/lib/evg/gl/view-check.mjs +343 -0
- package/dist/lib/evg/gl/view-policy-check.mjs +184 -0
- package/dist/lib/evg/html/evg-dom.js +318 -0
- package/dist/lib/evg/html/evg-html.js +600 -0
- package/dist/lib/evg/inspect/README.md +415 -0
- package/dist/lib/evg/inspect/browser-smoke.mjs +115 -0
- package/dist/lib/evg/inspect/evg-inspect.js +947 -0
- package/dist/lib/evg/inspect/shots/css.png +0 -0
- package/dist/lib/evg/inspect/shots/dashboard.png +0 -0
- package/dist/lib/evg/inspect/shots/pptx-slide.png +0 -0
- package/dist/lib/evg/inspect/shots/state.png +0 -0
- package/dist/lib/evg/inspect/shots.mjs +226 -0
- package/dist/lib/evg/oracle/css-blur.json +576 -0
- package/dist/lib/evg/oracle/css-box.json +157 -0
- package/dist/lib/evg/oracle/css-timing.json +709 -0
- package/dist/lib/evg/oracle/css_blur_oracle.mjs +529 -0
- package/dist/lib/evg/oracle/css_box_oracle.mjs +106 -0
- package/dist/lib/evg/oracle/css_timing_oracle.mjs +389 -0
- package/dist/lib/evg/original/EVGColor.clj +310 -0
- package/dist/lib/evg/original/EVGColorContext.rgr +178 -0
- package/dist/lib/evg/original/SVGPath.rgr +627 -0
- package/dist/lib/evg/original/Vec2.crgr +103 -0
- package/dist/lib/evg/ranger.json +9 -0
- package/dist/lib/evg/showcase/README.md +246 -0
- package/dist/lib/evg/showcase/assets/emblem.svg +30 -0
- package/dist/lib/evg/showcase/assets/rosette.svg +22 -0
- package/dist/lib/evg/showcase/build.mjs +651 -0
- package/dist/lib/evg/showcase/pages/album.tsx +30 -0
- package/dist/lib/evg/showcase/pages/boxmodel.tsx +37 -0
- package/dist/lib/evg/showcase/pages/cards.tsx +41 -0
- package/dist/lib/evg/showcase/pages/chart_api.tsx +170 -0
- package/dist/lib/evg/showcase/pages/charts.tsx +219 -0
- package/dist/lib/evg/showcase/pages/drawing.tsx +160 -0
- package/dist/lib/evg/showcase/pages/emoji.tsx +91 -0
- package/dist/lib/evg/showcase/pages/flex.tsx +46 -0
- package/dist/lib/evg/showcase/pages/more.tsx +332 -0
- package/dist/lib/evg/showcase/pages/plots.tsx +262 -0
- package/dist/lib/evg/showcase/pages/svg.tsx +63 -0
- package/dist/lib/evg/showcase/pages/tables.tsx +209 -0
- package/dist/lib/evg/showcase/pages/typography.tsx +43 -0
- package/dist/lib/evg/showcase/pages/units.tsx +35 -0
- package/dist/lib/evg/showcase/pages/variants.tsx +233 -0
- package/dist/lib/evg/showcase/pages/vector.tsx +75 -0
- package/dist/lib/evg/showcase/pages/views.tsx +223 -0
- package/dist/lib/evg/showcase/tests/chart_api_smoke.mjs +339 -0
- package/dist/lib/evg/showcase/tests/gl_smoke.mjs +125 -0
- package/dist/lib/evg/showcase/themes/chart_api-default.css +15 -0
- package/dist/lib/evg/showcase/themes/charts-default.css +29 -0
- package/dist/lib/evg/showcase/themes/drawing-default.css +35 -0
- package/dist/lib/evg/showcase/themes/more-default.css +128 -0
- package/dist/lib/evg/showcase/themes/plots-default.css +18 -0
- package/dist/lib/evg/showcase/themes/showcase.css +508 -0
- package/dist/lib/evg/showcase/themes/tables-default.css +123 -0
- package/dist/lib/evg/showcase/themes/variants-default.css +15 -0
- package/dist/lib/evg/showcase/themes/views-default.css +15 -0
- package/dist/lib/evg/tools/bench_vs_potrace.mjs +290 -0
- package/dist/lib/evg/tools/evg_image_tool.rgr +488 -0
- package/dist/lib/evg/tools/evg_trace_bench.rgr +114 -0
- package/dist/lib/evg/tools/evg_trace_cli.rgr +662 -0
- package/dist/lib/evg/tools/evg_trace_cpp_bench.rgr +83 -0
- package/dist/lib/evg/tools/run_trace_bench.sh +74 -0
- package/dist/lib/evg/tools/run_trace_cli_smoke.sh +128 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveCheck.rgr +271 -0
- package/dist/lib/evg/web/responsive/EvgResponsiveDemo.rgr +531 -0
- package/dist/lib/evg/web/responsive/README.md +97 -0
- package/dist/lib/evg/web/responsive/build.mjs +90 -0
- package/dist/lib/evg/web/responsive/dom-check.mjs +169 -0
- package/dist/lib/evg/web/responsive/index.html +177 -0
- package/dist/lib/evg/web/responsive/smoke.mjs +221 -0
- package/dist/lib/evg/web/tools/assets-client.mjs +95 -0
- package/dist/lib/evg/web/tools/boot-bench.mjs +189 -0
- package/dist/lib/evg/web/tools/inline-assets.mjs +119 -0
- package/dist/lib/evg/web/tools/minify.mjs +60 -0
- package/dist/lib/evg/web/tracer/build.mjs +87 -0
- package/dist/lib/evg/web/tracer/index.html +2805 -0
- package/dist/lib/evg/web/tracer/sample.png +0 -0
- package/dist/lib/evg/web/tracer/smoke.mjs +1162 -0
- package/dist/lib/evgr/Cargo.lock +21 -0
- package/dist/lib/evgr/Cargo.toml +19 -0
- package/dist/lib/evgr/README.md +140 -0
- package/dist/lib/evgr/bench/NativeBench.rgr +246 -0
- package/dist/lib/evgr/bench/compare.mjs +252 -0
- package/dist/lib/evgr/bench/speed.mjs +277 -0
- package/dist/lib/evgr/bin/.gitignore +3 -0
- package/dist/lib/evgr/src/bin/bench.rs +106 -0
- package/dist/lib/evgr/src/bin/smoke.rs +13 -0
- package/dist/lib/evgr/src/grid.rs +730 -0
- package/dist/lib/evgr/src/lib.rs +1003 -0
- package/dist/lib/evgr/src/style.rs +468 -0
- package/dist/lib/evgr/src/text.rs +83 -0
- package/dist/lib/image/BitReader.rgr +171 -0
- package/dist/lib/image/Buffer.rgr +173 -0
- package/dist/lib/image/DCT.rgr +283 -0
- package/dist/lib/image/Deflate.rgr +339 -0
- package/dist/lib/image/HuffmanDecoder.rgr +181 -0
- package/dist/lib/image/ImageBuffer.rgr +706 -0
- package/dist/lib/image/JPEGDecoder.rgr +808 -0
- package/dist/lib/image/PNGDecoder.rgr +544 -0
- package/dist/lib/image/PNGEncoder.rgr +388 -0
- package/dist/lib/image/PPMImage.rgr +224 -0
- package/dist/lib/image/ProgressiveJPEGDecoder.rgr +1154 -0
- package/dist/lib/image/README.md +33 -0
- package/dist/lib/image/RasterBuffer.rgr +294 -0
- package/dist/lib/image/VP8BoolDecoder.rgr +163 -0
- package/dist/lib/image/WebPDecoder.rgr +433 -0
- package/dist/lib/image/WebPLossless.rgr +1035 -0
- package/dist/lib/image/WebPLossy.rgr +1929 -0
- package/dist/lib/image/ranger.json +9 -0
- package/dist/lib/image/testdata/webp/anim_2f_24x16.webp +0 -0
- package/dist/lib/image/testdata/webp/fixtures.txt +33 -0
- package/dist/lib/image/testdata/webp/gen_fixtures.py +261 -0
- package/dist/lib/image/testdata/webp/ll_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_alpha_29x21.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_gradient_64x64.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_meta_32x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_noise_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal11_23x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal2_19x7.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal40_16x16.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_pal4_13x5.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_photo_40x30.webp +0 -0
- package/dist/lib/image/testdata/webp/ll_predictors_64x32.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_1x1.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_raw_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f0_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f1_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f2_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alph_vp8l_f3_21x13.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_alpha_33x17.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_exif_15x11.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_i16_96x80.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_nofilter_24x20.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_odd_17x9.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_photo_48x40.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_q100_20x18.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_sharp_40x36.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_simple_filter_33x31.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_parts8_37x45.webp +0 -0
- package/dist/lib/image/testdata/webp/ly_vpx_skip_96x80.webp +0 -0
- package/dist/lib/image/tests/WebPDecodeTool.rgr +81 -0
- package/dist/lib/image/tests/WebPDecoderTest.rgr +1162 -0
- package/dist/lib/image/tests/run_webp_tests.sh +39 -0
- package/dist/lib/rust/RsJson.rgr +468 -0
- package/dist/lib/rust/RsPrelude.rgr +1663 -0
- package/dist/lib/shell_test.rgr +7 -7
- package/dist/lib/stdlib.rgr +21 -5
- package/dist/lib/zip/ranger.json +6 -0
- package/dist/rgrc.js +87318 -67610
- package/package.json +1010 -1040
- package/dist/README.md +0 -117
- package/dist/package.json +0 -47
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// The display list as it crosses a thread: `EVGSceneBinary` in, the
|
|
4
|
+
// painter's commands out.
|
|
5
|
+
//
|
|
6
|
+
// `evg-list.js` reads the list off the Ranger OBJECT, which is what a host
|
|
7
|
+
// that shares a heap with the engine does. A host whose engine runs in a
|
|
8
|
+
// Worker (`evg-engine.js`, PLAN_NATIVE_HOSTS.md S1) cannot: what crosses is
|
|
9
|
+
// `EVGDisplayList.toBinary()` — three `Int32Array`s and a string pool,
|
|
10
|
+
// transferred rather than copied — and this is the reader. It produces the
|
|
11
|
+
// SAME command objects `cmdsOf` produces, key for key, so the painter does
|
|
12
|
+
// not know which way the list arrived. `list-binary-check.mjs` holds the two
|
|
13
|
+
// against each other on real frames.
|
|
14
|
+
//
|
|
15
|
+
// The record width is DERIVED from the buffer, never assumed: `cmds` is
|
|
16
|
+
// allocated as exactly `count * stride`, so dividing recovers the writer's
|
|
17
|
+
// number (ISSUES #4). This reader wants 36 fields; a wider record is fine and
|
|
18
|
+
// a narrower one is refused with both numbers in the message. Values are
|
|
19
|
+
// hundredths — `EVGDisplayList.fixed` rounds to what `toJson` writes — so the
|
|
20
|
+
// division below gives back exactly the JSON's numbers.
|
|
21
|
+
//
|
|
22
|
+
// Slots 36 to 40 are `letter-spacing` and a stroke's cap, join and dash, and
|
|
23
|
+
// they are read only when the buffer is wide
|
|
24
|
+
// enough to have it. Raising FIELDS_READ instead would have refused every
|
|
25
|
+
// bundle built before it existed — a hard failure for a field whose absence
|
|
26
|
+
// means "the font's own spacing", which is what those bundles meant. A slot
|
|
27
|
+
// that is optional in the data is optional in the reader.
|
|
28
|
+
|
|
29
|
+
export const FIELDS_READ = 36;
|
|
30
|
+
|
|
31
|
+
/** How many ints per command this buffer carries. */
|
|
32
|
+
export function binaryStride(bin) {
|
|
33
|
+
const n = bin.count | 0;
|
|
34
|
+
const len = bin.cmds.length;
|
|
35
|
+
if (n === 0) return FIELDS_READ;
|
|
36
|
+
const stride = (len / n) | 0;
|
|
37
|
+
if (stride * n !== len) {
|
|
38
|
+
throw new Error(`scene binary: ${len} ints do not divide into ${n} commands`);
|
|
39
|
+
}
|
|
40
|
+
if (stride < FIELDS_READ) {
|
|
41
|
+
throw new Error(`scene binary: ${stride} fields per command, but this reader wants ${FIELDS_READ}`);
|
|
42
|
+
}
|
|
43
|
+
return stride;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const rgb = (packed) => [(packed >> 16) & 255, (packed >> 8) & 255, packed & 255];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The camera on the envelope, or null when the list carried none.
|
|
50
|
+
*
|
|
51
|
+
* It is three numbers beside the page size rather than a field per command,
|
|
52
|
+
* which is why the record stride above is untouched by it: a reader that
|
|
53
|
+
* knows nothing about views reads this buffer exactly as it always did, and
|
|
54
|
+
* draws the scene unpanned — which is the right answer for a list whose host
|
|
55
|
+
* never asked for a camera.
|
|
56
|
+
*/
|
|
57
|
+
export function viewOfBinary(bin) {
|
|
58
|
+
if (!bin || !bin.hasView) return null;
|
|
59
|
+
return { x: bin.viewX || 0, y: bin.viewY || 0, scale: bin.viewScale === undefined ? 1 : bin.viewScale };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The commands, in the shape `evg-list.js`'s `cmdsOf` gives them. */
|
|
63
|
+
export function cmdsOfBinary(bin) {
|
|
64
|
+
const n = bin.count | 0;
|
|
65
|
+
const stride = binaryStride(bin);
|
|
66
|
+
const r = bin.cmds, p = bin.pts, e = bin.ends, pool = bin.strings;
|
|
67
|
+
const out = new Array(n);
|
|
68
|
+
for (let i = 0; i < n; i += 1) {
|
|
69
|
+
const b = i * stride;
|
|
70
|
+
const o = { k: r[b] };
|
|
71
|
+
const layer = r[b + 30];
|
|
72
|
+
if (layer > 0) o.layer = layer;
|
|
73
|
+
o.x = r[b + 1] / 100;
|
|
74
|
+
o.y = r[b + 2] / 100;
|
|
75
|
+
o.w = r[b + 3] / 100;
|
|
76
|
+
o.h = r[b + 4] / 100;
|
|
77
|
+
const flags = r[b + 9];
|
|
78
|
+
const radius = r[b + 5] / 100;
|
|
79
|
+
if (flags & 64) o.rc = [radius, r[b + 27] / 100, r[b + 28] / 100, r[b + 29] / 100];
|
|
80
|
+
if (radius > 0) o.r = radius;
|
|
81
|
+
const t = r[b + 6] / 100;
|
|
82
|
+
if (t > 0) {
|
|
83
|
+
o.t = t;
|
|
84
|
+
if (stride > 38) {
|
|
85
|
+
if (r[b + 37] !== 0) o.cap = r[b + 37];
|
|
86
|
+
if (r[b + 38] !== 0) o.join = r[b + 38];
|
|
87
|
+
}
|
|
88
|
+
if (stride > 40) {
|
|
89
|
+
const d = r[b + 39];
|
|
90
|
+
if (d >= 0) {
|
|
91
|
+
o.dash = pool[d];
|
|
92
|
+
const off = r[b + 40] / 100;
|
|
93
|
+
if (off !== 0) o.dashoff = off;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
o.c = [...rgb(r[b + 7]), r[b + 8] / 100];
|
|
98
|
+
if (flags & 1) {
|
|
99
|
+
o.gd = r[b + 10];
|
|
100
|
+
o.c2 = [...rgb(r[b + 11]), r[b + 12] / 100];
|
|
101
|
+
}
|
|
102
|
+
const textIdx = r[b + 15];
|
|
103
|
+
if (textIdx >= 0) {
|
|
104
|
+
o.text = pool[textIdx];
|
|
105
|
+
o.font = pool[r[b + 16]];
|
|
106
|
+
o.size = r[b + 13] / 100;
|
|
107
|
+
const w = r[b + 17];
|
|
108
|
+
if (w >= 0) o.weight = pool[w];
|
|
109
|
+
if (stride > 36) {
|
|
110
|
+
const ls = r[b + 36];
|
|
111
|
+
if (ls !== 0) o.ls = ls / 100;
|
|
112
|
+
}
|
|
113
|
+
if (flags & 2) o.italic = true;
|
|
114
|
+
}
|
|
115
|
+
const src = r[b + 18];
|
|
116
|
+
if (src >= 0) o.src = pool[src];
|
|
117
|
+
if (flags & 4) o.fx = true;
|
|
118
|
+
if (flags & 8) o.fy = true;
|
|
119
|
+
// The crop window into the source, when the picture is not shown whole.
|
|
120
|
+
// Ten-thousandths, not hundredths: a hundredth of a fraction is a whole
|
|
121
|
+
// percent of the bitmap, sixteen pixels on a wide screenshot.
|
|
122
|
+
if (stride > 44 && flags & 256) {
|
|
123
|
+
o.cu = [r[b + 41] / 10000, r[b + 42] / 10000, r[b + 43] / 10000, r[b + 44] / 10000];
|
|
124
|
+
}
|
|
125
|
+
const rot = r[b + 14] / 100;
|
|
126
|
+
if (rot !== 0) {
|
|
127
|
+
o.rot = rot;
|
|
128
|
+
if (flags & 32) {
|
|
129
|
+
o.rox = r[b + 24] / 100;
|
|
130
|
+
o.roy = r[b + 25] / 100;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const bb = r[b + 26] / 100;
|
|
134
|
+
if (bb > 0) o.bb = bb;
|
|
135
|
+
if (flags & 128) {
|
|
136
|
+
o.sh = {
|
|
137
|
+
x: r[b + 31] / 100, y: r[b + 32] / 100, blur: r[b + 33] / 100,
|
|
138
|
+
c: [...rgb(r[b + 34]), r[b + 35] / 100],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const pStart = r[b + 19], pCount = r[b + 20];
|
|
142
|
+
if (pCount > 0) {
|
|
143
|
+
const pts = new Array(pCount);
|
|
144
|
+
for (let j = 0; j < pCount; j += 1) pts[j] = p[pStart + j] / 100;
|
|
145
|
+
o.pts = pts;
|
|
146
|
+
const eStart = r[b + 21], eCount = r[b + 22];
|
|
147
|
+
const ends = new Array(eCount);
|
|
148
|
+
for (let j = 0; j < eCount; j += 1) ends[j] = e[eStart + j];
|
|
149
|
+
o.ends = ends;
|
|
150
|
+
if (flags & 16) o.eo = 1;
|
|
151
|
+
}
|
|
152
|
+
out[i] = o;
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The typed arrays a `postMessage` may TRANSFER rather than copy. */
|
|
158
|
+
export function transferablesOf(bin) {
|
|
159
|
+
return [bin.cmds.buffer, bin.pts.buffer, bin.ends.buffer];
|
|
160
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// The engine off the main thread.
|
|
4
|
+
//
|
|
5
|
+
// PLAN_NATIVE_HOSTS.md S1, the web half. An EVG app — the tree, the cascade,
|
|
6
|
+
// the layout, the display list, the hit test — is pure Ranger and needs no
|
|
7
|
+
// DOM, so it can run in a Worker, and then a layout that takes twelve
|
|
8
|
+
// milliseconds on a chart-heavy page blocks neither the pointer nor the
|
|
9
|
+
// compositor. What crosses back is the display list as `toBinary()` gives
|
|
10
|
+
// it: three `Int32Array`s and a string pool, TRANSFERRED rather than copied,
|
|
11
|
+
// read on the main thread by `evg-binary.js` into the same commands the
|
|
12
|
+
// painter has always been fed. Input crosses the other way, as the calls the
|
|
13
|
+
// host already made — `press`, `scrollDrag`, `setHover` — and the hit test
|
|
14
|
+
// happens where the tree is.
|
|
15
|
+
//
|
|
16
|
+
// One file, two halves:
|
|
17
|
+
//
|
|
18
|
+
// serveEngine(opts) the worker's side — owns the app and answers
|
|
19
|
+
// connectEngine(worker) the page's side — a proxy the host talks to
|
|
20
|
+
//
|
|
21
|
+
// THE PROTOCOL, and why it is shaped like this.
|
|
22
|
+
//
|
|
23
|
+
// post(name, ...args) a call with no answer, QUEUED — a pointer move, a
|
|
24
|
+
// key, a press. Nothing is sent yet.
|
|
25
|
+
// call(name, ...args) a call with an answer, as a promise: `hitId` for
|
|
26
|
+
// the cursor, `fieldStateJson` for the text bridge.
|
|
27
|
+
// Sent at once, with everything queued before it,
|
|
28
|
+
// so order is what the host wrote.
|
|
29
|
+
// frame(dt) the frame: every queued post, then `tick(dt)`,
|
|
30
|
+
// then the display list — ONE message each way per
|
|
31
|
+
// frame, however many events the browser delivered.
|
|
32
|
+
//
|
|
33
|
+
// The reply to `frame` is one of three things, and the host's painter
|
|
34
|
+
// already knows what to do with each — it is exactly the distinction
|
|
35
|
+
// `gallery/realtrainer/web/main.js` draws with `buildSeq` and `frameSeq`:
|
|
36
|
+
//
|
|
37
|
+
// { t: "frame", doc, shifts } a new build: buffers to upload
|
|
38
|
+
// { t: "shift", shifts } the kept list moved — a scroll — draw the
|
|
39
|
+
// frame on the card with new offsets
|
|
40
|
+
// { t: "idle" } nothing moved; do not draw
|
|
41
|
+
//
|
|
42
|
+
// Every reply carries `state`: a small object the app-side bootstrap
|
|
43
|
+
// composes from what the host reads on every frame — the scene's name, the
|
|
44
|
+
// focused field, the scroll velocity — so the host is not making a round
|
|
45
|
+
// trip for a string. What a host cannot do is READ synchronously; that is
|
|
46
|
+
// the whole change, and `main-worker.js` beside the RealTrainer page shows
|
|
47
|
+
// what a host looks like written that way.
|
|
48
|
+
|
|
49
|
+
import { cmdsOfBinary, transferablesOf } from "./evg-binary.js";
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// The worker's side
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Serve an app from inside a Worker.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} opts
|
|
59
|
+
* @param {(init: any) => object} opts.make build the app from the `init`
|
|
60
|
+
* message's payload — install the measurer FIRST (`evg-measure.js`
|
|
61
|
+
* works here through `OffscreenCanvas`), because the app keeps a
|
|
62
|
+
* layout from the moment it exists
|
|
63
|
+
* @param {(app: object) => object} opts.display the display list for the
|
|
64
|
+
* frame: `app.display()` for a host that keeps its list
|
|
65
|
+
* @param {(app: object, dt: number) => boolean} [opts.tick] the clock;
|
|
66
|
+
* default `app.tick(dt)`
|
|
67
|
+
* @param {(app: object) => object} [opts.state] what every reply carries
|
|
68
|
+
* @param {(app: object) => object|undefined} [opts.effect] the surface
|
|
69
|
+
* effect, in the JSON's shape — `effectOf` from evg-list.js
|
|
70
|
+
* @param {(app: object) => number[][]} opts.shifts the layers' shifts —
|
|
71
|
+
* `shiftsOf` from evg-list.js
|
|
72
|
+
* @param {(app: object, dl: object) => object} [opts.onBuild] what a NEW
|
|
73
|
+
* BUILD carries beside the picture — the accessibility tree, say,
|
|
74
|
+
* which changes when the tree changes and at no other time — merged
|
|
75
|
+
* into the "frame" reply as `built`
|
|
76
|
+
* @param {object} [opts.hooks] functions the host may post or call by
|
|
77
|
+
* `@name` — host logic that belongs beside the tree, such as "hit
|
|
78
|
+
* test this point and hover what is there", written once here rather
|
|
79
|
+
* than as three round trips
|
|
80
|
+
* @param {object} [opts.scope] `self` by default
|
|
81
|
+
*/
|
|
82
|
+
export function serveEngine(opts) {
|
|
83
|
+
const scope = opts.scope || self;
|
|
84
|
+
let app = null;
|
|
85
|
+
let lastList = null;
|
|
86
|
+
let lastSeq = -1;
|
|
87
|
+
let lastShifts = "";
|
|
88
|
+
const tick = opts.tick || ((a, dt) => a.tick(dt));
|
|
89
|
+
const state = opts.state || (() => ({}));
|
|
90
|
+
|
|
91
|
+
const reply = (msg, transfer) => scope.postMessage(msg, transfer || []);
|
|
92
|
+
|
|
93
|
+
const run = (name, args) => {
|
|
94
|
+
if (name.charCodeAt(0) === 64) {
|
|
95
|
+
const h = opts.hooks && opts.hooks[name.slice(1)];
|
|
96
|
+
if (typeof h !== "function") throw new Error(`engine: no hook ${name}`);
|
|
97
|
+
return h(app, ...args);
|
|
98
|
+
}
|
|
99
|
+
const f = app[name];
|
|
100
|
+
if (typeof f !== "function") throw new Error(`engine: the app has no method ${name}`);
|
|
101
|
+
return f.apply(app, args);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const frameReply = (dt, dirty) => {
|
|
105
|
+
let ticked = false;
|
|
106
|
+
if (dt !== undefined && dt !== null) ticked = !!tick(app, dt);
|
|
107
|
+
const dl = opts.display(app);
|
|
108
|
+
const seq = dl.buildSeq * 100000 + dl.frameSeq;
|
|
109
|
+
const shifts = opts.shifts(app, dl);
|
|
110
|
+
const st = state(app, dl);
|
|
111
|
+
if (dl !== lastList || seq !== lastSeq) {
|
|
112
|
+
lastList = dl;
|
|
113
|
+
lastSeq = seq;
|
|
114
|
+
lastShifts = JSON.stringify(shifts);
|
|
115
|
+
const bin = dl.toBinary();
|
|
116
|
+
const doc = {
|
|
117
|
+
width: bin.width, height: bin.height, seq,
|
|
118
|
+
bin: { count: bin.count, cmds: bin.cmds, pts: bin.pts, ends: bin.ends, strings: bin.strings },
|
|
119
|
+
};
|
|
120
|
+
const effect = opts.effect ? opts.effect(app, dl) : undefined;
|
|
121
|
+
if (effect) doc.effect = effect;
|
|
122
|
+
const built = opts.onBuild ? opts.onBuild(app, dl) : undefined;
|
|
123
|
+
reply({ t: "frame", doc, shifts, state: st, ticked, built }, transferablesOf(bin));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const sh = JSON.stringify(shifts);
|
|
127
|
+
if (sh !== lastShifts || ticked || dirty) {
|
|
128
|
+
lastShifts = sh;
|
|
129
|
+
reply({ t: "shift", shifts, state: st, ticked });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
reply({ t: "idle", state: st, ticked });
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
scope.onmessage = (ev) => {
|
|
136
|
+
const m = ev.data;
|
|
137
|
+
try {
|
|
138
|
+
switch (m.t) {
|
|
139
|
+
case "init":
|
|
140
|
+
app = opts.make(m.init);
|
|
141
|
+
reply({ t: "ready", state: state(app, null) });
|
|
142
|
+
break;
|
|
143
|
+
case "post":
|
|
144
|
+
run(m.name, m.args);
|
|
145
|
+
break;
|
|
146
|
+
case "call": {
|
|
147
|
+
const value = run(m.name, m.args);
|
|
148
|
+
reply({ t: "ret", id: m.id, value: value === undefined ? null : value });
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case "batch": {
|
|
152
|
+
let dirty = false;
|
|
153
|
+
for (const [name, args] of m.posts) {
|
|
154
|
+
const r = run(name, args);
|
|
155
|
+
if (r === true) dirty = true;
|
|
156
|
+
}
|
|
157
|
+
for (const c of m.calls) {
|
|
158
|
+
const value = run(c.name, c.args);
|
|
159
|
+
reply({ t: "ret", id: c.id, value: value === undefined ? null : value });
|
|
160
|
+
}
|
|
161
|
+
if (m.frame) frameReply(m.dt, dirty || m.dirty);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case "close":
|
|
165
|
+
scope.close();
|
|
166
|
+
break;
|
|
167
|
+
default:
|
|
168
|
+
throw new Error(`engine: unknown message ${m.t}`);
|
|
169
|
+
}
|
|
170
|
+
} catch (e) {
|
|
171
|
+
reply({ t: "error", id: m.id, message: String((e && e.stack) || e), during: m.t + ":" + (m.name || "") });
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// The page's side
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Talk to an app served by `serveEngine`.
|
|
182
|
+
*
|
|
183
|
+
* @param {Worker} worker
|
|
184
|
+
* @param {any} init what the worker's `make` is handed
|
|
185
|
+
* @returns {{
|
|
186
|
+
* ready: Promise<object>,
|
|
187
|
+
* post: (name: string, ...args: any[]) => void,
|
|
188
|
+
* call: (name: string, ...args: any[]) => Promise<any>,
|
|
189
|
+
* frame: (dt?: number, dirty?: boolean) => Promise<object>,
|
|
190
|
+
* flush: () => void,
|
|
191
|
+
* state: () => object,
|
|
192
|
+
* onError: (fn: (e: {message: string, during: string}) => void) => void,
|
|
193
|
+
* close: () => void,
|
|
194
|
+
* }}
|
|
195
|
+
*/
|
|
196
|
+
export function connectEngine(worker, init) {
|
|
197
|
+
let nextId = 1;
|
|
198
|
+
const waiting = new Map();
|
|
199
|
+
let posts = [];
|
|
200
|
+
let last = {};
|
|
201
|
+
let errorFn = (e) => console.error("engine:", e.during, e.message);
|
|
202
|
+
let readyResolve;
|
|
203
|
+
const ready = new Promise((r) => { readyResolve = r; });
|
|
204
|
+
// At most one frame in flight: a host that asks for the next frame before
|
|
205
|
+
// the last one came back is asking the worker to fall behind, and a
|
|
206
|
+
// second request is answered by the reply already coming.
|
|
207
|
+
let frameWaiting = null;
|
|
208
|
+
|
|
209
|
+
worker.onmessage = (ev) => {
|
|
210
|
+
const m = ev.data;
|
|
211
|
+
switch (m.t) {
|
|
212
|
+
case "ready":
|
|
213
|
+
last = m.state || {};
|
|
214
|
+
readyResolve(last);
|
|
215
|
+
break;
|
|
216
|
+
case "ret": {
|
|
217
|
+
const w = waiting.get(m.id);
|
|
218
|
+
if (w) { waiting.delete(m.id); w.resolve(m.value); }
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case "frame": {
|
|
222
|
+
// The buffers arrive owned by this thread; the commands are made once.
|
|
223
|
+
m.doc.list = { seq: m.doc.seq, shifts: m.shifts, cmds: cmdsOfBinary(m.doc.bin) };
|
|
224
|
+
if (m.doc.effect) m.doc.list.effect = m.doc.effect;
|
|
225
|
+
delete m.doc.bin;
|
|
226
|
+
// falls through
|
|
227
|
+
}
|
|
228
|
+
case "shift":
|
|
229
|
+
case "idle": {
|
|
230
|
+
last = m.state || last;
|
|
231
|
+
const w = frameWaiting;
|
|
232
|
+
frameWaiting = null;
|
|
233
|
+
if (w) w.resolve(m);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
case "error": {
|
|
237
|
+
if (m.id !== undefined && waiting.has(m.id)) {
|
|
238
|
+
const w = waiting.get(m.id);
|
|
239
|
+
waiting.delete(m.id);
|
|
240
|
+
w.reject(new Error(m.message));
|
|
241
|
+
} else if (frameWaiting && m.during.startsWith("batch")) {
|
|
242
|
+
const w = frameWaiting;
|
|
243
|
+
frameWaiting = null;
|
|
244
|
+
w.reject(new Error(m.message));
|
|
245
|
+
}
|
|
246
|
+
errorFn(m);
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
default:
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
worker.postMessage({ t: "init", init });
|
|
255
|
+
|
|
256
|
+
const send = (calls, frame, dt, dirty) => {
|
|
257
|
+
const batch = { t: "batch", posts, calls, frame, dt, dirty };
|
|
258
|
+
posts = [];
|
|
259
|
+
worker.postMessage(batch);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
ready,
|
|
264
|
+
post(name, ...args) {
|
|
265
|
+
posts.push([name, args]);
|
|
266
|
+
},
|
|
267
|
+
call(name, ...args) {
|
|
268
|
+
const id = nextId++;
|
|
269
|
+
return new Promise((resolve, reject) => {
|
|
270
|
+
waiting.set(id, { resolve, reject });
|
|
271
|
+
send([{ id, name, args }], false);
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
frame(dt, dirty) {
|
|
275
|
+
if (frameWaiting) return frameWaiting.promise;
|
|
276
|
+
let resolve, reject;
|
|
277
|
+
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
|
|
278
|
+
frameWaiting = { promise, resolve, reject };
|
|
279
|
+
send([], true, dt, !!dirty);
|
|
280
|
+
return promise;
|
|
281
|
+
},
|
|
282
|
+
/** Send what is queued without asking for a frame. */
|
|
283
|
+
flush() {
|
|
284
|
+
if (posts.length) send([], false);
|
|
285
|
+
},
|
|
286
|
+
state() {
|
|
287
|
+
return last;
|
|
288
|
+
},
|
|
289
|
+
onError(fn) {
|
|
290
|
+
errorFn = fn;
|
|
291
|
+
},
|
|
292
|
+
close() {
|
|
293
|
+
worker.postMessage({ t: "close" });
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// The effect driver: what turns a pointer into the events a surface effect
|
|
4
|
+
// reads, from what the DOCUMENT said rather than from what the page's code
|
|
5
|
+
// remembered to do.
|
|
6
|
+
//
|
|
7
|
+
// BEFORE THIS, the ripple was driven like this, in the demo's own main.js:
|
|
8
|
+
//
|
|
9
|
+
// canvas.addEventListener("pointerdown", (ev) => {
|
|
10
|
+
// app.ripple(ev.offsetX, ev.offsetY); // every press, anywhere
|
|
11
|
+
// });
|
|
12
|
+
//
|
|
13
|
+
// and the application kept the drops, aged them on every tick, retired them,
|
|
14
|
+
// and stamped them onto the root at paint time. Three arrays of state and four
|
|
15
|
+
// methods on a dashboard, for an effect the dashboard has no opinion about.
|
|
16
|
+
// Nothing in that chain could answer "which element ripples" — the answer was
|
|
17
|
+
// "the page", because the effect was the page's.
|
|
18
|
+
//
|
|
19
|
+
// Now the document answers it:
|
|
20
|
+
//
|
|
21
|
+
// .card { evg-surface-effect: ripple; evg-effect-on: press drag }
|
|
22
|
+
// .hero-sky { evg-surface-effect: starfield; evg-effect-on: always }
|
|
23
|
+
//
|
|
24
|
+
// and this file is the whole of the wiring:
|
|
25
|
+
//
|
|
26
|
+
// const fx = createEffectDriver();
|
|
27
|
+
// attachEffectPointer(canvas, fx, () => schedule()); // optional
|
|
28
|
+
// // per frame:
|
|
29
|
+
// fx.tick(dtMs, list); // ages the events, stamps the clock on
|
|
30
|
+
// renderDisplayList(gl, { width, height, list }, { dpr });
|
|
31
|
+
//
|
|
32
|
+
// WHAT IT OWNS is the events: where a press landed, in PAGE pixels, and how
|
|
33
|
+
// long ago. What it does not own is anything about how they look — that is the
|
|
34
|
+
// plugin's, in `evg-webgl.js` — or which element they belong to, which is the
|
|
35
|
+
// display list's, because that came from the stylesheet.
|
|
36
|
+
//
|
|
37
|
+
// WHY IT IS NOT IN THE ENGINE. The engine has no pointer. A press is a browser
|
|
38
|
+
// event on the web, a MotionEvent on Android and a UITouch on iOS, and the one
|
|
39
|
+
// thing all three have in common is that the box they landed in is already in
|
|
40
|
+
// the display list. So the driver is per host and small, and the part that is
|
|
41
|
+
// the same everywhere — which box, what trigger, how the event ages — is here
|
|
42
|
+
// rather than written again in each one.
|
|
43
|
+
|
|
44
|
+
/** How far a drag travels before it drops another event, in page pixels.
|
|
45
|
+
* Closer and the rings pile into one bright blob; further and a wake reads
|
|
46
|
+
* as a row of separate taps. */
|
|
47
|
+
const DRAG_STEP = 26;
|
|
48
|
+
|
|
49
|
+
/** The most events one instance carries. Eight is what a dragged finger fills
|
|
50
|
+
* in a third of a second, and it is what the shaders' `MAX_EVENTS` is. */
|
|
51
|
+
const MAX_EVENTS = 8;
|
|
52
|
+
|
|
53
|
+
/** How long an event lives, in seconds. The ripple's own decay has taken it
|
|
54
|
+
* under a 250th by three seconds, so this is the renderer's number and not a
|
|
55
|
+
* guess. */
|
|
56
|
+
const LIFE = 3;
|
|
57
|
+
|
|
58
|
+
/** Does this instance's trigger list mention `what`? A trigger is a list of
|
|
59
|
+
* words — `press drag` — because a card can want both and CSS has no better
|
|
60
|
+
* shape for "one or more of these".
|
|
61
|
+
*
|
|
62
|
+
* An instance the host has switched off (`inst.off`) answers no to everything,
|
|
63
|
+
* so a press on it lands on whatever is under it rather than being swallowed
|
|
64
|
+
* by an effect that is not running. The painter skips the same flag, and
|
|
65
|
+
* between them "off" means off rather than "invisible but still listening". */
|
|
66
|
+
export function triggerHas(inst, what) {
|
|
67
|
+
if (!inst || inst.off) return false;
|
|
68
|
+
const on = inst.on || "";
|
|
69
|
+
return on.length > 0 && on.split(/[\s,]+/).indexOf(what) >= 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Is the point inside the instance's box? Corners are not tested: an effect
|
|
73
|
+
* is triggered by a press on the element, and the element's rounded corner is
|
|
74
|
+
* a question about painting rather than about who was pressed. */
|
|
75
|
+
function inBox(inst, x, y) {
|
|
76
|
+
const b = inst.box;
|
|
77
|
+
return !!b && x >= b[0] && y >= b[1] && x <= b[0] + b[2] && y <= b[1] + b[3];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function createEffectDriver(opts = {}) {
|
|
81
|
+
// Per instance id: the events in flight and where the last drag event was
|
|
82
|
+
// dropped. Keyed by ID and not by object, because the display list is
|
|
83
|
+
// rebuilt — a reflow makes new instance objects for the same elements, and
|
|
84
|
+
// a ripple must not die because the window was resized.
|
|
85
|
+
const state = new Map();
|
|
86
|
+
// Seconds since the driver was made. What `always` effects run on, and the
|
|
87
|
+
// reason a starfield needs no events at all.
|
|
88
|
+
let clock = 0;
|
|
89
|
+
const life = opts.life === undefined ? LIFE : opts.life;
|
|
90
|
+
const step = opts.dragStep === undefined ? DRAG_STEP : opts.dragStep;
|
|
91
|
+
|
|
92
|
+
const stateFor = (id) => {
|
|
93
|
+
let st = state.get(id);
|
|
94
|
+
if (!st) { st = { events: [], lastX: -1, lastY: -1 }; state.set(id, st); }
|
|
95
|
+
return st;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// The instances of the list last synced, newest first — the reverse of paint
|
|
99
|
+
// order, so the TOPMOST element that wants the event gets it. Two effects
|
|
100
|
+
// that overlap are a stack, and a press belongs to the one on top.
|
|
101
|
+
let instances = [];
|
|
102
|
+
|
|
103
|
+
const fire = (x, y, what) => {
|
|
104
|
+
let hit = null;
|
|
105
|
+
for (let i = instances.length - 1; i >= 0; i -= 1) {
|
|
106
|
+
const inst = instances[i];
|
|
107
|
+
if (!triggerHas(inst, what)) continue;
|
|
108
|
+
if (!inBox(inst, x, y)) continue;
|
|
109
|
+
hit = inst;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (!hit) return null;
|
|
113
|
+
const st = stateFor(hit.id);
|
|
114
|
+
st.events.push([x, y, 0]);
|
|
115
|
+
if (st.events.length > MAX_EVENTS) st.events.shift();
|
|
116
|
+
st.lastX = x;
|
|
117
|
+
st.lastY = y;
|
|
118
|
+
return hit;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
/** The instances this driver is watching, for a host that wants to look. */
|
|
123
|
+
instances: () => instances,
|
|
124
|
+
|
|
125
|
+
/** Seconds since the driver was made. */
|
|
126
|
+
time: () => clock,
|
|
127
|
+
|
|
128
|
+
/** Take the list of the frame about to be drawn, advance every event by
|
|
129
|
+
* `dtMs`, and stamp what the painter reads onto each instance.
|
|
130
|
+
*
|
|
131
|
+
* IT MUTATES THE LIST, and that is deliberate: the instances are the
|
|
132
|
+
* painter's own objects, so a host that keeps a built frame across many
|
|
133
|
+
* draws sees the events move without rebuilding anything. Nothing in the
|
|
134
|
+
* tree changes — the clock is not in the document, which is what keeps a
|
|
135
|
+
* running effect off the layout's critical path entirely. */
|
|
136
|
+
tick(dtMs, list) {
|
|
137
|
+
const dt = (dtMs || 0) / 1000;
|
|
138
|
+
clock += dt;
|
|
139
|
+
for (const st of state.values()) {
|
|
140
|
+
if (!st.events.length) continue;
|
|
141
|
+
const kept = [];
|
|
142
|
+
for (const e of st.events) {
|
|
143
|
+
e[2] += dt;
|
|
144
|
+
if (e[2] < life) kept.push(e);
|
|
145
|
+
}
|
|
146
|
+
st.events = kept;
|
|
147
|
+
}
|
|
148
|
+
instances = (list && list.effects) || [];
|
|
149
|
+
for (const inst of instances) {
|
|
150
|
+
const st = state.get(inst.id);
|
|
151
|
+
inst.events = st ? st.events : [];
|
|
152
|
+
inst.time = clock;
|
|
153
|
+
}
|
|
154
|
+
return this.busy();
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
/** Is anything still moving? A page whose effects are all idle can stop
|
|
158
|
+
* asking for frames, which is the difference between an effect and a
|
|
159
|
+
* background process. */
|
|
160
|
+
busy() {
|
|
161
|
+
for (const inst of instances) {
|
|
162
|
+
if (inst.off) continue;
|
|
163
|
+
if (triggerHas(inst, "always")) return true;
|
|
164
|
+
const st = state.get(inst.id);
|
|
165
|
+
if (st && st.events.length) return true;
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/** A press at a point in PAGE pixels. Returns the instance it went to, or
|
|
171
|
+
* null when it landed on nothing that wanted it — which is the ordinary
|
|
172
|
+
* case, and the whole difference from a page that ripples everywhere. */
|
|
173
|
+
press(x, y) { return fire(x, y, "press"); },
|
|
174
|
+
|
|
175
|
+
/** The pointer moving with the button down. One event per step rather than
|
|
176
|
+
* one that follows the pointer: a wake is a row of sources, and a single
|
|
177
|
+
* source that moves has no history. */
|
|
178
|
+
drag(x, y) {
|
|
179
|
+
for (let i = instances.length - 1; i >= 0; i -= 1) {
|
|
180
|
+
const inst = instances[i];
|
|
181
|
+
if (!triggerHas(inst, "drag")) continue;
|
|
182
|
+
if (!inBox(inst, x, y)) continue;
|
|
183
|
+
const st = stateFor(inst.id);
|
|
184
|
+
if (st.lastX < 0) { st.lastX = x; st.lastY = y; return null; }
|
|
185
|
+
const dx = x - st.lastX, dy = y - st.lastY;
|
|
186
|
+
if (dx * dx + dy * dy < step * step) return null;
|
|
187
|
+
return fire(x, y, "drag");
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/** The pointer moving with no button down. */
|
|
193
|
+
hover(x, y) { return fire(x, y, "hover"); },
|
|
194
|
+
|
|
195
|
+
/** The button came up: the next drag starts a fresh trail rather than
|
|
196
|
+
* joining the last one. */
|
|
197
|
+
release() {
|
|
198
|
+
for (const st of state.values()) { st.lastX = -1; st.lastY = -1; }
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
/** Everything in flight, gone. For a host that navigated away from the
|
|
202
|
+
* page the events belonged to. */
|
|
203
|
+
clear() { state.clear(); },
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The web host's three lines of it. `onChange` is called whenever something
|
|
209
|
+
* happened that the page has to draw — a host that is already drawing every
|
|
210
|
+
* frame can leave it out.
|
|
211
|
+
*
|
|
212
|
+
* The coordinates are `offsetX`/`offsetY`, which are CSS pixels relative to the
|
|
213
|
+
* canvas, which is the page space the display list is in. A canvas with a
|
|
214
|
+
* camera on it has to unproject them first, and then this is the wrong helper —
|
|
215
|
+
* pass the driver the page point yourself.
|
|
216
|
+
*/
|
|
217
|
+
export function attachEffectPointer(canvas, driver, onChange) {
|
|
218
|
+
const poke = () => { if (onChange) onChange(); };
|
|
219
|
+
const down = (ev) => { if (driver.press(ev.offsetX, ev.offsetY)) poke(); };
|
|
220
|
+
const move = (ev) => {
|
|
221
|
+
const hit = ev.buttons ? driver.drag(ev.offsetX, ev.offsetY) : driver.hover(ev.offsetX, ev.offsetY);
|
|
222
|
+
if (hit) poke();
|
|
223
|
+
};
|
|
224
|
+
const up = () => driver.release();
|
|
225
|
+
canvas.addEventListener("pointerdown", down);
|
|
226
|
+
canvas.addEventListener("pointermove", move);
|
|
227
|
+
canvas.addEventListener("pointerup", up);
|
|
228
|
+
canvas.addEventListener("pointercancel", up);
|
|
229
|
+
canvas.addEventListener("pointerleave", up);
|
|
230
|
+
return () => {
|
|
231
|
+
canvas.removeEventListener("pointerdown", down);
|
|
232
|
+
canvas.removeEventListener("pointermove", move);
|
|
233
|
+
canvas.removeEventListener("pointerup", up);
|
|
234
|
+
canvas.removeEventListener("pointercancel", up);
|
|
235
|
+
canvas.removeEventListener("pointerleave", up);
|
|
236
|
+
};
|
|
237
|
+
}
|