diff --git a/src/client/gui/lib/main.dart b/src/client/gui/lib/main.dart index b14a19e5bd..134bb953cc 100644 --- a/src/client/gui/lib/main.dart +++ b/src/client/gui/lib/main.dart @@ -4,7 +4,6 @@ import 'package:hotkey_manager/hotkey_manager.dart'; import 'package:local_notifier/local_notifier.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:window_manager/window_manager.dart'; -import 'package:window_size/window_size.dart'; import 'before_quit_dialog.dart'; import 'catalogue/catalogue.dart'; @@ -20,6 +19,7 @@ import 'tray_menu.dart'; import 'vm_details/mapping_slider.dart'; import 'vm_details/vm_details.dart'; import 'vm_table/vm_table_screen.dart'; +import 'window_size.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -31,25 +31,12 @@ void main() async { shortcutPolicy: ShortcutPolicy.requireCreate, // Only for Windows ); - // Get the current screen size - final screenSize = await getCurrentScreen().then((screen) { - return screen?.frame.size; - }); - - final windowSize = (screenSize != null) - ? (screenSize.width >= 1600 && screenSize.height >= 900) - ? const Size(1400, 822) // For screens 1600x900 or larger - : (screenSize.width >= 1280 && screenSize.height >= 720) - ? const Size(1024, 576) // For screens 1280x720 or larger - : const Size(750, 450) // Default window size - : const Size(750, 450); // Default window size if screenSize is null - + final sharedPreferences = await SharedPreferences.getInstance(); await windowManager.ensureInitialized(); - final windowOptions = WindowOptions( center: true, minimumSize: const Size(750, 450), - size: windowSize, + size: await deriveWindowSize(sharedPreferences), title: 'Multipass', ); @@ -59,7 +46,6 @@ void main() async { }); await hotKeyManager.unregisterAll(); - final sharedPreferences = await SharedPreferences.getInstance(); providerContainer = ProviderContainer(overrides: [ guiSettingProvider.overrideWith(() { @@ -169,6 +155,11 @@ class _AppState extends ConsumerState with WindowListener { super.dispose(); } + // this event handler is called continuously during a window resizing operation + // so we want to save the data to the disk only after the resizing stops + @override + void onWindowResize() => saveWindowSizeTimer.reset(); + @override void onWindowClose() async { if (!await windowManager.isPreventClose()) return; diff --git a/src/client/gui/lib/window_size.dart b/src/client/gui/lib/window_size.dart new file mode 100644 index 0000000000..801071ff26 --- /dev/null +++ b/src/client/gui/lib/window_size.dart @@ -0,0 +1,96 @@ +import 'dart:ui'; + +import 'package:async/async.dart'; +import 'package:basics/basics.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:window_manager/window_manager.dart'; +import 'package:window_size/window_size.dart'; + +import 'logger.dart'; + +const windowWidthKey = 'windowWidth'; +const windowHeightKey = 'windowHeight'; + +String resolutionString(Size? size) { + return size != null ? '${size.width}x${size.height}' : ''; +} + +final saveWindowSizeTimer = RestartableTimer(1.seconds, () async { + final currentSize = await windowManager.getSize(); + final sharedPreferences = await SharedPreferences.getInstance(); + final screenSize = await getCurrentScreenSize(); + logger.d( + 'Saving window size ${currentSize.s()} for screen size ${screenSize?.s()}', + ); + final prefix = resolutionString(screenSize); + sharedPreferences.setDouble('$prefix$windowWidthKey', currentSize.width); + sharedPreferences.setDouble('$prefix$windowHeightKey', currentSize.height); +}); + +Future getCurrentScreenSize() async { + try { + final screen = await getCurrentScreen(); + if (screen == null) throw Exception('Screen instance is null'); + + logger.d( + 'Got Screen{frame: ${screen.frame.s()}, scaleFactor: ${screen.scaleFactor}, visibleFrame: ${screen.visibleFrame.s()}}', + ); + + return screen.visibleFrame.size; + } catch (e) { + logger.w('Failed to get current screen information: $e'); + return null; + } +} + +Future deriveWindowSize(SharedPreferences sharedPreferences) async { + final screenSize = await getCurrentScreenSize(); + final prefix = resolutionString(screenSize); + final lastWidth = sharedPreferences.getDouble('$prefix$windowWidthKey'); + final lastHeight = sharedPreferences.getDouble('$prefix$windowHeightKey'); + final size = lastWidth != null && lastHeight != null + ? Size(lastWidth, lastHeight) + : null; + logger.d('Got last window size: ${size?.s()}'); + return size ?? computeDefaultWindowSize(screenSize); +} + +Size computeDefaultWindowSize(Size? screenSize) { + const windowSizeFactor = 0.8; + final (screenWidth, screenHeight) = (screenSize?.width, screenSize?.height); + final aspectRatioFactor = screenSize?.flipped.aspectRatio; + + final defaultWidth = switch (screenWidth) { + null || <= 1024 => 750.0, + >= 1600 => 1400.0, + _ => screenWidth * windowSizeFactor, + }; + + final defaultHeight = switch (screenHeight) { + null || <= 576 => 450.0, + >= 900 => 822.0, + _ => aspectRatioFactor != null + ? defaultWidth * aspectRatioFactor + : screenHeight * windowSizeFactor, + }; + + final size = Size(defaultWidth, defaultHeight); + logger.d('Computed default window size: ${size.s()}'); + return size; +} + +// needed because in release mode Flutter does not emit the actual code for toString for some classes +// instead the returned strings are of type "Instance of ''" +// this is done to reduce binary size, and it cannot be turned off :face-with-rolling-eyes: +// see https://api.flutter.dev/flutter/dart-ui/keepToString-constant.html for more info +extension on Size { + String s() { + return 'Size(${width.toStringAsFixed(1)}, ${height.toStringAsFixed(1)})'; + } +} + +extension on Rect { + String s() { + return 'Rect.fromLTRB(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)})'; + } +}