feat! js_interop improvements
This commit is contained in:
21
examples/dart/js_wasm/pubspec.yaml
Normal file
21
examples/dart/js_wasm/pubspec.yaml
Normal file
@@ -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
|
||||
1
examples/dart/js_wasm/web/assets
Symbolic link
1
examples/dart/js_wasm/web/assets
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../assets/
|
||||
102
examples/dart/js_wasm/web/example.dart
Normal file
102
examples/dart/js_wasm/web/example.dart
Normal file
@@ -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<String> 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;
|
||||
}
|
||||
}
|
||||
35
examples/dart/js_wasm/web/index_js.html
Normal file
35
examples/dart/js_wasm/web/index_js.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="./thermion_dart.js"></script>
|
||||
<script type="module">
|
||||
try {
|
||||
window.thermion_dart = await thermion_dart();
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
const script = document.createElement('script')
|
||||
script.src = 'example.dart.js'
|
||||
document.head.append(script);
|
||||
</script>
|
||||
<style>
|
||||
html, body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
canvas {
|
||||
position:absolute;
|
||||
left:0;
|
||||
right:0;
|
||||
top:0;
|
||||
bottom:0;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="thermion_canvas"></canvas>
|
||||
</body>
|
||||
</html>
|
||||
35
examples/dart/js_wasm/web/index_wasm.html
Normal file
35
examples/dart/js_wasm/web/index_wasm.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="thermion_dart.js"></script>
|
||||
<style>
|
||||
html, body {
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
canvas {
|
||||
position:absolute;
|
||||
left:0;
|
||||
right:0;
|
||||
top:0;
|
||||
bottom:0;
|
||||
width:100%;
|
||||
height:100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<script type="module">
|
||||
try {
|
||||
window.thermion_dart = await thermion_dart();
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
const dartModulePromise = WebAssembly.compileStreaming(fetch('example.wasm'));
|
||||
const imports = {};
|
||||
const dart2wasm_runtime = await import('./example.mjs');
|
||||
const moduleInstance = await dart2wasm_runtime.instantiate(dartModulePromise, imports);
|
||||
await dart2wasm_runtime.invoke(moduleInstance);
|
||||
</script>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
</body>
|
||||
</html>
|
||||
22
examples/dart/js_wasm/web/main.js
Normal file
22
examples/dart/js_wasm/web/main.js
Normal file
@@ -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);
|
||||
|
||||
1
examples/dart/js_wasm/web/thermion_dart.js
Symbolic link
1
examples/dart/js_wasm/web/thermion_dart.js
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../thermion_dart/native/web/build/build/out/thermion_dart.js
|
||||
1
examples/dart/js_wasm/web/thermion_dart.wasm
Symbolic link
1
examples/dart/js_wasm/web/thermion_dart.wasm
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../thermion_dart/native/web/build/build/out/thermion_dart.wasm
|
||||
233
examples/dart/js_wasm/web/web_input_handler.dart
Normal file
233
examples/dart/js_wasm/web/web_input_handler.dart
Normal file
@@ -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<int, Vector2> _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);
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,6 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!--
|
||||
If you are serving your web app in a path other than the root, change the
|
||||
href value below to reflect the base path you are serving from.
|
||||
|
||||
The path provided below has to start and end with a slash "/" in order for
|
||||
it to work correctly.
|
||||
|
||||
For more details:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||
|
||||
This is a placeholder for base href that will be replaced by the value of
|
||||
the `--base-href` argument provided to `flutter build`.
|
||||
-->
|
||||
<base href="$FLUTTER_BASE_HREF">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
@@ -37,12 +24,17 @@
|
||||
const serviceWorkerVersion = null;
|
||||
</script>
|
||||
<script src="flutter.js" defer></script>
|
||||
|
||||
|
||||
<script type="text/javascript" src="./thermion_dart.js"></script>
|
||||
<script type="module">
|
||||
try {
|
||||
window.thermion_dart = await thermion_dart();
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<div id="flutter-container"></div>
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('load', function (ev) {
|
||||
|
||||
@@ -1 +1 @@
|
||||
../../../assets/thermion_dart.js
|
||||
../../../../thermion_dart/native/web/build/build/out/thermion_dart.js
|
||||
@@ -1 +1 @@
|
||||
../../../assets/thermion_dart.wasm
|
||||
../../../../thermion_dart/native/web/build/build/out/thermion_dart.wasm
|
||||
Reference in New Issue
Block a user