From 2f169089922bacb4482ddeca6d24beb78a1d7e46 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Wed, 7 May 2025 17:06:38 +0800 Subject: [PATCH] feat! js_interop improvements --- examples/dart/js_wasm/pubspec.yaml | 21 + examples/dart/js_wasm/web/assets | 1 + examples/dart/js_wasm/web/example.dart | 102 + examples/dart/js_wasm/web/index_js.html | 35 + examples/dart/js_wasm/web/index_wasm.html | 35 + examples/dart/js_wasm/web/main.js | 22 + examples/dart/js_wasm/web/thermion_dart.js | 1 + examples/dart/js_wasm/web/thermion_dart.wasm | 1 + .../dart/js_wasm/web/web_input_handler.dart | 233 + examples/flutter/quickstart/web/index.html | 24 +- .../flutter/quickstart/web/thermion_dart.js | 2 +- .../flutter/quickstart/web/thermion_dart.wasm | 2 +- thermion_dart/BUILDING.md | 64 + thermion_dart/ffigen/native.yaml | 9 +- thermion_dart/ffigen/web.yaml | 23 +- thermion_dart/hook/build.dart | 15 +- thermion_dart/lib/src/bindings/bindings.dart | 3 + .../callbacks.dart => bindings/src/ffi.dart} | 115 +- .../lib/src/bindings/src/js_interop.dart | 408 + .../src/stub.dart} | 0 .../src/thermion_dart_ffi.g.dart} | 2195 ++---- .../src/thermion_dart_js_interop.g.dart | 6834 +++++++++++++++++ thermion_dart/lib/src/filament/filament.dart | 8 +- .../lib/src/filament/src/geometry.dart | 32 - .../src/implementation}/background_image.dart | 16 +- .../src/implementation}/ffi_asset.dart | 159 +- .../src/implementation}/ffi_camera.dart | 70 +- .../src/implementation}/ffi_filament_app.dart | 362 +- .../src/implementation}/ffi_gizmo.dart | 59 +- .../src/implementation}/ffi_material.dart | 57 +- .../implementation}/ffi_render_target.dart | 6 +- .../src/implementation}/ffi_scene.dart | 6 +- .../src/implementation}/ffi_swapchain.dart | 2 +- .../src/implementation}/ffi_texture.dart | 65 +- .../src/implementation}/ffi_view.dart | 33 +- .../src/implementation}/grid_overlay.dart | 6 +- .../src/implementation/resource_loader.dart | 3 + .../implementation/resource_loader_io.dart | 8 + .../implementation/resource_loader_js.dart | 11 + .../filament/src/{ => interface}/asset.dart | 2 +- .../filament/src/{ => interface}/camera.dart | 2 +- .../filament/src/{ => interface}/engine.dart | 0 .../filament/src/{ => interface}/entity.dart | 0 .../src/{ => interface}/filament_app.dart | 8 +- .../src/filament/src/interface/geometry.dart | 37 + .../filament/src/{ => interface}/gizmo.dart | 3 + .../filament/src/{ => interface}/gltf.dart | 0 .../filament/src/interface/grid_overlay.dart | 0 .../filament/src/{ => interface}/layers.dart | 0 .../filament/src/{ => interface}/light.dart | 0 .../src/{ => interface}/light_options.dart | 0 .../src/{ => interface}/manipulator.dart | 0 .../src/{ => interface}/material.dart | 2 - .../src/{ => interface}/pick_result.dart | 2 +- .../src/{ => interface}/primitive.dart | 0 .../src/{ => interface}/render_target.dart | 0 .../filament/src/{ => interface}/scene.dart | 0 .../filament/src/{ => interface}/shadow.dart | 0 .../src/{ => interface}/shared_types.dart | 0 .../src/{ => interface}/swap_chain.dart | 0 .../filament/src/{ => interface}/texture.dart | 0 .../src/{ => interface}/texture_details.dart | 0 .../src/{ => interface}/tone_mapper.dart | 0 .../filament/src/{ => interface}/view.dart | 9 +- .../fixed_orbit_camera_rotation_delegate.dart | 6 +- .../lib/src/utils/src/dart_resources.dart | 30 - thermion_dart/lib/src/utils/src/geometry.dart | 30 +- thermion_dart/lib/src/utils/src/image.dart | 1 + thermion_dart/lib/src/utils/src/matrix.dart | 17 +- .../lib/src/utils/src/texture_projection.dart | 6 +- .../src/ffi/src/thermion_viewer_ffi.dart | 61 +- .../src/viewer/src/thermion_viewer_base.dart | 6 +- .../src/viewer/src/thermion_viewer_stub.dart | 317 - .../web_js/src/thermion_viewer_js_shim.dart | 2 +- .../src/viewer/src/web_wasm/src/camera.dart | 77 - .../src/web_wasm/src/material_instance.dart | 147 - .../web_wasm/src/thermion_viewer_wasm.dart | 39 +- thermion_dart/lib/src/viewer/viewer.dart | 9 +- thermion_dart/lib/thermion_dart.dart | 2 + thermion_dart/native/include/Log.hpp | 16 +- thermion_dart/native/include/MathUtils.hpp | 2 +- thermion_dart/native/include/RenderTicker.hpp | 5 +- .../native/include/c_api/APIBoundaryTypes.h | 11 +- .../native/include/c_api/APIExport.h | 5 +- thermion_dart/native/include/c_api/TCamera.h | 45 +- thermion_dart/native/include/c_api/TEngine.h | 2 + thermion_dart/native/include/c_api/TGizmo.h | 4 +- .../native/include/c_api/TLightManager.h | 1 + .../native/include/c_api/TMaterialInstance.h | 5 + .../native/include/c_api/TRenderTicker.h | 2 +- .../native/include/c_api/TRenderer.h | 1 + thermion_dart/native/include/c_api/TTexture.h | 10 + thermion_dart/native/include/c_api/TView.h | 3 + .../c_api/ThermionDartRenderThreadApi.h | 198 +- .../native/include/material/capture_uv.h | 4 + .../native/include/material/grid.bin | Bin 52018 -> 52018 bytes thermion_dart/native/include/material/grid.c | 2388 +++--- thermion_dart/native/include/material/grid.h | 4 + .../native/include/material/image.bin | Bin 63850 -> 63850 bytes thermion_dart/native/include/material/image.c | 2786 +++---- thermion_dart/native/include/material/image.h | 4 + .../include/material/unlit_fixed_size.bin | Bin 45907 -> 45907 bytes .../include/material/unlit_fixed_size.c | 4 +- .../include/material/unlit_fixed_size.h | 4 + .../native/include/rendering/RenderThread.hpp | 37 +- .../include/resources/rotation_gizmo_glb.h | 4 + .../include/resources/translation_gizmo_glb.h | 4 + .../native/macos/ThermionTexture.swift | 4 +- thermion_dart/native/src/RenderTicker.cpp | 22 +- .../native/src/c_api/TAnimationManager.cpp | 4 + thermion_dart/native/src/c_api/TCamera.cpp | 5 +- thermion_dart/native/src/c_api/TEngine.cpp | 30 +- .../native/src/c_api/TFilamentAsset.cpp | 4 + thermion_dart/native/src/c_api/TGizmo.cpp | 4 + .../native/src/c_api/TGltfAssetLoader.cpp | 4 + .../native/src/c_api/TGltfResourceLoader.cpp | 4 + .../native/src/c_api/TIndirectLight.cpp | 4 + .../native/src/c_api/TLightManager.cpp | 3 + .../native/src/c_api/TMaterialInstance.cpp | 4 + .../native/src/c_api/TMaterialProvider.cpp | 4 + .../src/c_api/TNameComponentManager.cpp | 5 + .../native/src/c_api/TRenderTarget.cpp | 4 + .../native/src/c_api/TRenderTicker.cpp | 7 +- .../native/src/c_api/TRenderableManager.cpp | 4 + thermion_dart/native/src/c_api/TRenderer.cpp | 4 + thermion_dart/native/src/c_api/TScene.cpp | 4 + .../native/src/c_api/TSceneAsset.cpp | 3 + thermion_dart/native/src/c_api/TTexture.cpp | 4 + .../native/src/c_api/TTransformManager.cpp | 4 + thermion_dart/native/src/c_api/TView.cpp | 4 + .../src/c_api/ThermionDartRenderThreadApi.cpp | 295 +- .../native/src/rendering/RenderThread.cpp | 119 +- thermion_dart/native/web/CMakeLists.txt | 98 +- .../web/include/ThermionFlutterWebApi.h | 41 - .../native/web/include/ThermionWebApi.h | 16 + .../native/web/src/cpp/ThermionDartWebApi.cpp | 225 - .../native/web/src/cpp/ThermionWebApi.cpp | 123 + thermion_dart/pubspec.yaml | 5 +- thermion_dart/test/animation_tests.dart | 4 +- thermion_dart/test/depth_tests.dart | 18 +- thermion_dart/test/destructor_tests.dart | 2 +- thermion_dart/test/helpers.dart | 31 +- thermion_dart/test/input_handlers.mocks.dart | 2365 ------ thermion_dart/test/instancing_tests.dart | 4 +- .../test/libThermionTextureSwift.dylib | Bin 122016 -> 141840 bytes thermion_dart/test/light_tests.dart | 2 +- thermion_dart/test/postprocessing_tests.dart | 2 +- thermion_dart/test/shadow_tests.dart | 2 +- thermion_dart/test/texture_tests.dart | 2 +- .../view_dependent_texture_mapping_tests.dart | 2 +- .../lib/src/thermion_flutter_plugin.dart | 10 +- .../lib/src/widgets/src/thermion_widget.dart | 10 +- .../widgets/src/thermion_widget_web_impl.dart | 188 +- .../lib/src/widgets/src/viewer_widget.dart | 1 + ...rmion_flutter_method_channel_platform.dart | 27 +- .../thermion_flutter_platform_interface.dart | 52 +- .../lib/thermion_flutter_web.dart | 163 +- .../lib/thermion_flutter_web_options.dart | 2 +- .../thermion_flutter_web/pubspec.yaml | 6 + 159 files changed, 12989 insertions(+), 8377 deletions(-) create mode 100644 examples/dart/js_wasm/pubspec.yaml create mode 120000 examples/dart/js_wasm/web/assets create mode 100644 examples/dart/js_wasm/web/example.dart create mode 100644 examples/dart/js_wasm/web/index_js.html create mode 100644 examples/dart/js_wasm/web/index_wasm.html create mode 100644 examples/dart/js_wasm/web/main.js create mode 120000 examples/dart/js_wasm/web/thermion_dart.js create mode 120000 examples/dart/js_wasm/web/thermion_dart.wasm create mode 100644 examples/dart/js_wasm/web/web_input_handler.dart create mode 100644 thermion_dart/lib/src/bindings/bindings.dart rename thermion_dart/lib/src/{viewer/src/ffi/src/callbacks.dart => bindings/src/ffi.dart} (60%) create mode 100644 thermion_dart/lib/src/bindings/src/js_interop.dart rename thermion_dart/lib/src/{filament/src/grid_overlay.dart => bindings/src/stub.dart} (100%) rename thermion_dart/lib/src/{viewer/src/ffi/src/thermion_dart.g.dart => bindings/src/thermion_dart_ffi.g.dart} (69%) create mode 100644 thermion_dart/lib/src/bindings/src/thermion_dart_js_interop.g.dart delete mode 100644 thermion_dart/lib/src/filament/src/geometry.dart rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/background_image.dart (94%) rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/ffi_asset.dart (88%) rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/ffi_camera.dart (70%) rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/ffi_filament_app.dart (74%) rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/ffi_gizmo.dart (58%) rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/ffi_material.dart (74%) rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/ffi_render_target.dart (75%) rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/ffi_scene.dart (61%) rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/ffi_swapchain.dart (70%) rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/ffi_texture.dart (89%) rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/ffi_view.dart (87%) rename thermion_dart/lib/src/{viewer/src/ffi/src => filament/src/implementation}/grid_overlay.dart (63%) create mode 100644 thermion_dart/lib/src/filament/src/implementation/resource_loader.dart create mode 100644 thermion_dart/lib/src/filament/src/implementation/resource_loader_io.dart create mode 100644 thermion_dart/lib/src/filament/src/implementation/resource_loader_js.dart rename thermion_dart/lib/src/filament/src/{ => interface}/asset.dart (99%) rename thermion_dart/lib/src/filament/src/{ => interface}/camera.dart (95%) rename thermion_dart/lib/src/filament/src/{ => interface}/engine.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/entity.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/filament_app.dart (95%) create mode 100644 thermion_dart/lib/src/filament/src/interface/geometry.dart rename thermion_dart/lib/src/filament/src/{ => interface}/gizmo.dart (96%) rename thermion_dart/lib/src/filament/src/{ => interface}/gltf.dart (100%) create mode 100644 thermion_dart/lib/src/filament/src/interface/grid_overlay.dart rename thermion_dart/lib/src/filament/src/{ => interface}/layers.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/light.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/light_options.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/manipulator.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/material.dart (99%) rename thermion_dart/lib/src/filament/src/{ => interface}/pick_result.dart (93%) rename thermion_dart/lib/src/filament/src/{ => interface}/primitive.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/render_target.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/scene.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/shadow.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/shared_types.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/swap_chain.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/texture.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/texture_details.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/tone_mapper.dart (100%) rename thermion_dart/lib/src/filament/src/{ => interface}/view.dart (91%) delete mode 100644 thermion_dart/lib/src/utils/src/dart_resources.dart delete mode 100644 thermion_dart/lib/src/viewer/src/thermion_viewer_stub.dart delete mode 100644 thermion_dart/lib/src/viewer/src/web_wasm/src/camera.dart delete mode 100644 thermion_dart/lib/src/viewer/src/web_wasm/src/material_instance.dart delete mode 100644 thermion_dart/native/web/include/ThermionFlutterWebApi.h create mode 100644 thermion_dart/native/web/include/ThermionWebApi.h delete mode 100644 thermion_dart/native/web/src/cpp/ThermionDartWebApi.cpp create mode 100644 thermion_dart/native/web/src/cpp/ThermionWebApi.cpp delete mode 100644 thermion_dart/test/input_handlers.mocks.dart diff --git a/examples/dart/js_wasm/pubspec.yaml b/examples/dart/js_wasm/pubspec.yaml new file mode 100644 index 00000000..171e4b10 --- /dev/null +++ b/examples/dart/js_wasm/pubspec.yaml @@ -0,0 +1,21 @@ +name: example_web +description: A sample command-line application. +version: 1.0.0 +# repository: https://github.com/my_org/my_repo + +environment: + sdk: ^3.3.0 + +dependencies: + thermion_dart: + path: ../../../thermion_dart + native_toolchain_c: ^0.9.0 + native_assets_cli: ^0.12.0 + logging: ^1.3.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 + build_runner: ^2.4.13 + build_test: ^2.2.2 + build_web_compilers: ^4.0.11 diff --git a/examples/dart/js_wasm/web/assets b/examples/dart/js_wasm/web/assets new file mode 120000 index 00000000..f86487ff --- /dev/null +++ b/examples/dart/js_wasm/web/assets @@ -0,0 +1 @@ +../../../assets/ \ No newline at end of file diff --git a/examples/dart/js_wasm/web/example.dart b/examples/dart/js_wasm/web/example.dart new file mode 100644 index 00000000..9dcf029a --- /dev/null +++ b/examples/dart/js_wasm/web/example.dart @@ -0,0 +1,102 @@ +import 'dart:async'; +import 'dart:js_interop'; +import 'dart:math'; +import 'package:web/web.dart'; +import 'package:logging/logging.dart'; +import 'package:thermion_dart/thermion_dart.dart' hide NativeLibrary; +import 'package:thermion_dart/src/filament/src/implementation/ffi_filament_app.dart'; +import 'package:thermion_dart/src/filament/src/implementation/resource_loader.dart'; +import 'web_input_handler.dart'; +import 'package:thermion_dart/src/bindings/src/thermion_dart_js_interop.g.dart'; + +void main(List arguments) async { + Logger.root.onRecord.listen((record) { + print(record); + }); + + NativeLibrary.initBindings("thermion_dart"); + + final canvas = + document.getElementById("thermion_canvas") as HTMLCanvasElement; + try { + canvas.width = canvas.clientWidth; + canvas.height = canvas.clientHeight; + } catch (err) { + print(err.toString()); + } + + final config = + FFIFilamentConfig(sharedContext: nullptr.cast(), backend: Backend.OPENGL); + + await FFIFilamentApp.create(config: config); + + var swapChain = await FilamentApp.instance! + .createHeadlessSwapChain(canvas.width, canvas.height); + final viewer = ThermionViewerFFI(loadAssetFromUri: defaultResourceLoader); + await viewer.initialized; + await FilamentApp.instance!.setClearOptions(1.0, 0.0, 0.0, 1.0); + await FilamentApp.instance!.register(swapChain, viewer.view); + await viewer.setViewport(canvas.width, canvas.height); + await viewer.setRendering(true); + final rnd = Random(); + // ignore: prefer_function_declarations_over_variables + bool resizing = false; + // ignore: prefer_function_declarations_over_variables + final resizer = () async { + if (resizing) { + return; + } + try { + resizing = true; + await viewer.setViewport(canvas.clientWidth, canvas.clientHeight); + Thermion_resizeCanvas(canvas.clientWidth, canvas.clientHeight); + } catch (err) { + print(err); + } finally { + resizing = false; + } + }; + // ignore: unused_local_variable, prefer_function_declarations_over_variables + final jsWrapper = () { + var promise = resizer().toJS; + return promise; + }; + window.addEventListener('resize', jsWrapper.toJS); + // // await FilamentApp.instance!.render(); + // // await Future.delayed(Duration(seconds: 1)); + + // // await FilamentApp.instance!.setClearOptions(1.0, 1.0, 0.0, 1.0); + // // await FilamentApp.instance!.render(); + // // await Future.delayed(Duration(seconds: 1)); + + await viewer.loadSkybox("assets/default_env_skybox.ktx"); + await viewer.loadGltf("assets/cube.glb"); + final camera = await viewer.getActiveCamera(); + + var zOffset = 10.0; + + final inputHandler = DelegateInputHandler.flight(viewer); + + final webInputHandler = + WebInputHandler(inputHandler: inputHandler, canvas: canvas); + await camera.lookAt(Vector3(0, 0, zOffset)); + DateTime lastRender = DateTime.now(); + + while (true) { + var stackPtr = stackSave(); + var now = DateTime.now(); + await FilamentApp.instance!.requestFrame(); + now = DateTime.now(); + var timeSinceLast = + now.microsecondsSinceEpoch - lastRender.microsecondsSinceEpoch; + lastRender = now; + if (timeSinceLast < 1667) { + var waitFor = 1667 - timeSinceLast; + await Future.delayed(Duration(microseconds: waitFor)); + } + stackRestore(stackPtr); + // inputHandler.keyDown(PhysicalKey.S); + // await camera.lookAt(Vector3(0,0,zOffset)); + // zOffset +=0.1; + } +} diff --git a/examples/dart/js_wasm/web/index_js.html b/examples/dart/js_wasm/web/index_js.html new file mode 100644 index 00000000..6955f86d --- /dev/null +++ b/examples/dart/js_wasm/web/index_js.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/dart/js_wasm/web/index_wasm.html b/examples/dart/js_wasm/web/index_wasm.html new file mode 100644 index 00000000..154418fb --- /dev/null +++ b/examples/dart/js_wasm/web/index_wasm.html @@ -0,0 +1,35 @@ + + + + + + + + + + \ No newline at end of file diff --git a/examples/dart/js_wasm/web/main.js b/examples/dart/js_wasm/web/main.js new file mode 100644 index 00000000..440838ed --- /dev/null +++ b/examples/dart/js_wasm/web/main.js @@ -0,0 +1,22 @@ +import { readFile } from 'fs/promises'; + +import { compile } from './example.mjs'; +import thermion_dart from './thermion_dart.js'; + + +async function runDartWasm() { + globalThis['thermion_dart'] = await thermion_dart(); + + const wasmBytes = await readFile('example.wasm'); + const compiledApp = await compile(wasmBytes); + const instantiatedApp = await compiledApp.instantiate({}); + try { + instantiatedApp.invokeMain(); + } catch(err) { + console.error("Failed"); + console.error(err); + } +} + +runDartWasm().catch(console.error); + diff --git a/examples/dart/js_wasm/web/thermion_dart.js b/examples/dart/js_wasm/web/thermion_dart.js new file mode 120000 index 00000000..7046c2e9 --- /dev/null +++ b/examples/dart/js_wasm/web/thermion_dart.js @@ -0,0 +1 @@ +../../../../thermion_dart/native/web/build/build/out/thermion_dart.js \ No newline at end of file diff --git a/examples/dart/js_wasm/web/thermion_dart.wasm b/examples/dart/js_wasm/web/thermion_dart.wasm new file mode 120000 index 00000000..c7a867ee --- /dev/null +++ b/examples/dart/js_wasm/web/thermion_dart.wasm @@ -0,0 +1 @@ +../../../../thermion_dart/native/web/build/build/out/thermion_dart.wasm \ No newline at end of file diff --git a/examples/dart/js_wasm/web/web_input_handler.dart b/examples/dart/js_wasm/web/web_input_handler.dart new file mode 100644 index 00000000..5b5e9649 --- /dev/null +++ b/examples/dart/js_wasm/web/web_input_handler.dart @@ -0,0 +1,233 @@ +import 'dart:js_interop'; + +import 'package:web/web.dart' as web; +import 'package:thermion_dart/thermion_dart.dart'; + +class WebInputHandler { + final DelegateInputHandler inputHandler; + final web.HTMLCanvasElement canvas; + late double pixelRatio; + + final Map _touchPositions = {}; + + WebInputHandler({ + required this.inputHandler, + required this.canvas, + }) { + pixelRatio = web.window.devicePixelRatio; + _initializeEventListeners(); + } + + void _initializeEventListeners() { + canvas.addEventListener('mousedown', _onMouseDown.toJS); + canvas.addEventListener('mousemove', _onMouseMove.toJS); + canvas.addEventListener('mouseup', _onMouseUp.toJS); + canvas.addEventListener('wheel', _onMouseWheel.toJS); + web.window.addEventListener('keydown', _onKeyDown.toJS); + web.window.addEventListener('keyup', _onKeyUp.toJS); + + canvas.addEventListener('touchstart', _onTouchStart.toJS); + canvas.addEventListener('touchmove', _onTouchMove.toJS); + canvas.addEventListener('touchend', _onTouchEnd.toJS); + canvas.addEventListener('touchcancel', _onTouchCancel.toJS); + } + + void _onMouseDown(web.MouseEvent event) { + final localPos = _getLocalPositionFromEvent(event); + final isMiddle = event.button == 1; + inputHandler.onPointerDown(localPos, isMiddle); + event.preventDefault(); + } + + void _onMouseMove(web.MouseEvent event) { + final localPos = _getLocalPositionFromEvent(event); + + final delta = Vector2(event.movementX ?? 0, event.movementY ?? 0); + final isMiddle = event.buttons & 4 != 0; + inputHandler.onPointerMove(localPos, delta, isMiddle); + event.preventDefault(); + } + + void _onMouseUp(web.MouseEvent event) { + final isMiddle = event.button == 1; + inputHandler.onPointerUp(isMiddle); + event.preventDefault(); + } + + void _onMouseWheel(web.WheelEvent event) { + final localPos = _getLocalPositionFromEvent(event); + final delta = event.deltaY; + inputHandler.onPointerScroll(localPos, delta); + event.preventDefault(); + } + + void _onKeyDown(web.KeyboardEvent event) { + PhysicalKey? key; + switch (event.code) { + case 'KeyW': + key = PhysicalKey.W; + break; + case 'KeyA': + key = PhysicalKey.A; + break; + case 'KeyS': + key = PhysicalKey.S; + break; + case 'KeyD': + key = PhysicalKey.D; + break; + } + if (key != null) inputHandler.keyDown(key); + event.preventDefault(); + } + + void _onKeyUp(web.KeyboardEvent event) { + PhysicalKey? key; + switch (event.code) { + case 'KeyW': + key = PhysicalKey.W; + break; + case 'KeyA': + key = PhysicalKey.A; + break; + case 'KeyS': + key = PhysicalKey.S; + break; + case 'KeyD': + key = PhysicalKey.D; + break; + } + if (key != null) inputHandler.keyUp(key); + event.preventDefault(); + } + + void _onTouchStart(web.TouchEvent event) { + + for (var touch in event.changedTouches.toList()) { + final pos = _getLocalPositionFromTouch(touch); + _touchPositions[touch.identifier] = pos; + } + + final touchCount = event.touches.toList().length; + if (touchCount == 1) { + final touch = event.touches.toList().first; + final pos = _getLocalPositionFromTouch(touch); + inputHandler.onPointerDown(pos, false); + } + + _handleScaleStart(touchCount, null); + event.preventDefault(); + } + + void _onTouchMove(web.TouchEvent event) { + for (var touch in event.changedTouches.toList()) { + final id = touch.identifier; + final currPos = _getLocalPositionFromTouch(touch); + final prevPos = _touchPositions[id]; + + if (prevPos != null) { + final delta = currPos - prevPos; + inputHandler.onPointerMove(currPos, delta, false); + } + + _touchPositions[id] = currPos; + } + + final touchCount = event.touches.toList().length; + if (touchCount >= 2) { + final touches = event.touches.toList().toList(); + final touch0 = touches[0]; + final touch1 = touches[1]; + final pos0 = _getLocalPositionFromTouch(touch0); + final pos1 = _getLocalPositionFromTouch(touch1); + final prevPos0 = _touchPositions[touch0.identifier]; + final prevPos1 = _touchPositions[touch1.identifier]; + + if (prevPos0 != null && prevPos1 != null) { + final prevDist = (prevPos0 - prevPos1).length; + final currDist = (pos0 - pos1).length; + final scale = currDist / prevDist; + final focalPoint = (pos0 + pos1) * 0.5; + + inputHandler.onScaleUpdate( + focalPoint, + Vector2(0, 0), + 0.0, + 0.0, + scale, + touchCount, + 0.0, + null, + ); + } + } + + event.preventDefault(); + } + + void _onTouchEnd(web.TouchEvent event) { + for (var touch in event.changedTouches.toList()) { + _touchPositions.remove(touch.identifier); + } + + final touchCount = event.touches.toList().length; + inputHandler.onScaleEnd(touchCount, 0.0); + event.preventDefault(); + } + + void _onTouchCancel(web.TouchEvent event) { + for (var touch in event.changedTouches.toList()) { + _touchPositions.remove(touch.identifier); + } + + final touchCount = event.touches.toList().length; + inputHandler.onScaleEnd(touchCount, 0.0); + event.preventDefault(); + } + + void _handleScaleStart(int pointerCount, Duration? sourceTimestamp) { + inputHandler.onScaleStart(Vector2.zero(), pointerCount, sourceTimestamp); + } + + Vector2 _getLocalPositionFromEvent(web.Event event) { + final rect = canvas.getBoundingClientRect(); + double clientX = 0, clientY = 0; + + if (event is web.MouseEvent) { + clientX = event.clientX.toDouble(); + clientY = event.clientY.toDouble(); + } else if (event is web.TouchEvent) { + final touch = event.touches.toList().firstOrNull; + if (touch != null) { + clientX = touch.clientX; + clientY = touch.clientY; + } + } + + return Vector2( + (clientX - rect.left) * pixelRatio, + (clientY - rect.top) * pixelRatio, + ); + } + + Vector2 _getLocalPositionFromTouch(web.Touch touch) { + final rect = canvas.getBoundingClientRect(); + return Vector2( + (touch.clientX - rect.left) * pixelRatio, + (touch.clientY - rect.top) * pixelRatio, + ); + } + + void dispose() { + canvas.removeEventListener('mousedown', _onMouseDown.toJS); + canvas.removeEventListener('mousemove', _onMouseMove.toJS); + canvas.removeEventListener('mouseup', _onMouseUp.toJS); + canvas.removeEventListener('wheel', _onMouseWheel.toJS); + web.window.removeEventListener('keydown', _onKeyDown.toJS); + web.window.removeEventListener('keyup', _onKeyUp.toJS); + canvas.removeEventListener('touchstart', _onTouchStart.toJS); + canvas.removeEventListener('touchmove', _onTouchMove.toJS); + canvas.removeEventListener('touchend', _onTouchEnd.toJS); + canvas.removeEventListener('touchcancel', _onTouchCancel.toJS); + } +} diff --git a/examples/flutter/quickstart/web/index.html b/examples/flutter/quickstart/web/index.html index cbadfa17..115ebc24 100644 --- a/examples/flutter/quickstart/web/index.html +++ b/examples/flutter/quickstart/web/index.html @@ -2,19 +2,6 @@ - @@ -37,12 +24,17 @@ const serviceWorkerVersion = null; - - + + -