From af66d2a73b25e4a942c575f8d66f8961ca524111 Mon Sep 17 00:00:00 2001 From: 21pages Date: Mon, 8 Jul 2024 20:08:05 +0800 Subject: [PATCH] main window add block mask, cm add keyboard block (#8640) * block window body only so user can still click minisize button. * cm doesn't show mask * Remove focusable Offstage in tabbar_widget.dart Signed-off-by: 21pages --- flutter/lib/common.dart | 22 +- .../lib/desktop/pages/desktop_tab_page.dart | 14 +- flutter/lib/desktop/pages/server_page.dart | 32 ++- .../lib/desktop/widgets/tabbar_widget.dart | 229 ++++++++++-------- 4 files changed, 162 insertions(+), 135 deletions(-) diff --git a/flutter/lib/common.dart b/flutter/lib/common.dart index 390d75ff4..0c46836e4 100644 --- a/flutter/lib/common.dart +++ b/flutter/lib/common.dart @@ -2715,20 +2715,26 @@ Future shouldBeBlocked(RxBool block, WhetherUseRemoteBlock? use) async { } typedef WhetherUseRemoteBlock = Future Function(); -Widget buildRemoteBlock({required Widget child, WhetherUseRemoteBlock? use}) { - var block = false.obs; +Widget buildRemoteBlock( + {required Widget child, + required RxBool block, + required bool mask, + WhetherUseRemoteBlock? use}) { return Obx(() => MouseRegion( onEnter: (_) async { await shouldBeBlocked(block, use); }, onExit: (event) => block.value = false, child: Stack(children: [ - child, - Offstage( - offstage: !block.value, - child: Container( - color: Colors.black.withOpacity(0.5), - )), + // scope block tab + FocusScope(child: child, canRequestFocus: !block.value), + // mask block click, cm not block click and still use check_click_time to avoid block local click + if (mask) + Offstage( + offstage: !block.value, + child: Container( + color: Colors.black.withOpacity(0.5), + )), ]), )); } diff --git a/flutter/lib/desktop/pages/desktop_tab_page.dart b/flutter/lib/desktop/pages/desktop_tab_page.dart index 9c42ab465..a981be01a 100644 --- a/flutter/lib/desktop/pages/desktop_tab_page.dart +++ b/flutter/lib/desktop/pages/desktop_tab_page.dart @@ -116,22 +116,14 @@ class _DesktopTabPageState extends State isClose: false, ), ), + blockTab: _block, ))); - widget() => MouseRegion( - onEnter: (_) async { - // mouseIn = true; - await shouldBeBlocked(_block, canBeBlocked); - }, - onExit: (_) { - // mouseIn = false; - }, - child: FocusScope(child: tabWidget, canRequestFocus: !_block.value)); return isMacOS || kUseCompatibleUiMode - ? Obx(() => widget()) + ? Obx(() => tabWidget) : Obx( () => DragToResizeArea( resizeEdgeSize: stateGlobal.resizeEdgeSize.value, - child: widget(), + child: tabWidget, ), ); } diff --git a/flutter/lib/desktop/pages/server_page.dart b/flutter/lib/desktop/pages/server_page.dart index 4af54b9af..43f9c2bd3 100644 --- a/flutter/lib/desktop/pages/server_page.dart +++ b/flutter/lib/desktop/pages/server_page.dart @@ -104,7 +104,18 @@ class ConnectionManager extends StatefulWidget { State createState() => ConnectionManagerState(); } -class ConnectionManagerState extends State { +class ConnectionManagerState extends State + with WidgetsBindingObserver { + final RxBool _block = false.obs; + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + if (state == AppLifecycleState.resumed) { + shouldBeBlocked(_block, null); + } + } + @override void initState() { gFFI.serverModel.updateClientState(); @@ -127,9 +138,16 @@ class ConnectionManagerState extends State { } }; gFFI.chatModel.isConnManager = true; + WidgetsBinding.instance.addObserver(this); super.initState(); } + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + @override Widget build(BuildContext context) { final serverModel = Provider.of(context); @@ -165,6 +183,7 @@ class ConnectionManagerState extends State { selectedBorderColor: MyTheme.accent, maxLabelWidth: 100, tail: null, //buildScrollJumper(), + blockTab: _block, selectedTabBackgroundColor: Theme.of(context).hintColor.withOpacity(0), tabBuilder: (key, icon, label, themeConf) { @@ -208,14 +227,9 @@ class ConnectionManagerState extends State { builder: (_, model, child) => SizedBox( width: realChatPageWidth, child: buildRemoteBlock( - child: Container( - decoration: BoxDecoration( - border: Border( - right: BorderSide( - color: Theme.of(context) - .dividerColor))), - child: buildSidePage()), - ), + child: buildSidePage(), + block: _block, + mask: true), )), SizedBox( width: realClosedWidth, diff --git a/flutter/lib/desktop/widgets/tabbar_widget.dart b/flutter/lib/desktop/widgets/tabbar_widget.dart index fe8519847..c7d9e5493 100644 --- a/flutter/lib/desktop/widgets/tabbar_widget.dart +++ b/flutter/lib/desktop/widgets/tabbar_widget.dart @@ -248,6 +248,7 @@ class DesktopTab extends StatelessWidget { final Color? selectedTabBackgroundColor; final Color? unSelectedTabBackgroundColor; final Color? selectedBorderColor; + final RxBool? blockTab; final DesktopTabController controller; @@ -277,6 +278,7 @@ class DesktopTab extends StatelessWidget { this.selectedTabBackgroundColor, this.unSelectedTabBackgroundColor, this.selectedBorderColor, + this.blockTab, }) : super(key: key) { tabType = controller.tabType; isMainWindow = tabType == DesktopTabType.main || @@ -292,10 +294,10 @@ class DesktopTab extends StatelessWidget { @override Widget build(BuildContext context) { return Column(children: [ - Obx(() => Offstage( - offstage: !stateGlobal.showTabBar.isTrue || - (kUseCompatibleUiMode && isHideSingleItem()), - child: SizedBox( + Obx(() { + if (stateGlobal.showTabBar.isTrue && + !(kUseCompatibleUiMode && isHideSingleItem())) { + return SizedBox( height: _kTabBarHeight, child: Column( children: [ @@ -308,7 +310,11 @@ class DesktopTab extends StatelessWidget { ), ], ), - ))), + ); + } else { + return Offstage(); + } + }), Expanded( child: pageViewBuilder != null ? pageViewBuilder!(_buildPageView()) @@ -317,10 +323,15 @@ class DesktopTab extends StatelessWidget { } Widget _buildBlock({required Widget child}) { - if (tabType != DesktopTabType.main) { + if (blockTab != null) { + return buildRemoteBlock( + child: child, + block: blockTab!, + use: canBeBlocked, + mask: tabType == DesktopTabType.main); + } else { return child; } - return buildRemoteBlock(child: child, use: canBeBlocked); } List _tabWidgets = []; @@ -703,72 +714,69 @@ class WindowActionPanelState extends State return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - Obx(() => Offstage( - offstage: - !(showTabDowndown() && existingInvisibleTab().isNotEmpty), - child: _TabDropDownButton( - controller: widget.tabController, - labelGetter: widget.labelGetter, - tabkeys: existingInvisibleTab()), - )), - Offstage(offstage: widget.tail == null, child: widget.tail), - Offstage( - offstage: kUseCompatibleUiMode, - child: Row( + Obx(() { + if (showTabDowndown() && existingInvisibleTab().isNotEmpty) { + return _TabDropDownButton( + controller: widget.tabController, + labelGetter: widget.labelGetter, + tabkeys: existingInvisibleTab()); + } else { + return Offstage(); + } + }), + if (widget.tail != null) widget.tail!, + if (!kUseCompatibleUiMode) + Row( children: [ - Offstage( - offstage: !widget.showMinimize || isMacOS, - child: ActionIcon( - message: 'Minimize', - icon: IconFont.min, - onTap: () { - if (widget.isMainWindow) { - windowManager.minimize(); - } else { - WindowController.fromWindowId(kWindowId!).minimize(); - } - }, - isClose: false, - )), - Offstage( - offstage: !widget.showMaximize || isMacOS, - child: Obx(() => ActionIcon( - message: stateGlobal.isMaximized.isTrue - ? 'Restore' - : 'Maximize', - icon: stateGlobal.isMaximized.isTrue - ? IconFont.restore - : IconFont.max, - onTap: bind.isIncomingOnly() && isInHomePage() - ? null - : _toggleMaximize, - isClose: false, - ))), - Offstage( - offstage: !widget.showClose || isMacOS, - child: ActionIcon( - message: 'Close', - icon: IconFont.close, - onTap: () async { - final res = await widget.onClose?.call() ?? true; - if (res) { - // hide for all window - // note: the main window can be restored by tray icon - Future.delayed(Duration.zero, () async { - if (widget.isMainWindow) { - await windowManager.close(); - } else { - await WindowController.fromWindowId(kWindowId!) - .close(); - } - }); - } - }, - isClose: true, - )) + if (widget.showMinimize && !isMacOS) + ActionIcon( + message: 'Minimize', + icon: IconFont.min, + onTap: () { + if (widget.isMainWindow) { + windowManager.minimize(); + } else { + WindowController.fromWindowId(kWindowId!).minimize(); + } + }, + isClose: false, + ), + if (widget.showMaximize && !isMacOS) + Obx(() => ActionIcon( + message: stateGlobal.isMaximized.isTrue + ? 'Restore' + : 'Maximize', + icon: stateGlobal.isMaximized.isTrue + ? IconFont.restore + : IconFont.max, + onTap: bind.isIncomingOnly() && isInHomePage() + ? null + : _toggleMaximize, + isClose: false, + )), + if (widget.showClose && !isMacOS) + ActionIcon( + message: 'Close', + icon: IconFont.close, + onTap: () async { + final res = await widget.onClose?.call() ?? true; + if (res) { + // hide for all window + // note: the main window can be restored by tray icon + Future.delayed(Duration.zero, () async { + if (widget.isMainWindow) { + await windowManager.close(); + } else { + await WindowController.fromWindowId(kWindowId!) + .close(); + } + }); + } + }, + isClose: true, + ) ], ), - ), ], ); } @@ -1172,22 +1180,26 @@ class _CloseButton extends StatelessWidget { @override Widget build(BuildContext context) { return SizedBox( - width: _kIconSize, - child: Offstage( - offstage: !visible, - child: InkWell( - hoverColor: MyTheme.tabbar(context).closeHoverColor, - customBorder: const CircleBorder(), - onTap: () => onClose(), - child: Icon( - Icons.close, - size: _kIconSize, - color: tabSelected - ? MyTheme.tabbar(context).selectedIconColor - : MyTheme.tabbar(context).unSelectedIconColor, - ), - ), - )).paddingOnly(left: 10); + width: _kIconSize, + child: () { + if (visible) { + return InkWell( + hoverColor: MyTheme.tabbar(context).closeHoverColor, + customBorder: const CircleBorder(), + onTap: () => onClose(), + child: Icon( + Icons.close, + size: _kIconSize, + color: tabSelected + ? MyTheme.tabbar(context).selectedIconColor + : MyTheme.tabbar(context).unSelectedIconColor, + ), + ); + } else { + return Offstage(); + } + }()) + .paddingOnly(left: 10); } } @@ -1341,27 +1353,30 @@ class _TabDropDownButtonState extends State<_TabDropDownButton> { child: InkWell(child: Text(label)), ), Obx( - () => Offstage( - offstage: !(tabInfo?.onTabCloseButton != null && - menuHover.value), - child: InkWell( - onTap: () { - tabInfo?.onTabCloseButton?.call(); - if (Navigator.of(context).canPop()) { - Navigator.of(context).pop(); - } - }, - child: MouseRegion( - cursor: SystemMouseCursors.click, - onHover: (event) => - setState(() => btnHover.value = true), - onExit: (event) => - setState(() => btnHover.value = false), - child: Icon(Icons.close, - color: - btnHover.value ? Colors.red : null))), - ), - ) + () { + if (tabInfo?.onTabCloseButton != null && + menuHover.value) { + return InkWell( + onTap: () { + tabInfo?.onTabCloseButton?.call(); + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onHover: (event) => + setState(() => btnHover.value = true), + onExit: (event) => + setState(() => btnHover.value = false), + child: Icon(Icons.close, + color: + btnHover.value ? Colors.red : null))); + } else { + return Offstage(); + } + }, + ), ], ), ),