Files
PiliPlus/lib/common/widgets/scale_app.dart
dom 8964197b73 tweaks
Signed-off-by: dom <githubaccount56556@proton.me>
2026-01-31 13:20:51 +08:00

122 lines
3.9 KiB
Dart

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<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
/// 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());
}
}
}