Merge pull request #1726 from Heap-Hop/window_maximize

flutter desktop window un/maximize
This commit is contained in:
RustDesk 2022-10-14 22:12:29 +08:00 committed by GitHub
commit c67c55f74b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 246 additions and 136 deletions

View File

@ -38,7 +38,6 @@ var isWeb = false;
var isWebDesktop = false;
var version = "";
int androidVersion = 0;
const windowPrefix = "wm_";
DesktopType? desktopType;
typedef F = String Function(String);
@ -957,16 +956,18 @@ class LastWindowPosition {
double? height;
double? offsetWidth;
double? offsetHeight;
bool? isMaximized;
LastWindowPosition(
this.width, this.height, this.offsetWidth, this.offsetHeight);
LastWindowPosition(this.width, this.height, this.offsetWidth,
this.offsetHeight, this.isMaximized);
Map<String, dynamic> toJson() {
return <String, dynamic>{
"width": width,
"height": height,
"offsetWidth": offsetWidth,
"offsetHeight": offsetHeight
"offsetHeight": offsetHeight,
"isMaximized": isMaximized,
};
}
@ -981,8 +982,8 @@ class LastWindowPosition {
}
try {
final m = jsonDecode(content);
return LastWindowPosition(
m["width"], m["height"], m["offsetWidth"], m["offsetHeight"]);
return LastWindowPosition(m["width"], m["height"], m["offsetWidth"],
m["offsetHeight"], m["isMaximized"]);
} catch (e) {
debugPrint(e.toString());
return null;
@ -999,22 +1000,29 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
}
switch (type) {
case WindowType.Main:
List resp = await Future.wait(
[windowManager.getPosition(), windowManager.getSize()]);
Offset position = resp[0];
Size sz = resp[1];
final pos =
LastWindowPosition(sz.width, sz.height, position.dx, position.dy);
final position = await windowManager.getPosition();
final sz = await windowManager.getSize();
final isMaximized = await windowManager.isMaximized();
final pos = LastWindowPosition(
sz.width, sz.height, position.dx, position.dy, isMaximized);
await Get.find<SharedPreferences>()
.setString(windowPrefix + type.name, pos.toString());
.setString(kWindowPrefix + type.name, pos.toString());
break;
default:
// TODO: implement window
final wc = WindowController.fromWindowId(windowId!);
final frame = await wc.getFrame();
final position = frame.topLeft;
final sz = frame.size;
final isMaximized = await wc.isMaximized();
final pos = LastWindowPosition(
sz.width, sz.height, position.dx, position.dy, isMaximized);
await Get.find<SharedPreferences>()
.setString(kWindowPrefix + type.name, pos.toString());
break;
}
}
_adjustRestoreMainWindowSize(double? width, double? height) async {
Future<Size> _adjustRestoreMainWindowSize(double? width, double? height) async {
const double minWidth = 600;
const double minHeight = 100;
double maxWidth = (((isDesktop || isWebDesktop)
@ -1055,10 +1063,12 @@ _adjustRestoreMainWindowSize(double? width, double? height) async {
if (restoreHeight > maxHeight) {
restoreWidth = maxHeight;
}
await windowManager.setSize(Size(restoreWidth, restoreHeight));
return Size(restoreWidth, restoreHeight);
}
_adjustRestoreMainWindowOffset(double? left, double? top) async {
/// return null means center
Future<Offset?> _adjustRestoreMainWindowOffset(
double? left, double? top) async {
if (left == null || top == null) {
await windowManager.center();
} else {
@ -1090,40 +1100,68 @@ _adjustRestoreMainWindowOffset(double? left, double? top) async {
windowLeft > frameRight ||
windowTop < frameTop ||
windowTop > frameBottom) {
await windowManager.center();
return null;
} else {
await windowManager.setPosition(Offset(windowLeft, windowTop));
return Offset(windowLeft, windowTop);
}
}
return null;
}
/// Save window position and size on exit
/// Restore window position and size on start
/// Note that windowId must be provided if it's subwindow
Future<bool> restoreWindowPosition(WindowType type, {int? windowId}) async {
if (type != WindowType.Main && windowId == null) {
debugPrint(
"Error: windowId cannot be null when saving positions for sub window");
}
final pos =
Get.find<SharedPreferences>().getString(kWindowPrefix + type.name);
if (pos == null) {
debugPrint("no window position saved, ignore restore");
return false;
}
var lpos = LastWindowPosition.loadFromString(pos);
if (lpos == null) {
debugPrint("window position saved, but cannot be parsed");
return false;
}
switch (type) {
case WindowType.Main:
var pos =
Get.find<SharedPreferences>().getString(windowPrefix + type.name);
if (pos == null) {
debugPrint("no window position saved, ignore restore");
return false;
if (lpos.isMaximized == true) {
await windowManager.maximize();
} else {
final size =
await _adjustRestoreMainWindowSize(lpos.width, lpos.height);
final offset = await _adjustRestoreMainWindowOffset(
lpos.offsetWidth, lpos.offsetHeight);
await windowManager.setSize(size);
if (offset == null) {
await windowManager.center();
} else {
await windowManager.setPosition(offset);
}
}
var lpos = LastWindowPosition.loadFromString(pos);
if (lpos == null) {
debugPrint("window position saved, but cannot be parsed");
return false;
}
await _adjustRestoreMainWindowSize(lpos.width, lpos.height);
await _adjustRestoreMainWindowOffset(lpos.offsetWidth, lpos.offsetHeight);
return true;
default:
// TODO: implement subwindow
final wc = WindowController.fromWindowId(windowId!);
if (lpos.isMaximized == true) {
await wc.maximize();
} else {
final size =
await _adjustRestoreMainWindowSize(lpos.width, lpos.height);
final offset = await _adjustRestoreMainWindowOffset(
lpos.offsetWidth, lpos.offsetHeight);
if (offset == null) {
await wc.center();
} else {
final frame =
Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height);
await wc.setFrame(frame);
}
}
break;
}
return false;
@ -1150,7 +1188,7 @@ void checkArguments() {
}
/// Parse `rustdesk://` unilinks
///
///
/// [Functions]
/// 1. New Connection: rustdesk://connection/new/your_peer_id
void parseRustdeskUri(String uriPath) {

View File

@ -15,6 +15,8 @@ const String kActionNewConnection = "connection/new/";
const String kTabLabelHomePage = "Home";
const String kTabLabelSettingPage = "Settings";
const String kWindowPrefix = "wm_";
const Color kColorWarn = Color.fromARGB(255, 245, 133, 59);
const int kMobileDefaultDisplayWidth = 720;

View File

@ -289,57 +289,48 @@ class DesktopTab extends StatelessWidget {
Widget _buildBar() {
return Row(
children: [
Expanded(
child: Row(
children: [
Offstage(
offstage: !Platform.isMacOS,
child: const SizedBox(
width: 78,
)),
Row(children: [
Offstage(
offstage: !showLogo,
child: SvgPicture.asset(
'assets/logo.svg',
width: 16,
height: 16,
)),
Offstage(
offstage: !showTitle,
child: const Text(
"RustDesk",
style: TextStyle(fontSize: 13),
).marginOnly(left: 2))
]).marginOnly(
left: 5,
right: 10,
),
Expanded(
child: GestureDetector(
onPanStart: (_) {
if (isMainWindow) {
windowManager.startDragging();
} else {
WindowController.fromWindowId(windowId!)
.startDragging();
}
},
child: _ListView(
controller: controller,
onTabClose: onTabClose,
tabBuilder: tabBuilder,
labelGetter: labelGetter,
)),
),
],
),
Row(
children: [
Offstage(
offstage: !Platform.isMacOS,
child: const SizedBox(
width: 78,
)),
GestureDetector(
onDoubleTap: () =>
showMaximize ? toggleMaximize(isMainWindow) : null,
onPanStart: (_) => startDragging(isMainWindow),
child: Row(children: [
Offstage(
offstage: !showLogo,
child: SvgPicture.asset(
'assets/logo.svg',
width: 16,
height: 16,
)),
Offstage(
offstage: !showTitle,
child: const Text(
"RustDesk",
style: TextStyle(fontSize: 13),
).marginOnly(left: 2))
]).marginOnly(
left: 5,
right: 10,
)),
_ListView(
controller: controller,
onTabClose: onTabClose,
tabBuilder: tabBuilder,
labelGetter: labelGetter,
),
],
),
Offstage(offstage: tail == null, child: tail),
WindowActionPanel(
mainTab: isMainWindow,
isMainWindow: isMainWindow,
tabType: tabType,
state: state,
tail: tail,
showMinimize: showMinimize,
showMaximize: showMaximize,
showClose: showClose,
@ -350,38 +341,106 @@ class DesktopTab extends StatelessWidget {
}
}
class WindowActionPanel extends StatelessWidget {
final bool mainTab;
class WindowActionPanel extends StatefulWidget {
final bool isMainWindow;
final DesktopTabType tabType;
final Rx<DesktopTabState> state;
final bool showMinimize;
final bool showMaximize;
final bool showClose;
final Widget? tail;
final Future<bool> Function()? onClose;
const WindowActionPanel(
{Key? key,
required this.mainTab,
required this.isMainWindow,
required this.tabType,
required this.state,
this.tail,
this.showMinimize = true,
this.showMaximize = true,
this.showClose = true,
this.onClose})
: super(key: key);
@override
State<StatefulWidget> createState() {
return WindowActionPanelState();
}
}
class WindowActionPanelState extends State<WindowActionPanel>
with MultiWindowListener, WindowListener {
bool isMaximized = false;
@override
void initState() {
super.initState();
DesktopMultiWindow.addListener(this);
windowManager.addListener(this);
if (widget.isMainWindow) {
windowManager.isMaximized().then((maximized) {
if (isMaximized != maximized) {
WidgetsBinding.instance.addPostFrameCallback(
(_) => setState(() => isMaximized = maximized));
}
});
} else {
final wc = WindowController.fromWindowId(windowId!);
wc.isMaximized().then((maximized) {
if (isMaximized != maximized) {
WidgetsBinding.instance.addPostFrameCallback(
(_) => setState(() => isMaximized = maximized));
}
});
}
}
@override
void dispose() {
DesktopMultiWindow.removeListener(this);
windowManager.removeListener(this);
super.dispose();
}
@override
void onWindowMaximize() {
// catch maximize from system
if (!isMaximized) {
setState(() => isMaximized = true);
}
super.onWindowMaximize();
}
@override
void onWindowUnmaximize() {
// catch unmaximize from system
if (isMaximized) {
setState(() => isMaximized = false);
}
super.onWindowUnmaximize();
}
@override
Widget build(BuildContext context) {
return Row(
return Expanded(
child: Row(
children: [
Expanded(
child: GestureDetector(
onDoubleTap: widget.showMaximize ? _toggleMaximize : null,
onPanStart: (_) => startDragging(widget.isMainWindow),
)),
Offstage(offstage: widget.tail == null, child: widget.tail),
Offstage(
offstage: !showMinimize,
offstage: !widget.showMinimize,
child: ActionIcon(
message: 'Minimize',
icon: IconFont.min,
onTap: () {
if (mainTab) {
if (widget.isMainWindow) {
windowManager.minimize();
} else {
WindowController.fromWindowId(windowId!).minimize();
@ -389,56 +448,23 @@ class WindowActionPanel extends StatelessWidget {
},
isClose: false,
)),
// TODO: drag makes window restore
Offstage(
offstage: !showMaximize,
child: FutureBuilder(builder: (context, snapshot) {
RxBool isMaximized = false.obs;
if (mainTab) {
windowManager.isMaximized().then((maximized) {
isMaximized.value = maximized;
});
} else {
final wc = WindowController.fromWindowId(windowId!);
wc.isMaximized().then((maximized) {
isMaximized.value = maximized;
});
}
return Obx(
() => ActionIcon(
message: isMaximized.value ? "Restore" : "Maximize",
icon: isMaximized.value ? IconFont.restore : IconFont.max,
onTap: () {
if (mainTab) {
if (isMaximized.value) {
windowManager.unmaximize();
} else {
windowManager.maximize();
}
} else {
// TODO: subwindow is maximized but first query result is not maximized.
final wc = WindowController.fromWindowId(windowId!);
if (isMaximized.value) {
wc.unmaximize();
} else {
wc.maximize();
}
}
isMaximized.value = !isMaximized.value;
},
isClose: false,
),
);
})),
offstage: !widget.showMaximize,
child: ActionIcon(
message: isMaximized ? "Restore" : "Maximize",
icon: isMaximized ? IconFont.restore : IconFont.max,
onTap: _toggleMaximize,
isClose: false,
)),
Offstage(
offstage: !showClose,
offstage: !widget.showClose,
child: ActionIcon(
message: 'Close',
icon: IconFont.close,
onTap: () async {
final res = await onClose?.call() ?? true;
final res = await widget.onClose?.call() ?? true;
if (res) {
if (mainTab) {
if (widget.isMainWindow) {
windowManager.close();
} else {
// only hide for multi window, not close
@ -451,7 +477,47 @@ class WindowActionPanel extends StatelessWidget {
isClose: true,
)),
],
);
));
}
void _toggleMaximize() {
toggleMaximize(widget.isMainWindow).then((maximize) {
if (isMaximized != maximize) {
// setState for sub window, wc.unmaximize/maximize() will not invoke onWindowMaximize/Unmaximize
setState(() => isMaximized = !isMaximized);
}
});
}
}
void startDragging(bool isMainWindow) {
if (isMainWindow) {
windowManager.startDragging();
} else {
WindowController.fromWindowId(windowId!).startDragging();
}
}
/// return true -> window will be maximize
/// return false -> window will be unmaximize
Future<bool> toggleMaximize(bool isMainWindow) async {
if (isMainWindow) {
if (await windowManager.isMaximized()) {
windowManager.unmaximize();
return false;
} else {
windowManager.maximize();
return true;
}
} else {
final wc = WindowController.fromWindowId(windowId!);
if (await wc.isMaximized()) {
wc.unmaximize();
return false;
} else {
wc.maximize();
return true;
}
}
}

View File

@ -45,7 +45,6 @@ Future<void> main(List<String> args) async {
int type = argument['type'] ?? -1;
argument['windowId'] = windowId;
WindowType wType = type.windowType;
restoreWindowPosition(wType, windowId: windowId);
switch (wType) {
case WindowType.RemoteDesktop:
desktopType = DesktopType.remote;
@ -118,6 +117,7 @@ void runMobileApp() async {
void runRemoteScreen(Map<String, dynamic> argument) async {
await initEnv(kAppTypeDesktopRemote);
await restoreWindowPosition(WindowType.RemoteDesktop, windowId: windowId);
runApp(GetMaterialApp(
navigatorKey: globalKey,
debugShowCheckedModeBanner: false,
@ -143,6 +143,7 @@ void runRemoteScreen(Map<String, dynamic> argument) async {
void runFileTransferScreen(Map<String, dynamic> argument) async {
await initEnv(kAppTypeDesktopFileTransfer);
await restoreWindowPosition(WindowType.FileTransfer, windowId: windowId);
runApp(
GetMaterialApp(
navigatorKey: globalKey,
@ -168,6 +169,7 @@ void runFileTransferScreen(Map<String, dynamic> argument) async {
void runPortForwardScreen(Map<String, dynamic> argument) async {
await initEnv(kAppTypeDesktopPortForward);
await restoreWindowPosition(WindowType.PortForward, windowId: windowId);
runApp(
GetMaterialApp(
navigatorKey: globalKey,

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
/// must keep the order
enum WindowType { Main, RemoteDesktop, FileTransfer, PortForward, Unknown }
@ -153,6 +154,7 @@ class RustDeskMultiWindowManager {
int? wId = findWindowByType(type);
if (wId != null) {
debugPrint("closing multi window: ${type.toString()}");
saveWindowPosition(type, windowId: wId);
try {
final ids = await DesktopMultiWindow.getAllSubWindowIds();
if (!ids.contains(wId)) {

View File

@ -243,8 +243,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "0019311e8aba4e84ffd44c57ba1834cc76924f2a"
resolved-ref: "0019311e8aba4e84ffd44c57ba1834cc76924f2a"
ref: f25487b8aacfcc9d22b86a84e97eda1a5c07ccaf
resolved-ref: f25487b8aacfcc9d22b86a84e97eda1a5c07ccaf
url: "https://github.com/Kingtous/rustdesk_desktop_multi_window"
source: git
version: "0.1.0"