Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[gui] remember window size + better default sizes #3814

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
25 changes: 8 additions & 17 deletions src/client/gui/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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();
Expand All @@ -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',
);

Expand All @@ -59,7 +46,6 @@ void main() async {
});

await hotKeyManager.unregisterAll();
final sharedPreferences = await SharedPreferences.getInstance();

providerContainer = ProviderContainer(overrides: [
guiSettingProvider.overrideWith(() {
Expand Down Expand Up @@ -169,6 +155,11 @@ class _AppState extends ConsumerState<App> 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;
Expand Down
96 changes: 96 additions & 0 deletions src/client/gui/lib/window_size.dart
Original file line number Diff line number Diff line change
@@ -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<Size?> 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<Size> 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 '<Type>'"
// 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)})';
}
}
Loading