From cf73c04cb3ed6f389d5818859950b2a1abc9e4e6 Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 14 Oct 2022 10:48:33 +0900 Subject: [PATCH 1/3] drag to Un/Maximize update icon state --- .../lib/desktop/widgets/tabbar_widget.dart | 138 ++++++++++++------ flutter/pubspec.lock | 4 +- 2 files changed, 94 insertions(+), 48 deletions(-) diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index e3fcf0f53..d92af9447 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -350,7 +350,7 @@ class DesktopTab extends StatelessWidget { } } -class WindowActionPanel extends StatelessWidget { +class WindowActionPanel extends StatefulWidget { final bool mainTab; final DesktopTabType tabType; final Rx state; @@ -371,17 +371,79 @@ class WindowActionPanel extends StatelessWidget { this.onClose}) : super(key: key); + @override + State createState() { + return WindowActionPanelState(); + } +} + +class WindowActionPanelState extends State + with MultiWindowListener, WindowListener { + bool isMaximized = false; + + @override + void initState() { + super.initState(); + DesktopMultiWindow.addListener(this); + windowManager.addListener(this); + + // TODO init window can't detect isMaximized + if (widget.mainTab) { + windowManager.isMaximized().then((maximized) { + debugPrint("init main maximized: $maximized"); + if (isMaximized != maximized) { + WidgetsBinding.instance.addPostFrameCallback( + (_) => setState(() => isMaximized = maximized)); + } + }); + } else { + final wc = WindowController.fromWindowId(windowId!); + wc.isMaximized().then((maximized) { + debugPrint("init sun maximized: $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( children: [ Offstage( - offstage: !showMinimize, + offstage: !widget.showMinimize, child: ActionIcon( message: 'Minimize', icon: IconFont.min, onTap: () { - if (mainTab) { + if (widget.mainTab) { windowManager.minimize(); } else { WindowController.fromWindowId(windowId!).minimize(); @@ -389,56 +451,40 @@ 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: () { + if (widget.mainTab) { + if (isMaximized) { + windowManager.unmaximize(); + } else { + windowManager.maximize(); + } + } else { + final wc = WindowController.fromWindowId(windowId!); + if (isMaximized) { + wc.unmaximize(); + } else { + wc.maximize(); + } + } + // setState for sub window, wc.unmaximize/maximize() will not invoke onWindowMaximize/Unmaximize + setState(() => isMaximized = !isMaximized); + }, + 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.mainTab) { windowManager.close(); } else { // only hide for multi window, not close diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index fb28840b4..d5f46f2f6 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -243,8 +243,8 @@ packages: dependency: "direct main" description: path: "." - ref: "0019311e8aba4e84ffd44c57ba1834cc76924f2a" - resolved-ref: "0019311e8aba4e84ffd44c57ba1834cc76924f2a" + ref: c09d65018f402dd0d6073149fe6705185101a270 + resolved-ref: c09d65018f402dd0d6073149fe6705185101a270 url: "https://github.com/Kingtous/rustdesk_desktop_multi_window" source: git version: "0.1.0" From c01b9d5d7d846ab8b5d99daa24ad8b1524060411 Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 14 Oct 2022 19:48:41 +0900 Subject: [PATCH 2/3] restoreWindowPosition for sub window and add restore maximize --- flutter/lib/common.dart | 110 ++++++++++++------ flutter/lib/consts.dart | 2 + .../lib/desktop/widgets/tabbar_widget.dart | 9 +- flutter/lib/main.dart | 4 +- flutter/lib/utils/multi_window_manager.dart | 2 + flutter/pubspec.lock | 4 +- 6 files changed, 89 insertions(+), 42 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 890feac6b..6e32ad09c 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -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 toJson() { return { "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 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() - .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() + .setString(kWindowPrefix + type.name, pos.toString()); break; } } -_adjustRestoreMainWindowSize(double? width, double? height) async { +Future _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 _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 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().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().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) { diff --git a/flutter/lib/consts.dart b/flutter/lib/consts.dart index 056cc000c..f43c20cc6 100644 --- a/flutter/lib/consts.dart +++ b/flutter/lib/consts.dart @@ -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; diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index d92af9447..b11ded495 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -387,10 +387,8 @@ class WindowActionPanelState extends State DesktopMultiWindow.addListener(this); windowManager.addListener(this); - // TODO init window can't detect isMaximized if (widget.mainTab) { windowManager.isMaximized().then((maximized) { - debugPrint("init main maximized: $maximized"); if (isMaximized != maximized) { WidgetsBinding.instance.addPostFrameCallback( (_) => setState(() => isMaximized = maximized)); @@ -399,7 +397,6 @@ class WindowActionPanelState extends State } else { final wc = WindowController.fromWindowId(windowId!); wc.isMaximized().then((maximized) { - debugPrint("init sun maximized: $maximized"); if (isMaximized != maximized) { WidgetsBinding.instance.addPostFrameCallback( (_) => setState(() => isMaximized = maximized)); @@ -433,6 +430,12 @@ class WindowActionPanelState extends State super.onWindowUnmaximize(); } + @override + void onWindowClose() { + debugPrint("onWindowClose : is Main : ${widget.mainTab}"); + super.onWindowClose(); + } + @override Widget build(BuildContext context) { return Row( diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 43f4b9c24..835606eb1 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -45,7 +45,6 @@ Future main(List 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 argument) async { await initEnv(kAppTypeDesktopRemote); + await restoreWindowPosition(WindowType.RemoteDesktop, windowId: windowId); runApp(GetMaterialApp( navigatorKey: globalKey, debugShowCheckedModeBanner: false, @@ -143,6 +143,7 @@ void runRemoteScreen(Map argument) async { void runFileTransferScreen(Map argument) async { await initEnv(kAppTypeDesktopFileTransfer); + await restoreWindowPosition(WindowType.FileTransfer, windowId: windowId); runApp( GetMaterialApp( navigatorKey: globalKey, @@ -168,6 +169,7 @@ void runFileTransferScreen(Map argument) async { void runPortForwardScreen(Map argument) async { await initEnv(kAppTypeDesktopPortForward); + await restoreWindowPosition(WindowType.PortForward, windowId: windowId); runApp( GetMaterialApp( navigatorKey: globalKey, diff --git a/flutter/lib/utils/multi_window_manager.dart b/flutter/lib/utils/multi_window_manager.dart index 97d5a5e23..8fd71540d 100644 --- a/flutter/lib/utils/multi_window_manager.dart +++ b/flutter/lib/utils/multi_window_manager.dart @@ -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)) { diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock index d5f46f2f6..4b78bab93 100644 --- a/flutter/pubspec.lock +++ b/flutter/pubspec.lock @@ -243,8 +243,8 @@ packages: dependency: "direct main" description: path: "." - ref: c09d65018f402dd0d6073149fe6705185101a270 - resolved-ref: c09d65018f402dd0d6073149fe6705185101a270 + ref: f25487b8aacfcc9d22b86a84e97eda1a5c07ccaf + resolved-ref: f25487b8aacfcc9d22b86a84e97eda1a5c07ccaf url: "https://github.com/Kingtous/rustdesk_desktop_multi_window" source: git version: "0.1.0" From 06844f2f4f0407931f62c31b3e444b82ae0b547b Mon Sep 17 00:00:00 2001 From: csf Date: Fri, 14 Oct 2022 20:44:57 +0900 Subject: [PATCH 3/3] double click toggle maximize --- .../lib/desktop/widgets/tabbar_widget.dart | 173 ++++++++++-------- 1 file changed, 95 insertions(+), 78 deletions(-) diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index b11ded495..3eadf75fd 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -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, @@ -351,20 +342,22 @@ class DesktopTab extends StatelessWidget { } class WindowActionPanel extends StatefulWidget { - final bool mainTab; + final bool isMainWindow; final DesktopTabType tabType; final Rx state; final bool showMinimize; final bool showMaximize; final bool showClose; + final Widget? tail; final Future 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, @@ -387,7 +380,7 @@ class WindowActionPanelState extends State DesktopMultiWindow.addListener(this); windowManager.addListener(this); - if (widget.mainTab) { + if (widget.isMainWindow) { windowManager.isMaximized().then((maximized) { if (isMaximized != maximized) { WidgetsBinding.instance.addPostFrameCallback( @@ -430,23 +423,24 @@ class WindowActionPanelState extends State super.onWindowUnmaximize(); } - @override - void onWindowClose() { - debugPrint("onWindowClose : is Main : ${widget.mainTab}"); - super.onWindowClose(); - } - @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: !widget.showMinimize, child: ActionIcon( message: 'Minimize', icon: IconFont.min, onTap: () { - if (widget.mainTab) { + if (widget.isMainWindow) { windowManager.minimize(); } else { WindowController.fromWindowId(windowId!).minimize(); @@ -459,24 +453,7 @@ class WindowActionPanelState extends State child: ActionIcon( message: isMaximized ? "Restore" : "Maximize", icon: isMaximized ? IconFont.restore : IconFont.max, - onTap: () { - if (widget.mainTab) { - if (isMaximized) { - windowManager.unmaximize(); - } else { - windowManager.maximize(); - } - } else { - final wc = WindowController.fromWindowId(windowId!); - if (isMaximized) { - wc.unmaximize(); - } else { - wc.maximize(); - } - } - // setState for sub window, wc.unmaximize/maximize() will not invoke onWindowMaximize/Unmaximize - setState(() => isMaximized = !isMaximized); - }, + onTap: _toggleMaximize, isClose: false, )), Offstage( @@ -487,7 +464,7 @@ class WindowActionPanelState extends State onTap: () async { final res = await widget.onClose?.call() ?? true; if (res) { - if (widget.mainTab) { + if (widget.isMainWindow) { windowManager.close(); } else { // only hide for multi window, not close @@ -500,7 +477,47 @@ class WindowActionPanelState extends State 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 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; + } } }