| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:typed_data'; |
| import 'dart:io'; |
| import 'dart:ui'; |
| import 'dart:zircon'; |
| |
| import 'package:args/args.dart'; |
| import 'package:vector_math/vector_math_64.dart' as vector_math_64; |
| |
| final _argsCsvFilePath = '/config/data/args.csv'; |
| |
| void main(List<String> args) async { |
| print('Launching embedding-flutter-view'); |
| |
| args = args + _GetArgsFromConfigFile(); |
| final parser = ArgParser() |
| ..addFlag('showOverlay', defaultsTo: false) |
| ..addFlag('focusable', defaultsTo: true); |
| |
| final arguments = parser.parse(args); |
| for (final option in arguments.options) { |
| print('embedding-flutter-view args: $option: ${arguments[option]}'); |
| } |
| |
| TestApp app = TestApp( |
| ChildView(await _launchChildView()), |
| showOverlay: arguments['showOverlay'], |
| focusable: arguments['focusable'], |
| ); |
| |
| app.run(); |
| } |
| |
| class TestApp { |
| static const _black = Color.fromARGB(255, 0, 0, 0); |
| static const _blue = Color.fromARGB(255, 0, 0, 255); |
| |
| final ChildView childView; |
| final bool showOverlay; |
| final bool focusable; |
| |
| Color _backgroundColor = _blue; |
| |
| TestApp( |
| this.childView, |
| {this.showOverlay = false, |
| this.focusable = true}) { |
| } |
| |
| void run() { |
| childView.create(focusable, (ByteData? reply) { |
| // Set up window callbacks. |
| window.onPointerDataPacket = (PointerDataPacket packet) { |
| this.pointerDataPacket(packet); |
| }; |
| window.onMetricsChanged = () { |
| window.scheduleFrame(); |
| }; |
| window.onBeginFrame = (Duration duration) { |
| this.beginFrame(duration); |
| }; |
| |
| // The child view should be attached to Scenic now. |
| // Ready to build the scene. |
| window.scheduleFrame(); |
| }); |
| } |
| |
| void beginFrame(Duration duration) { |
| // Convert physical screen size of device to values |
| final pixelRatio = window.devicePixelRatio; |
| final size = window.physicalSize / pixelRatio; |
| final physicalBounds = Offset.zero & window.physicalSize; |
| final windowBounds = Offset.zero & size; |
| // Set up a Canvas that uses the screen size |
| final recorder = PictureRecorder(); |
| final canvas = Canvas(recorder, physicalBounds); |
| canvas.scale(pixelRatio); |
| // Draw something |
| final paint = Paint()..color = this._backgroundColor; |
| canvas.drawRect(windowBounds, paint); |
| final picture = recorder.endRecording(); |
| // Build the scene |
| final sceneBuilder = SceneBuilder() |
| ..pushClipRect(physicalBounds) |
| ..addPicture(Offset.zero, picture); |
| |
| final childPhysicalSize = window.physicalSize * 0.25; |
| // Alignment.center |
| final windowCenter = size.center(Offset.zero); |
| final windowPhysicalCenter = window.physicalSize.center(Offset.zero); |
| final childPhysicalOffset = windowPhysicalCenter - childPhysicalSize.center(Offset.zero); |
| |
| sceneBuilder |
| ..pushTransform( |
| vector_math_64.Matrix4.translationValues(childPhysicalOffset.dx, |
| childPhysicalOffset.dy, |
| 0.0).storage) |
| ..addPlatformView(childView.viewId, |
| width: childPhysicalSize.width, |
| height: childPhysicalSize.height) |
| ..pop(); |
| |
| if (showOverlay) { |
| final containerSize = size * 0.5; |
| // Alignment.center |
| final containerOffset = windowCenter - containerSize.center(Offset.zero); |
| |
| final overlaySize = containerSize * 0.5; |
| // Alignment.topRight |
| final overlayOffset = Offset( |
| containerOffset.dx + containerSize.width - overlaySize.width, |
| containerOffset.dy); |
| final overlayPhysicalSize = overlaySize * pixelRatio; |
| final overlayPhysicalOffset = overlayOffset * pixelRatio; |
| final overlayPhysicalBounds = overlayPhysicalOffset & overlayPhysicalSize; |
| |
| final recorder = PictureRecorder(); |
| final overlayCullRect = Offset.zero & overlayPhysicalSize; // in canvas physical coordinates |
| final canvas = Canvas(recorder, overlayCullRect); |
| canvas.scale(pixelRatio); |
| |
| final paint = Paint()..color = Color.fromARGB(255, 0, 255, 0); |
| canvas.drawRect(Offset.zero & overlaySize, paint); |
| |
| final overlayPicture = recorder.endRecording(); |
| sceneBuilder |
| ..pushClipRect(overlayPhysicalBounds) // in window physical coordinates |
| ..addPicture(overlayPhysicalOffset, overlayPicture) |
| ..pop(); |
| } |
| |
| sceneBuilder.pop(); |
| window.render(sceneBuilder.build()); |
| } |
| |
| void pointerDataPacket(PointerDataPacket packet) async { |
| int nowNanos = System.clockGetMonotonic(); |
| |
| for (PointerData data in packet.data) { |
| print('embedding-flutter-view received tap: ${data.toStringFull()}'); |
| |
| if (data.change == PointerChange.down) { |
| this._backgroundColor = _black; |
| } |
| |
| if (data.change == PointerChange.down || data.change == PointerChange.move) { |
| _reportTouchInput( |
| localX: data.physicalX, |
| localY: data.physicalY, |
| timeReceived: nowNanos, |
| ); |
| } |
| } |
| |
| window.scheduleFrame(); |
| } |
| |
| void _reportTouchInput({required double localX, required double localY, required int timeReceived}) { |
| print('embedding-flutter-view reporting touch input to TouchInputListener'); |
| final message = utf8.encode(json.encode({ |
| 'method': 'TouchInputListener.ReportTouchInput', |
| 'local_x': localX, |
| 'local_y': localY, |
| 'time_received': timeReceived, |
| 'component_name': 'embedding-flutter-view', |
| })).buffer.asByteData(); |
| PlatformDispatcher.instance.sendPlatformMessage('fuchsia/input_test', message, null); |
| } |
| } |
| |
| class ChildView { |
| final int viewId; |
| |
| ChildView(this.viewId); |
| |
| void create( |
| bool focusable, |
| PlatformMessageResponseCallback callback) { |
| // Construct the dart:ui platform message to create the view, and when the |
| // return callback is invoked, build the scene. At that point, it is safe |
| // to embed the child view in the scene. |
| final viewOcclusionHint = Rect.zero; |
| final Map<String, dynamic> args = <String, dynamic>{ |
| 'viewId': viewId, |
| // Flatland doesn't support disabling hit testing. |
| 'hitTestable': true, |
| 'focusable': focusable, |
| 'viewOcclusionHintLTRB': <double>[ |
| viewOcclusionHint.left, |
| viewOcclusionHint.top, |
| viewOcclusionHint.right, |
| viewOcclusionHint.bottom |
| ], |
| }; |
| |
| final ByteData createViewMessage = utf8.encode( |
| json.encode(<String, Object>{ |
| 'method': 'View.create', |
| 'args': args, |
| }) |
| ).buffer.asByteData(); |
| |
| final platformViewsChannel = 'flutter/platform_views'; |
| |
| PlatformDispatcher.instance.sendPlatformMessage( |
| platformViewsChannel, |
| createViewMessage, |
| callback); |
| } |
| } |
| |
| Future<int> _launchChildView() async { |
| final message = Int8List.fromList([0x31]); |
| final completer = new Completer<ByteData>(); |
| PlatformDispatcher.instance.sendPlatformMessage( |
| 'fuchsia/child_view', ByteData.sublistView(message), (ByteData? reply) { |
| completer.complete(reply!); |
| }); |
| |
| return int.parse( |
| ascii.decode(((await completer.future).buffer.asUint8List()))); |
| } |
| |
| |
| List<String> _GetArgsFromConfigFile() { |
| List<String> args; |
| final f = File(_argsCsvFilePath); |
| if (!f.existsSync()) { |
| return List.empty(); |
| } |
| final fileContentCsv = f.readAsStringSync(); |
| args = fileContentCsv.split('\n'); |
| return args; |
| } |