diff --git a/flutter/lib/common/shared_state.dart b/flutter/lib/common/shared_state.dart index 9e741846f..4cfe219e4 100644 --- a/flutter/lib/common/shared_state.dart +++ b/flutter/lib/common/shared_state.dart @@ -179,3 +179,25 @@ class RemoteCursorMovedState { static RxBool find(String id) => Get.find(tag: tag(id)); } + +class RemoteCountState { + static String tag() => 'remote_count_'; + + static void init() { + final key = tag(); + if (!Get.isRegistered(tag: key)) { + // Server side, default true + final RxInt state = 1.obs; + Get.put(state, tag: key); + } + } + + static void delete() { + final key = tag(); + if (Get.isRegistered(tag: key)) { + Get.delete(tag: key); + } + } + + static RxInt find() => Get.find(tag: tag()); +} diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart index 82a9227c7..8672a1dad 100644 --- a/flutter/lib/desktop/pages/desktop_tab_page.dart +++ b/flutter/lib/desktop/pages/desktop_tab_page.dart @@ -9,6 +9,8 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart'; import 'package:get/get.dart'; import 'package:window_manager/window_manager.dart'; +import '../../common/shared_state.dart'; + class DesktopTabPage extends StatefulWidget { const DesktopTabPage({Key? key}) : super(key: key); @@ -40,6 +42,7 @@ class _DesktopTabPageState extends State { void initState() { super.initState(); Get.put(tabController); + RemoteCountState.init(); tabController.add(TabInfo( key: kTabLabelHomePage, label: kTabLabelHomePage, diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 52f474fbd..396e736e0 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -28,11 +28,13 @@ class RemotePage extends StatefulWidget { const RemotePage({ Key? key, required this.id, + required this.windowId, required this.tabBarHeight, required this.windowBorderWidth, }) : super(key: key); final String id; + final int windowId; final double tabBarHeight; final double windowBorderWidth; @@ -239,6 +241,7 @@ class _RemotePageState extends State paints.add(QualityMonitor(_ffi.qualityMonitorModel)); paints.add(RemoteMenubar( id: widget.id, + windowId: widget.windowId, ffi: _ffi, onEnterOrLeaveImageSetter: (func) => _onEnterOrLeaveImage4Menubar = func, onEnterOrLeaveImageCleaner: () => _onEnterOrLeaveImage4Menubar = null, diff --git a/flutter/lib/desktop/pages/remote_tab_page.dart b/flutter/lib/desktop/pages/remote_tab_page.dart index 7b38488fb..352e4682a 100644 --- a/flutter/lib/desktop/pages/remote_tab_page.dart +++ b/flutter/lib/desktop/pages/remote_tab_page.dart @@ -32,6 +32,7 @@ class _ConnectionTabPageState extends State { var connectionMap = RxList.empty(growable: true); _ConnectionTabPageState(Map params) { + RemoteCountState.init(); final RxBool fullscreen = Get.find(tag: 'fullscreen'); final peerId = params['id']; if (peerId != null) { @@ -45,10 +46,12 @@ class _ConnectionTabPageState extends State { page: Obx(() => RemotePage( key: ValueKey(peerId), id: peerId, + windowId: windowId(), tabBarHeight: fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight, windowBorderWidth: fullscreen.isTrue ? 0 : kWindowBorderWidth, )))); + _update_remote_count(); } } @@ -79,6 +82,7 @@ class _ConnectionTabPageState extends State { page: Obx(() => RemotePage( key: ValueKey(id), id: id, + windowId: windowId(), tabBarHeight: fullscreen.isTrue ? 0 : kDesktopRemoteTabBarHeight, windowBorderWidth: fullscreen.isTrue ? 0 : kWindowBorderWidth, @@ -86,6 +90,7 @@ class _ConnectionTabPageState extends State { } else if (call.method == "onDestroy") { tabController.clear(); } + _update_remote_count(); }); } @@ -161,6 +166,7 @@ class _ConnectionTabPageState extends State { WindowController.fromWindowId(windowId()).hide(); } ConnectionTypeState.delete(id); + _update_remote_count(); } int windowId() { @@ -178,7 +184,7 @@ class _ConnectionTabPageState extends State { } Future handleWindowCloseButton() async { - final connLength = tabController.state.value.tabs.length; + final connLength = tabController.length; if (connLength < 1) { return true; } else if (connLength == 1) { @@ -189,8 +195,12 @@ class _ConnectionTabPageState extends State { final res = await closeConfirmDialog(); if (res) { tabController.clear(); + _update_remote_count(); } return res; } } + + _update_remote_count() => + RemoteCountState.find().value = tabController.length; } diff --git a/flutter/lib/desktop/widgets/remote_menubar.dart b/flutter/lib/desktop/widgets/remote_menubar.dart index d524ef279..e06af22da 100644 --- a/flutter/lib/desktop/widgets/remote_menubar.dart +++ b/flutter/lib/desktop/widgets/remote_menubar.dart @@ -8,6 +8,7 @@ import 'package:flutter_hbb/models/chat_model.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:rxdart/rxdart.dart' as rxdart; +import 'package:desktop_multi_window/desktop_multi_window.dart'; import '../../common.dart'; import '../../mobile/widgets/dialog.dart'; @@ -26,6 +27,7 @@ class _MenubarTheme { class RemoteMenubar extends StatefulWidget { final String id; + final int windowId; final FFI ffi; final Function(Function(bool)) onEnterOrLeaveImageSetter; final Function() onEnterOrLeaveImageCleaner; @@ -33,6 +35,7 @@ class RemoteMenubar extends StatefulWidget { const RemoteMenubar({ Key? key, required this.id, + required this.windowId, required this.ffi, required this.onEnterOrLeaveImageSetter, required this.onEnterOrLeaveImageCleaner, @@ -306,25 +309,29 @@ class _RemoteMenubarState extends State { return {'supportedHwcodec': supportedHwcodec}; }(), builder: (context, snapshot) { if (snapshot.hasData) { - return mod_menu.PopupMenuButton( - padding: EdgeInsets.zero, - icon: const Icon( - Icons.tv, - color: _MenubarTheme.commonColor, - ), - tooltip: translate('Display Settings'), - position: mod_menu.PopupMenuPosition.under, - itemBuilder: (BuildContext context) => _getDisplayMenu(snapshot.data!) - .map((entry) => entry.build( - context, - const MenuConfig( - commonColor: _MenubarTheme.commonColor, - height: _MenubarTheme.height, - dividerHeight: _MenubarTheme.dividerHeight, - ))) - .expand((i) => i) - .toList(), - ); + return Obx(() { + final remoteCount = RemoteCountState.find().value; + return mod_menu.PopupMenuButton( + padding: EdgeInsets.zero, + icon: const Icon( + Icons.tv, + color: _MenubarTheme.commonColor, + ), + tooltip: translate('Display Settings'), + position: mod_menu.PopupMenuPosition.under, + itemBuilder: (BuildContext context) => + _getDisplayMenu(snapshot.data!, remoteCount) + .map((entry) => entry.build( + context, + const MenuConfig( + commonColor: _MenubarTheme.commonColor, + height: _MenubarTheme.height, + dividerHeight: _MenubarTheme.dividerHeight, + ))) + .expand((i) => i) + .toList(), + ); + }); } else { return const Offstage(); } @@ -586,7 +593,15 @@ class _RemoteMenubarState extends State { return displayMenu; } - List> _getDisplayMenu(dynamic futureData) { + bool _isWindowCanBeAdjusted(int remoteCount) { + final RxBool fullscreen = Get.find(tag: 'fullscreen'); + return remoteCount == 1 && + fullscreen.isFalse && + widget.ffi.canvasModel.scale > 1.0; + } + + List> _getDisplayMenu( + dynamic futureData, int remoteCount) { const EdgeInsets padding = EdgeInsets.only(left: 18.0, right: 8.0); final displayMenu = [ MenuEntryRadios( @@ -739,6 +754,42 @@ class _RemoteMenubarState extends State { MenuEntryDivider(), ]; + if (_isWindowCanBeAdjusted(remoteCount)) { + displayMenu.insert( + 0, + MenuEntryDivider(), + ); + displayMenu.insert( + 0, + MenuEntryButton( + childBuilder: (TextStyle? style) => Container( + alignment: AlignmentDirectional.center, + height: _MenubarTheme.height, + child: Text( + translate('Adjust Window'), + style: style, + )), + proc: () { + () async { + final wndRect = + await WindowController.fromWindowId(widget.windowId) + .getFrame(); + final canvasModel = widget.ffi.canvasModel; + final width = + canvasModel.size.width + canvasModel.windowBorderWidth * 2; + final height = canvasModel.size.height + + canvasModel.tabBarHeight + + canvasModel.windowBorderWidth * 2; + await WindowController.fromWindowId(widget.windowId).setFrame( + Rect.fromLTWH(wndRect.left, wndRect.top, width, height)); + }(); + }, + padding: padding, + dismissOnClicked: true, + ), + ); + } + /// Show Codec Preference if (bind.mainHasHwcodec()) { final List codecs = []; diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index 9bfc010ec..a98b3af20 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -69,6 +69,8 @@ class DesktopTabController { DesktopTabController({required this.tabType}); + int get length => state.value.tabs.length; + void add(TabInfo tab, {bool authorized = false}) { if (!isDesktop) return; final index = state.value.tabs.indexWhere((e) => e.key == tab.key);