feat! js_interop improvements

This commit is contained in:
Nick Fisher
2025-05-07 17:06:38 +08:00
parent 63e2dcd0ca
commit 2f16908992
159 changed files with 12989 additions and 8377 deletions

View File

@@ -11,14 +11,8 @@ import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_in
class ThermionFlutterPlugin {
ThermionFlutterPlugin._();
static Future<ThermionViewer> createViewer(
{ThermionFlutterOptions options =
const ThermionFlutterOptions()}) async {
final viewer =
await ThermionFlutterPlatform.instance.createViewer(options: options);
return viewer;
static Future<ThermionViewer> createViewer() {
return ThermionFlutterPlatform.instance.createViewer();
}
}

View File

@@ -19,7 +19,6 @@ Future kDefaultResizeCallback(Size size, View view, double pixelRatio) async {
}
class ThermionWidget extends StatefulWidget {
///
/// The viewer whose content will be rendered into this widget.
///
@@ -43,7 +42,7 @@ class ThermionWidget extends StatefulWidget {
///
/// If true, add an overlay showing the FPS on top of the rendered content.
///
///
final bool showFpsCounter;
///
@@ -67,12 +66,9 @@ class ThermionWidget extends StatefulWidget {
class _ThermionWidgetState extends State<ThermionWidget> {
@override
Widget build(BuildContext context) {
// Web doesn't support imported textures yet
if (kIsWeb) {
throw Exception();
// return ThermionWidgetWeb(
// viewer: widget.viewer,
// options: ThermionFlutterPlugin.options as ThermionFlutterWebOptions?);
return ThermionWidgetWeb(
viewer: widget.viewer, options: const ThermionFlutterWebOptions(importCanvasAsWidget: true));
}
return ThermionTextureWidget(

View File

@@ -1,37 +1,121 @@
import 'dart:js_util';
import 'dart:async';
import 'dart:ui' as ui;
import 'dart:ui_web' as ui_web;
import 'package:logging/logging.dart';
import 'package:thermion_flutter/thermion_flutter.dart';
import 'package:thermion_flutter_web/thermion_flutter_web.dart';
import 'package:thermion_flutter_web/thermion_flutter_web_options.dart';
import 'package:web/web.dart';
import 'package:web/web.dart' as web;
import 'package:flutter/widgets.dart';
class ThermionWidgetWeb extends StatelessWidget {
import 'resize_observer.dart';
class ThermionWidgetWeb extends StatefulWidget {
final ThermionFlutterWebOptions options;
final ThermionViewer viewer;
const ThermionWidgetWeb(
{super.key, this.options = const ThermionFlutterWebOptions.empty(), required this.viewer});
{super.key,
this.options = const ThermionFlutterWebOptions(),
required this.viewer});
@override
State<StatefulWidget> createState() => _ThermionWidgetWebState();
}
class _ThermionWidgetWebState extends State<ThermionWidgetWeb> {
void initState() {
super.initState();
_requestFrame();
}
DateTime lastRender = DateTime.now();
void _requestFrame() async {
Pointer? stackPtr;
WidgetsBinding.instance.scheduleFrameCallback((d) async {
if (stackPtr != null) {
stackRestore(stackPtr!);
stackPtr = null;
}
var elapsed = DateTime.now().microsecondsSinceEpoch -
lastRender.microsecondsSinceEpoch;
// if (elapsed > 1667) {
lastRender = DateTime.now();
if (widget.viewer.rendering) {
await FilamentApp.instance!.requestFrame();
}
// }
stackPtr = stackSave();
_requestFrame();
});
}
@override
Widget build(BuildContext context) {
// if (_texture == null || _resizing) {
// return widget.initial ?? Container(color: Colors.red);
// }
// return ResizeObserver(
// onResized: _resizeTexture,
// child: ThermionWidgetWeb(
// options: widget.options as ThermionFlutterWebOptions?));
if (options?.importCanvasAsWidget == true) {
return _ImageCopyingWidget();
if (widget.options.importCanvasAsWidget) {
return _ImageCopyingWidget(viewer: widget.viewer);
// return _PlatformView(
// viewer: widget.viewer,
// );
}
return Container(color: const Color(0x00000000));
}
}
class _PlatformView extends StatefulWidget {
final ThermionViewer viewer;
const _PlatformView({super.key, required this.viewer});
@override
State<StatefulWidget> createState() => _PlatformViewState();
}
class _PlatformViewState extends State<_PlatformView> {
void initState() {
super.initState();
ui_web.platformViewRegistry.registerViewFactory(
'imported-canvas',
(int viewId, {Object? params}) {
var canvas = web.document.getElementById("thermion_canvas");
WidgetsBinding.instance.addPostFrameCallback((_) {
var renderBox = this.context.findRenderObject() as RenderBox?;
_resize(Size(0, 0), renderBox!.size);
});
return canvas! as Object;
},
);
}
void _resize(Size oldSize, Size newSize) {
var width = newSize.width.toInt();
var height = newSize.height.toInt();
ThermionFlutterWebPlugin.instance
.resizeCanvas(newSize.width, newSize.height);
widget.viewer.setViewport(width, height);
}
@override
Widget build(BuildContext context) {
return ResizeObserver(
onResized: _resize,
child: HtmlElementView(
viewType: 'imported-canvas',
onPlatformViewCreated: (i) {},
creationParams: <String, Object?>{
'key': 'someValue',
},
));
}
}
class _ImageCopyingWidget extends StatefulWidget {
final ThermionViewer viewer;
const _ImageCopyingWidget({super.key, required this.viewer});
@override
State<StatefulWidget> createState() {
return _ImageCopyingWidgetState();
@@ -39,34 +123,82 @@ class _ImageCopyingWidget extends StatefulWidget {
}
class _ImageCopyingWidgetState extends State<_ImageCopyingWidget> {
final _logger = Logger("_ImageCopyingWidgetState");
late final _logger = Logger(this.runtimeType.toString());
late web.HTMLCanvasElement canvas;
ui.Image? _img;
double width = 0;
double height = 0;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
capture();
canvas =
web.document.getElementById("thermion_canvas") as web.HTMLCanvasElement;
WidgetsBinding.instance.addPostFrameCallback((t) {
_refresh(Duration.zero);
});
}
Future capture() async {
void _refresh(Duration _) async {
try {
final ImageBitmap newSource = await promiseToFuture<ImageBitmap>(
window.createImageBitmap(
document.getElementById("canvas") as HTMLCanvasElement));
_img = await ui_web.createImageFromImageBitmap(newSource);
setState(() {});
WidgetsBinding.instance.addPostFrameCallback((_) {
capture();
});
final rb = this.context.findRenderObject() as RenderBox?;
if (_resizing || rb == null || rb.size.isEmpty) {
setState(() {});
return;
}
if (canvas.width != rb.size.width || canvas.height != rb.size.height) {
ThermionFlutterWebPlugin.instance
.resizeCanvas(rb.size.width, rb.size.height);
await widget.viewer
.setViewport(rb.size.width.ceil(), rb.size.height.ceil())
.timeout(Duration(seconds: 1));
}
width = canvas.width * web.window.devicePixelRatio;
height = canvas.height * web.window.devicePixelRatio;
_img = await ui_web.createImageFromTextureSource(canvas,
width: width.ceil(), height: height.ceil(), transferOwnership: true);
_request++;
} catch (err) {
_logger.severe(err);
} finally {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {});
});
WidgetsBinding.instance.scheduleFrameCallback(_refresh);
}
}
int _request = 0;
bool _resizing = false;
Timer? _resizeTimer;
void _resize(Size oldSize, Size newSize) {
_resizeTimer?.cancel();
_resizing = true;
_resizeTimer = Timer(Duration(milliseconds: 100), () {
_resizing = false;
});
}
@override
Widget build(BuildContext context) {
return RawImage(image: _img!);
if (_img == null) {
return Container();
}
return ResizeObserver(
onResized: _resize,
child: RawImage(
key: Key(_request.toString()),
width: width,
height: height,
image: _img!,
filterQuality: FilterQuality.high,
isAntiAlias: false,
));
}
}

View File

@@ -113,6 +113,7 @@ class _ViewerWidgetState extends State<ViewerWidget> {
}
void didUpdateWidget(ViewerWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.manipulatorType != widget.manipulatorType) {
_setViewportWidget();
setState(() {});