import 'dart:async' show scheduleMicrotask; import 'dart:collection' show Queue; import 'dart:ui' show PointerDataPacket; import 'package:flutter/gestures.dart' show PointerEventConverter; import 'package:flutter/rendering.dart' show RenderView, ViewConfiguration; import 'package:flutter/widgets.dart'; /// ref https://github.com/LastMonopoly/scaled_app /// Adapted from [WidgetsFlutterBinding] /// class ScaledWidgetsFlutterBinding extends WidgetsFlutterBinding { ScaledWidgetsFlutterBinding._({double scaleFactor = 1.0}) : _scaleFactor = scaleFactor; /// Calculate scale factor from device size. double _scaleFactor; /// Update scaleFactor callback, then rebuild layout set scaleFactor(double scaleFactor) { if (_scaleFactor == scaleFactor) return; _scaleFactor = scaleFactor; handleMetricsChanged(); } double devicePixelRatioScaled = 0; static ScaledWidgetsFlutterBinding? _binding; static ScaledWidgetsFlutterBinding get instance => _binding!; /// Scaling will be applied based on [scaleFactor] callback. /// static WidgetsBinding ensureInitialized({double scaleFactor = 1.0}) => _binding ??= ScaledWidgetsFlutterBinding._(scaleFactor: scaleFactor); /// Override the method from [RendererBinding.createViewConfiguration] to /// change what size or device pixel ratio the [RenderView] will use. /// /// See more: /// * [RendererBinding.createViewConfiguration] /// * [TestWidgetsFlutterBinding.createViewConfiguration] @override ViewConfiguration createViewConfigurationFor(RenderView renderView) { final view = renderView.flutterView; final devicePixelRatio = view.devicePixelRatio; devicePixelRatioScaled = devicePixelRatio * _scaleFactor; final BoxConstraints physicalConstraints = BoxConstraints.fromViewConstraints(view.physicalConstraints); return ViewConfiguration( physicalConstraints: physicalConstraints, logicalConstraints: physicalConstraints / devicePixelRatioScaled, devicePixelRatio: devicePixelRatioScaled, ); } /// Adapted from [GestureBinding.initInstances] @override void initInstances() { super.initInstances(); platformDispatcher.onPointerDataPacket = _handlePointerDataPacket; } @override void unlocked() { super.unlocked(); _flushPointerEventQueue(); } final Queue _pendingPointerEvents = Queue(); /// When we scale UI using [ViewConfiguration], [ui.window] stays the same. /// /// [GestureBinding] uses [platformDispatcher.implicitView.devicePixelRatio] for calculations, /// so we override corresponding methods. /// void _handlePointerDataPacket(PointerDataPacket packet) { // We convert pointer data to logical pixels so that e.g. the touch slop can be // defined in a device-independent manner. try { _pendingPointerEvents.addAll( PointerEventConverter.expand(packet.data, _devicePixelRatioForView), ); if (!locked) { _flushPointerEventQueue(); } } catch (error, stack) { FlutterError.reportError( FlutterErrorDetails( exception: error, stack: stack, library: 'gestures library', context: ErrorDescription('while handling a pointer data packet'), ), ); } } double _devicePixelRatioForView(int viewId) => devicePixelRatioScaled; /// Dispatch a [PointerCancelEvent] for the given pointer soon. /// /// The pointer event will be dispatched before the next pointer event and /// before the end of the microtask but not within this function call. @override void cancelPointer(int pointer) { if (_pendingPointerEvents.isEmpty && !locked) { scheduleMicrotask(_flushPointerEventQueue); } _pendingPointerEvents.addFirst(PointerCancelEvent(pointer: pointer)); } void _flushPointerEventQueue() { assert(!locked); while (_pendingPointerEvents.isNotEmpty) { handlePointerEvent(_pendingPointerEvents.removeFirst()); } } }