mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-01 08:38:18 +08:00
121
lib/common/widgets/scale_app.dart
Normal file
121
lib/common/widgets/scale_app.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
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
|
||||
void setScaleFactor(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_toast.dart';
|
||||
import 'package:PiliPlus/common/widgets/mouse_back.dart';
|
||||
import 'package:PiliPlus/common/widgets/scale_app.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/models/common/theme/theme_color_type.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
||||
@@ -46,7 +47,7 @@ import 'package:window_manager/window_manager.dart' hide calcWindowPosition;
|
||||
WebViewEnvironment? webViewEnvironment;
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
ScaledWidgetsFlutterBinding.ensureInitialized();
|
||||
MediaKit.ensureInitialized();
|
||||
tmpDirPath = (await getTemporaryDirectory()).path;
|
||||
appSupportDirPath = (await getApplicationSupportDirectory()).path;
|
||||
@@ -57,6 +58,7 @@ void main() async {
|
||||
if (kDebugMode) debugPrint('GStorage init error: $e');
|
||||
exit(0);
|
||||
}
|
||||
ScaledWidgetsFlutterBinding.instance.setScaleFactor(Pref.uiScale);
|
||||
|
||||
if (PlatformUtils.isDesktop) {
|
||||
final customDownPath = Pref.downloadPath;
|
||||
@@ -298,29 +300,16 @@ class MyApp extends StatelessWidget {
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
final textScaler = TextScaler.linear(Pref.defaultTextScale);
|
||||
if (uiScale != 1.0) {
|
||||
// Apply full UI scaling for desktop
|
||||
final actualSize = mediaQuery.size;
|
||||
final scaledSize = actualSize / uiScale;
|
||||
child = MediaQuery(
|
||||
data: mediaQuery.copyWith(
|
||||
// Tell child the logical size it should layout to
|
||||
size: scaledSize,
|
||||
textScaler: textScaler,
|
||||
size: mediaQuery.size / uiScale,
|
||||
padding: mediaQuery.padding / uiScale,
|
||||
viewPadding: mediaQuery.viewPadding / uiScale,
|
||||
viewInsets: mediaQuery.viewInsets / uiScale,
|
||||
viewPadding: mediaQuery.viewPadding / uiScale,
|
||||
devicePixelRatio: mediaQuery.devicePixelRatio * uiScale,
|
||||
),
|
||||
// Use OverflowBox to let child layout to scaledSize,
|
||||
// then FittedBox scales it to fit actualSize
|
||||
child: FittedBox(
|
||||
fit: BoxFit.fill,
|
||||
alignment: Alignment.topLeft,
|
||||
child: SizedBox(
|
||||
width: scaledSize.width,
|
||||
height: scaledSize.height,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
} else {
|
||||
child = MediaQuery(
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:PiliPlus/pages/live_room/superchat/superchat_card.dart';
|
||||
import 'package:PiliPlus/pages/video/widgets/header_control.dart';
|
||||
import 'package:PiliPlus/utils/accounts.dart';
|
||||
import 'package:PiliPlus/utils/extension/theme_ext.dart';
|
||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||
import 'package:flutter/gestures.dart';
|
||||
@@ -295,11 +294,9 @@ class LiveRoomChatPanel extends StatelessWidget {
|
||||
TapUpDetails details,
|
||||
DanmakuMsg item,
|
||||
) {
|
||||
final uiScale = Pref.uiScale;
|
||||
final dx = details.globalPosition.dx / uiScale;
|
||||
final dx = details.globalPosition.dx;
|
||||
final renderBox = itemContext.findRenderObject() as RenderBox;
|
||||
final dy =
|
||||
renderBox.localToGlobal(renderBox.size.bottomLeft(.zero)).dy / uiScale;
|
||||
final dy = renderBox.localToGlobal(renderBox.size.bottomLeft(.zero)).dy;
|
||||
final autoScroll =
|
||||
liveRoomController.autoScroll &&
|
||||
!liveRoomController.disableAutoScroll.value;
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/color_palette.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_toast.dart';
|
||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||
import 'package:PiliPlus/common/widgets/scale_app.dart';
|
||||
import 'package:PiliPlus/common/widgets/stateful_builder.dart';
|
||||
import 'package:PiliPlus/main.dart';
|
||||
import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart';
|
||||
@@ -56,12 +57,6 @@ List<SettingsModel> get styleSettings => [
|
||||
needReboot: true,
|
||||
),
|
||||
],
|
||||
NormalModel(
|
||||
title: '界面缩放',
|
||||
getSubtitle: () => '当前缩放比例:${Pref.uiScale.toStringAsFixed(2)}',
|
||||
leading: const Icon(Icons.zoom_in_outlined),
|
||||
onTap: _showUiScaleDialog,
|
||||
),
|
||||
SwitchModel(
|
||||
title: '横屏适配',
|
||||
subtitle: '启用横屏布局与逻辑,平板、折叠屏等可开启;建议全屏方向设为【不改变当前方向】',
|
||||
@@ -116,6 +111,12 @@ List<SettingsModel> get styleSettings => [
|
||||
Get.forceAppUpdate();
|
||||
},
|
||||
),
|
||||
NormalModel(
|
||||
title: '界面缩放',
|
||||
getSubtitle: () => '当前缩放比例:${Pref.uiScale.toStringAsFixed(2)}',
|
||||
leading: const Icon(Icons.zoom_in_outlined),
|
||||
onTap: _showUiScaleDialog,
|
||||
),
|
||||
NormalModel(
|
||||
title: '页面过渡动画',
|
||||
leading: const Icon(Icons.animation),
|
||||
@@ -846,10 +847,10 @@ void _showUiScaleDialog(
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
Pref.uiScale = 1.0;
|
||||
GStorage.setting.delete(SettingBoxKey.uiScale).whenComplete(() {
|
||||
setState();
|
||||
Get.appUpdate();
|
||||
ScaledWidgetsFlutterBinding.instance.setScaleFactor(1.0);
|
||||
});
|
||||
},
|
||||
child: const Text('重置'),
|
||||
@@ -866,11 +867,11 @@ void _showUiScaleDialog(
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
Pref.uiScale = uiScale;
|
||||
GStorage.setting.put(SettingBoxKey.uiScale, uiScale).whenComplete(
|
||||
() {
|
||||
setState();
|
||||
Get.appUpdate();
|
||||
ScaledWidgetsFlutterBinding.instance.setScaleFactor(uiScale);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -44,10 +44,7 @@ abstract final class PageUtils {
|
||||
RouteObserver<PageRoute>();
|
||||
|
||||
static RelativeRect menuPosition(Offset offset) {
|
||||
final uiScale = Pref.uiScale;
|
||||
final dx = offset.dx / uiScale;
|
||||
final dy = offset.dy / uiScale;
|
||||
return .fromLTRB(dx, dy, dx, 0);
|
||||
return .fromLTRB(offset.dx, offset.dy, offset.dx, 0);
|
||||
}
|
||||
|
||||
static Future<void> imageView({
|
||||
|
||||
@@ -649,10 +649,8 @@ abstract final class Pref {
|
||||
static double get defaultTextScale =>
|
||||
_setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0);
|
||||
|
||||
static double uiScale = _setting.get(
|
||||
SettingBoxKey.uiScale,
|
||||
defaultValue: 1.0,
|
||||
);
|
||||
static double get uiScale =>
|
||||
_setting.get(SettingBoxKey.uiScale, defaultValue: 1.0);
|
||||
|
||||
static bool get dynamicsWaterfallFlow =>
|
||||
_setting.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
|
||||
|
||||
Reference in New Issue
Block a user