2022-08-30 16:50:25 +08:00
import ' dart:async ' ;
2022-08-06 17:08:48 +08:00
import ' dart:math ' ;
2022-11-03 21:58:25 +08:00
import ' dart:ui ' as ui ;
2022-08-06 17:08:48 +08:00
2023-02-03 17:08:40 +08:00
import ' package:bot_toast/bot_toast.dart ' ;
2022-08-18 11:07:53 +08:00
import ' package:desktop_multi_window/desktop_multi_window.dart ' ;
2022-10-20 22:10:26 +08:00
import ' package:flutter/gestures.dart ' ;
2022-09-04 11:03:16 +08:00
import ' package:flutter/material.dart ' hide TabBarTheme ;
2022-08-06 17:08:48 +08:00
import ' package:flutter_hbb/common.dart ' ;
import ' package:flutter_hbb/consts.dart ' ;
2023-10-08 21:44:54 +08:00
import ' package:flutter_hbb/desktop/pages/remote_page.dart ' ;
2022-08-18 11:07:53 +08:00
import ' package:flutter_hbb/main.dart ' ;
2022-08-30 16:50:25 +08:00
import ' package:flutter_hbb/models/platform_model.dart ' ;
2022-11-01 17:01:43 +08:00
import ' package:flutter_hbb/models/state_model.dart ' ;
2022-08-06 17:08:48 +08:00
import ' package:get/get.dart ' ;
2022-10-20 22:10:26 +08:00
import ' package:get/get_rx/src/rx_workers/utils/debouncer.dart ' ;
2022-08-18 10:54:09 +08:00
import ' package:scroll_pos/scroll_pos.dart ' ;
2022-08-30 16:45:47 +08:00
import ' package:window_manager/window_manager.dart ' ;
2024-05-06 17:10:02 +08:00
import ' package:visibility_detector/visibility_detector.dart ' ;
2022-08-06 17:08:48 +08:00
2022-08-24 21:09:18 +08:00
import ' ../../utils/multi_window_manager.dart ' ;
2022-08-06 17:08:48 +08:00
const double _kTabBarHeight = kDesktopRemoteTabBarHeight ;
const double _kIconSize = 18 ;
const double _kDividerIndent = 10 ;
2022-08-20 19:57:16 +08:00
const double _kActionIconSize = 12 ;
2022-08-06 17:08:48 +08:00
2022-08-11 16:03:04 +08:00
class TabInfo {
2023-06-07 20:31:54 +08:00
final String key ; // Notice: cm use client_id.toString() as key
2022-08-24 20:12:04 +08:00
final String label ;
final IconData ? selectedIcon ;
final IconData ? unselectedIcon ;
2022-09-08 21:03:20 +08:00
final bool closable ;
2022-09-08 19:26:55 +08:00
final VoidCallback ? onTabCloseButton ;
2022-10-26 22:50:36 +08:00
final VoidCallback ? onTap ;
2022-08-24 20:12:04 +08:00
final Widget page ;
2022-08-11 16:03:04 +08:00
2022-08-18 10:54:09 +08:00
TabInfo (
2022-08-22 20:18:31 +08:00
{ required this . key ,
required this . label ,
this . selectedIcon ,
this . unselectedIcon ,
2022-08-24 20:12:04 +08:00
this . closable = true ,
2022-09-08 19:26:55 +08:00
this . onTabCloseButton ,
2022-10-26 22:50:36 +08:00
this . onTap ,
2022-08-24 20:12:04 +08:00
required this . page } ) ;
2022-08-11 16:03:04 +08:00
}
2022-08-30 16:50:25 +08:00
enum DesktopTabType {
main ,
cm ,
remoteScreen ,
fileTransfer ,
portForward ,
2023-03-01 14:18:46 +08:00
install ,
2022-08-30 16:50:25 +08:00
}
2022-08-24 20:17:51 +08:00
class DesktopTabState {
2022-08-24 20:12:04 +08:00
final List < TabInfo > tabs = [ ] ;
2022-08-18 10:54:09 +08:00
final ScrollPosController scrollController =
ScrollPosController ( itemCount: 0 ) ;
2022-08-24 20:12:04 +08:00
final PageController pageController = PageController ( ) ;
int selected = 0 ;
2022-08-06 17:08:48 +08:00
2022-10-26 22:50:36 +08:00
TabInfo get selectedTabInfo = > tabs [ selected ] ;
2022-08-24 20:17:51 +08:00
DesktopTabState ( ) {
2022-08-18 10:54:09 +08:00
scrollController . itemCount = tabs . length ;
}
2022-08-24 20:12:04 +08:00
}
2022-08-06 17:08:48 +08:00
2022-11-03 21:58:25 +08:00
CancelFunc showRightMenu ( ToastBuilder builder ,
{ BuildContext ? context , Offset ? target } ) {
return BotToast . showAttachedWidget (
target: target ,
targetContext: context ,
2023-11-07 11:56:07 +08:00
verticalOffset: 0.0 ,
horizontalOffset: 0.0 ,
2023-08-24 00:00:18 +08:00
duration: Duration ( seconds: 300 ) ,
2022-11-03 21:58:25 +08:00
animationDuration: Duration ( milliseconds: 0 ) ,
animationReverseDuration: Duration ( milliseconds: 0 ) ,
preferDirection: PreferDirection . rightTop ,
ignoreContentClick: false ,
onlyOne: true ,
allowClick: true ,
enableSafeArea: true ,
backgroundColor: Color ( 0x00000000 ) ,
attachedBuilder: builder ,
) ;
}
2022-08-24 20:17:51 +08:00
class DesktopTabController {
final state = DesktopTabState ( ) . obs ;
2022-09-01 21:18:53 +08:00
final DesktopTabType tabType ;
2022-08-11 18:08:35 +08:00
2022-08-24 20:56:42 +08:00
/// index, key
2022-10-27 10:56:14 +08:00
Function ( int , String ) ? onRemoved ;
2023-06-07 20:31:54 +08:00
Function ( String ) ? onSelected ;
2022-08-24 21:52:21 +08:00
2022-10-27 10:56:14 +08:00
DesktopTabController (
{ required this . tabType , this . onRemoved , this . onSelected } ) ;
2022-09-01 21:18:53 +08:00
2022-10-08 17:27:30 +08:00
int get length = > state . value . tabs . length ;
2022-10-26 20:39:28 +08:00
void add ( TabInfo tab ) {
2022-08-23 15:25:18 +08:00
if ( ! isDesktop ) return ;
2022-08-24 20:12:04 +08:00
final index = state . value . tabs . indexWhere ( ( e ) = > e . key = = tab . key ) ;
int toIndex ;
2022-08-11 18:08:35 +08:00
if ( index > = 0 ) {
2022-08-24 20:12:04 +08:00
toIndex = index ;
2022-08-11 18:08:35 +08:00
} else {
2022-08-24 20:12:04 +08:00
state . update ( ( val ) {
val ! . tabs . add ( tab ) ;
} ) ;
2022-08-30 14:43:57 +08:00
state . value . scrollController . itemCount = state . value . tabs . length ;
2022-08-24 20:12:04 +08:00
toIndex = state . value . tabs . length - 1 ;
assert ( toIndex > = 0 ) ;
2022-08-11 18:08:35 +08:00
}
2022-08-22 20:18:31 +08:00
try {
2023-06-07 20:31:54 +08:00
// tabPage has not been initialized, call `onSelected` at the end of initState
jumpTo ( toIndex , callOnSelected: false ) ;
2022-08-22 20:18:31 +08:00
} catch ( e ) {
// call before binding controller will throw
2022-08-24 20:12:04 +08:00
debugPrint ( " Failed to jumpTo: $ e " ) ;
2022-08-22 20:18:31 +08:00
}
}
2022-08-24 20:12:04 +08:00
void remove ( int index ) {
2022-08-23 15:25:18 +08:00
if ( ! isDesktop ) return ;
2022-08-24 20:12:04 +08:00
final len = state . value . tabs . length ;
2022-08-24 20:56:42 +08:00
if ( index < 0 | | index > len - 1 ) return ;
final key = state . value . tabs [ index ] . key ;
2022-08-24 20:12:04 +08:00
final currentSelected = state . value . selected ;
int toIndex = 0 ;
if ( index = = len - 1 ) {
toIndex = max ( 0 , currentSelected - 1 ) ;
} else if ( index < len - 1 & & index < currentSelected ) {
toIndex = max ( 0 , currentSelected - 1 ) ;
2022-08-22 20:18:31 +08:00
}
2022-08-24 20:12:04 +08:00
state . value . tabs . removeAt ( index ) ;
state . value . scrollController . itemCount = state . value . tabs . length ;
jumpTo ( toIndex ) ;
2022-10-27 10:56:14 +08:00
onRemoved ? . call ( index , key ) ;
2022-08-22 20:18:31 +08:00
}
2023-06-07 20:31:54 +08:00
/// For addTab, tabPage has not been initialized, set [callOnSelected] to false,
/// and call [onSelected] at the end of initState
2023-08-02 20:38:09 +08:00
bool jumpTo ( int index , { bool callOnSelected = true } ) {
if ( ! isDesktop | | index < 0 ) {
return false ;
}
2022-08-24 20:12:04 +08:00
state . update ( ( val ) {
2024-01-02 16:58:10 +08:00
if ( val ! = null ) {
val . selected = index ;
Future . delayed ( Duration ( milliseconds: 100 ) , ( ( ) {
if ( val . pageController . hasClients ) {
val . pageController . jumpToPage ( index ) ;
}
val . scrollController . itemCount = val . tabs . length ;
if ( val . scrollController . hasClients & &
val . scrollController . itemCount > index ) {
val . scrollController
. scrollToItem ( index , center: false , animate: true ) ;
}
} ) ) ;
}
2022-08-24 20:12:04 +08:00
} ) ;
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
if ( ( isDesktop & & ( bind . isIncomingOnly ( ) | | bind . isOutgoingOnly ( ) ) ) | |
callOnSelected ) {
2023-06-07 20:31:54 +08:00
if ( state . value . tabs . length > index ) {
final key = state . value . tabs [ index ] . key ;
onSelected ? . call ( key ) ;
}
2022-09-01 21:18:53 +08:00
}
2023-08-02 20:38:09 +08:00
return true ;
2022-08-11 18:08:35 +08:00
}
2022-08-24 20:56:42 +08:00
2023-08-02 20:38:09 +08:00
bool jumpToByKey ( String key , { bool callOnSelected = true } ) = >
jumpTo ( state . value . tabs . indexWhere ( ( tab ) = > tab . key = = key ) ,
callOnSelected: callOnSelected ) ;
2023-10-08 21:44:54 +08:00
bool jumpToByKeyAndDisplay ( String key , int display ) {
for ( int i = 0 ; i < state . value . tabs . length ; i + + ) {
final tab = state . value . tabs [ i ] ;
if ( tab . key = = key ) {
final ffi = ( tab . page as RemotePage ) . ffi ;
if ( ffi . ffiModel . pi . currentDisplay = = display ) {
return jumpTo ( i , callOnSelected: true ) ;
}
}
}
return false ;
}
2022-08-24 20:56:42 +08:00
void closeBy ( String ? key ) {
if ( ! isDesktop ) return ;
2022-10-27 10:56:14 +08:00
assert ( onRemoved ! = null ) ;
2022-08-24 20:56:42 +08:00
if ( key = = null ) {
if ( state . value . selected < state . value . tabs . length ) {
remove ( state . value . selected ) ;
}
} else {
2022-09-08 19:26:55 +08:00
final index = state . value . tabs . indexWhere ( ( tab ) = > tab . key = = key ) ;
remove ( index ) ;
2022-08-24 20:56:42 +08:00
}
}
2022-08-30 20:48:03 +08:00
void clear ( ) {
state . value . tabs . clear ( ) ;
state . refresh ( ) ;
}
2023-07-06 09:40:03 +08:00
Widget ? widget ( String key ) {
return state . value . tabs . firstWhereOrNull ( ( tab ) = > tab . key = = key ) ? . page ;
}
2022-08-24 20:12:04 +08:00
}
2022-08-22 20:18:31 +08:00
2022-08-29 18:48:12 +08:00
class TabThemeConf {
double iconSize ;
2022-10-24 16:44:43 +08:00
2022-09-04 11:03:16 +08:00
TabThemeConf ( { required this . iconSize } ) ;
2022-08-29 18:48:12 +08:00
}
typedef TabBuilder = Widget Function (
String key , Widget icon , Widget label , TabThemeConf themeConf ) ;
2022-11-03 21:58:25 +08:00
typedef TabMenuBuilder = Widget Function ( String key ) ;
2022-08-29 18:48:12 +08:00
typedef LabelGetter = Rx < String > Function ( String key ) ;
2022-10-20 22:56:23 +08:00
/// [_lastClickTime], help to handle double click
2022-11-01 19:56:46 +08:00
int _lastClickTime =
2022-11-02 11:32:30 +08:00
DateTime . now ( ) . millisecondsSinceEpoch - bind . getDoubleClickTime ( ) - 1000 ;
2022-10-20 22:56:23 +08:00
2023-06-23 17:05:48 +08:00
// ignore: must_be_immutable
2022-08-24 20:12:04 +08:00
class DesktopTab extends StatelessWidget {
final bool showLogo ;
final bool showTitle ;
final bool showMinimize ;
final bool showMaximize ;
final bool showClose ;
final Widget Function ( Widget pageView ) ? pageViewBuilder ;
2022-11-03 21:58:25 +08:00
// Right click tab menu
final TabMenuBuilder ? tabMenuBuilder ;
2022-08-24 20:12:04 +08:00
final Widget ? tail ;
2022-09-09 19:29:19 +08:00
final Future < bool > Function ( ) ? onWindowCloseButton ;
2022-08-29 18:48:12 +08:00
final TabBuilder ? tabBuilder ;
final LabelGetter ? labelGetter ;
2022-10-20 22:10:26 +08:00
final double ? maxLabelWidth ;
2022-10-20 22:56:23 +08:00
final Color ? selectedTabBackgroundColor ;
final Color ? unSelectedTabBackgroundColor ;
2023-07-08 17:55:55 +08:00
final Color ? selectedBorderColor ;
2022-08-24 20:12:04 +08:00
2022-08-24 20:17:51 +08:00
final DesktopTabController controller ;
2022-10-24 16:44:43 +08:00
2022-08-30 20:48:03 +08:00
Rx < DesktopTabState > get state = > controller . state ;
2022-10-20 22:10:26 +08:00
final _scrollDebounce = Debouncer ( delay: Duration ( milliseconds: 50 ) ) ;
2022-09-01 21:18:53 +08:00
late final DesktopTabType tabType ;
late final bool isMainWindow ;
2022-08-24 20:12:04 +08:00
2024-05-06 17:10:02 +08:00
final RxList < String > invisibleTabKeys = RxList . empty ( ) ;
2022-09-01 21:18:53 +08:00
DesktopTab ( {
Key ? key ,
2022-08-29 18:48:12 +08:00
required this . controller ,
this . showLogo = true ,
2023-02-10 15:05:35 +08:00
this . showTitle = false ,
2022-08-29 18:48:12 +08:00
this . showMinimize = true ,
this . showMaximize = true ,
this . showClose = true ,
this . pageViewBuilder ,
2022-11-03 21:58:25 +08:00
this . tabMenuBuilder ,
2022-08-29 18:48:12 +08:00
this . tail ,
2022-09-08 19:26:55 +08:00
this . onWindowCloseButton ,
2022-08-29 18:48:12 +08:00
this . tabBuilder ,
this . labelGetter ,
2022-10-20 22:10:26 +08:00
this . maxLabelWidth ,
2022-10-20 22:56:23 +08:00
this . selectedTabBackgroundColor ,
this . unSelectedTabBackgroundColor ,
2023-07-08 17:55:55 +08:00
this . selectedBorderColor ,
2022-09-01 21:18:53 +08:00
} ) : super ( key: key ) {
tabType = controller . tabType ;
2023-03-01 14:18:46 +08:00
isMainWindow = tabType = = DesktopTabType . main | |
tabType = = DesktopTabType . cm | |
tabType = = DesktopTabType . install ;
2022-09-01 21:18:53 +08:00
}
2022-08-24 20:12:04 +08:00
2023-09-26 15:11:31 +08:00
static RxString tablabelGetter ( String peerId ) {
2023-09-28 09:05:10 +08:00
final alias = bind . mainGetPeerOptionSync ( id: peerId , key: ' alias ' ) ;
return RxString ( getDesktopTabLabel ( peerId , alias ) ) ;
2022-11-12 22:33:10 +08:00
}
2022-08-24 20:12:04 +08:00
@ override
Widget build ( BuildContext context ) {
return Column ( children: [
2022-11-01 17:01:43 +08:00
Obx ( ( ) = > Offstage (
2022-11-30 13:56:02 +08:00
offstage: ! stateGlobal . showTabBar . isTrue | |
( kUseCompatibleUiMode & & isHideSingleItem ( ) ) ,
2022-09-04 11:03:16 +08:00
child: SizedBox (
2022-08-24 21:20:50 +08:00
height: _kTabBarHeight ,
child: Column (
children: [
2022-09-04 11:03:16 +08:00
SizedBox (
2022-08-24 21:20:50 +08:00
height: _kTabBarHeight - 1 ,
child: _buildBar ( ) ,
) ,
2022-09-04 11:03:16 +08:00
const Divider (
2022-08-24 21:20:50 +08:00
height: 1 ,
) ,
] ,
2022-08-24 20:12:04 +08:00
) ,
2022-11-01 17:01:43 +08:00
) ) ) ,
2022-08-24 20:12:04 +08:00
Expanded (
child: pageViewBuilder ! = null
? pageViewBuilder ! ( _buildPageView ( ) )
: _buildPageView ( ) )
] ) ;
}
2022-08-30 16:50:25 +08:00
Widget _buildBlock ( { required Widget child } ) {
if ( tabType ! = DesktopTabType . main ) {
return child ;
}
2023-07-05 17:35:37 +08:00
return buildRemoteBlock (
child: child ,
use: ( ) async {
2024-05-18 23:13:54 +08:00
var access_mode = await bind . mainGetOption ( key: kOptionAccessMode ) ;
2023-07-05 17:35:37 +08:00
var option = option2bool (
2024-05-18 23:13:54 +08:00
kOptionAllowRemoteConfigModification ,
2023-07-05 17:35:37 +08:00
await bind . mainGetOption (
2024-05-18 23:13:54 +08:00
key: kOptionAllowRemoteConfigModification ) ) ;
2023-07-05 17:35:37 +08:00
return access_mode = = ' view ' | | ( access_mode . isEmpty & & ! option ) ;
} ) ;
2022-08-30 16:50:25 +08:00
}
2023-02-08 09:11:53 +08:00
List < Widget > _tabWidgets = [ ] ;
2022-08-24 20:12:04 +08:00
Widget _buildPageView ( ) {
2024-05-27 09:29:31 +08:00
final child = _buildBlock (
2022-08-30 16:50:25 +08:00
child: Obx ( ( ) = > PageView (
controller: state . value . pageController ,
2022-12-24 14:29:32 +08:00
physics: NeverScrollableScrollPhysics ( ) ,
2023-02-08 09:11:53 +08:00
children: ( ) {
/// to-do refactor, separate connection state and UI state for remote session.
/// [workaround] PageView children need an immutable list, after it has been passed into PageView
final tabLen = state . value . tabs . length ;
if ( tabLen = = _tabWidgets . length ) {
return _tabWidgets ;
} else if ( _tabWidgets . isNotEmpty & &
tabLen = = _tabWidgets . length + 1 ) {
/// On add. Use the previous list(pointer) to prevent item's state init twice.
/// *[_tabWidgets.isNotEmpty] means TabsWindow(remote_tab_page or file_manager_tab_page) opened before, but was hidden. In this case, we have to reload, otherwise the child can't be built.
_tabWidgets . add ( state . value . tabs . last . page ) ;
return _tabWidgets ;
} else {
/// On remove or change. Use new list(pointer) to reload list children so that items loading order is normal.
/// the Widgets in list must enable [AutomaticKeepAliveClientMixin]
final newList = state . value . tabs . map ( ( v ) = > v . page ) . toList ( ) ;
_tabWidgets = newList ;
return newList ;
}
} ( ) ) ) ) ;
2024-05-27 09:29:31 +08:00
if ( tabType = = DesktopTabType . remoteScreen ) {
return Container ( color: kColorCanvas , child: child ) ;
} else {
return child ;
}
2022-08-24 20:12:04 +08:00
}
2022-11-30 13:56:02 +08:00
/// Check whether to show ListView
///
/// Conditions:
/// - hide single item when only has one item (home) on [DesktopTabPage].
bool isHideSingleItem ( ) {
return state . value . tabs . length = = 1 & &
2023-03-01 14:18:46 +08:00
( controller . tabType = = DesktopTabType . main | |
controller . tabType = = DesktopTabType . install ) ;
2022-11-30 13:56:02 +08:00
}
2022-08-24 20:12:04 +08:00
Widget _buildBar ( ) {
return Row (
2022-10-20 22:10:26 +08:00
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
2022-08-24 20:12:04 +08:00
children: [
2022-10-20 22:10:26 +08:00
Expanded (
child: GestureDetector (
// custom double tap handler
2024-03-16 17:33:58 +08:00
onTap: ! ( bind . isIncomingOnly ( ) & & isInHomePage ( ) ) & &
showMaximize
2022-10-20 22:10:26 +08:00
? ( ) {
final current = DateTime . now ( ) . millisecondsSinceEpoch ;
final elapsed = current - _lastClickTime ;
_lastClickTime = current ;
2022-11-02 11:32:30 +08:00
if ( elapsed < bind . getDoubleClickTime ( ) ) {
2022-10-20 22:10:26 +08:00
// onDoubleTap
toggleMaximize ( isMainWindow )
2023-08-11 19:03:09 +08:00
. then ( ( value ) = > stateGlobal . setMaximized ( value ) ) ;
2022-10-20 22:10:26 +08:00
}
}
2022-10-17 18:37:00 +08:00
: null ,
2022-10-14 19:44:57 +08:00
onPanStart: ( _ ) = > startDragging ( isMainWindow ) ,
2022-10-20 22:10:26 +08:00
child: Row (
children: [
Offstage (
2024-03-24 11:23:06 +08:00
offstage: ! isMacOS ,
2022-10-20 22:10:26 +08:00
child: const SizedBox (
width: 78 ,
) ) ,
2022-11-30 13:56:02 +08:00
Offstage (
2024-03-24 11:23:06 +08:00
offstage: kUseCompatibleUiMode | | isMacOS ,
2022-11-30 13:56:02 +08:00
child: Row ( children: [
Offstage (
2024-02-26 17:49:12 +08:00
offstage: ! showLogo ,
Fix/custom client styles (#7373)
* Fix. qs styles
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* custom client, options
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Move logo.svg to icon.svg
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Refact. Custom client, connection status ui.
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client ui. Disable settings, hide "Change password"
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, logo align center
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, refact, outgoing ui
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, outgoing, settings icon
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, powered by RustDesk
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Custom client, remove unused SizeBox
Signed-off-by: fufesou <shuanglongchen@yeah.net>
* Update config.rs
* Update flutter_ffi.rs
---------
Signed-off-by: fufesou <shuanglongchen@yeah.net>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
2024-03-14 11:36:14 +08:00
child: loadIcon ( 16 ) ,
2024-02-26 17:49:12 +08:00
) ,
2022-11-30 13:56:02 +08:00
Offstage (
offstage: ! showTitle ,
child: const Text (
" RustDesk " ,
style: TextStyle ( fontSize: 13 ) ,
) . marginOnly ( left: 2 ) )
] ) . marginOnly (
left: 5 ,
right: 10 ,
) ,
2022-10-20 22:10:26 +08:00
) ,
Expanded (
child: Listener (
// handle mouse wheel
onPointerSignal: ( e ) {
if ( e is PointerScrollEvent ) {
final sc =
controller . state . value . scrollController ;
if ( ! sc . canScroll ) return ;
_scrollDebounce . call ( ( ) {
sc . animateTo ( sc . offset + e . scrollDelta . dy ,
duration: Duration ( milliseconds: 200 ) ,
curve: Curves . ease ) ;
} ) ;
}
} ,
child: _ListView (
2023-07-08 17:55:55 +08:00
controller: controller ,
2024-05-06 17:10:02 +08:00
invisibleTabKeys: invisibleTabKeys ,
2023-07-08 17:55:55 +08:00
tabBuilder: tabBuilder ,
tabMenuBuilder: tabMenuBuilder ,
labelGetter: labelGetter ,
maxLabelWidth: maxLabelWidth ,
selectedTabBackgroundColor:
selectedTabBackgroundColor ,
unSelectedTabBackgroundColor:
unSelectedTabBackgroundColor ,
selectedBorderColor: selectedBorderColor ,
) ) ) ,
2022-10-20 22:10:26 +08:00
] ,
) ) ) ,
2022-11-30 13:56:02 +08:00
// hide simulated action buttons when we in compatible ui mode, because of reusing system title bar.
2022-11-30 14:05:49 +08:00
WindowActionPanel (
isMainWindow: isMainWindow ,
tabType: tabType ,
state: state ,
2023-10-26 16:05:49 +08:00
tabController: controller ,
2024-05-06 17:10:02 +08:00
invisibleTabKeys: invisibleTabKeys ,
2022-11-30 14:05:49 +08:00
tail: tail ,
showMinimize: showMinimize ,
showMaximize: showMaximize ,
showClose: showClose ,
onClose: onWindowCloseButton ,
2024-05-06 17:10:02 +08:00
labelGetter: labelGetter ,
) . paddingOnly ( left: 10 )
2022-08-24 20:12:04 +08:00
] ,
) ;
2022-08-22 20:18:31 +08:00
}
2022-08-06 17:08:48 +08:00
}
2022-10-14 09:48:33 +08:00
class WindowActionPanel extends StatefulWidget {
2022-10-14 19:44:57 +08:00
final bool isMainWindow ;
2022-09-01 12:07:05 +08:00
final DesktopTabType tabType ;
final Rx < DesktopTabState > state ;
2023-10-26 16:05:49 +08:00
final DesktopTabController tabController ;
2022-08-18 11:07:53 +08:00
2022-08-22 20:18:31 +08:00
final bool showMinimize ;
final bool showMaximize ;
final bool showClose ;
2022-10-14 19:44:57 +08:00
final Widget ? tail ;
2022-09-09 19:29:19 +08:00
final Future < bool > Function ( ) ? onClose ;
2022-08-22 20:18:31 +08:00
2024-05-06 17:10:02 +08:00
final RxList < String > invisibleTabKeys ;
final LabelGetter ? labelGetter ;
2022-08-18 11:07:53 +08:00
const WindowActionPanel (
2022-08-22 20:18:31 +08:00
{ Key ? key ,
2022-10-14 19:44:57 +08:00
required this . isMainWindow ,
2022-09-01 12:07:05 +08:00
required this . tabType ,
required this . state ,
2023-10-26 16:05:49 +08:00
required this . tabController ,
2024-05-06 17:10:02 +08:00
required this . invisibleTabKeys ,
2022-10-14 19:44:57 +08:00
this . tail ,
2022-08-22 20:18:31 +08:00
this . showMinimize = true ,
this . showMaximize = true ,
2022-08-30 20:48:03 +08:00
this . showClose = true ,
2024-05-06 17:10:02 +08:00
this . onClose ,
this . labelGetter } )
2022-08-18 11:07:53 +08:00
: super ( key: key ) ;
2022-10-14 09:48:33 +08:00
@ override
State < StatefulWidget > createState ( ) {
return WindowActionPanelState ( ) ;
}
}
class WindowActionPanelState extends State < WindowActionPanel >
with MultiWindowListener , WindowListener {
2023-07-26 00:40:03 +08:00
final _saveFrameDebounce = Debouncer ( delay: Duration ( seconds: 1 ) ) ;
2023-09-07 21:50:03 +08:00
Timer ? _macOSCheckRestoreTimer ;
int _macOSCheckRestoreCounter = 0 ;
2023-07-26 00:40:03 +08:00
2022-10-14 09:48:33 +08:00
@ override
void initState ( ) {
super . initState ( ) ;
DesktopMultiWindow . addListener ( this ) ;
windowManager . addListener ( this ) ;
2022-10-17 18:37:00 +08:00
Future . delayed ( Duration ( milliseconds: 500 ) , ( ) {
if ( widget . isMainWindow ) {
windowManager . isMaximized ( ) . then ( ( maximized ) {
2023-09-03 22:18:48 +08:00
if ( stateGlobal . isMaximized . value ! = maximized ) {
2022-10-17 18:37:00 +08:00
WidgetsBinding . instance . addPostFrameCallback (
2023-09-03 22:18:48 +08:00
( _ ) = > setState ( ( ) = > stateGlobal . setMaximized ( maximized ) ) ) ;
2022-10-17 18:37:00 +08:00
}
} ) ;
} else {
2023-01-23 22:07:50 +08:00
final wc = WindowController . fromWindowId ( kWindowId ! ) ;
2022-10-17 18:37:00 +08:00
wc . isMaximized ( ) . then ( ( maximized ) {
debugPrint ( " isMaximized $ maximized " ) ;
2023-09-03 22:18:48 +08:00
if ( stateGlobal . isMaximized . value ! = maximized ) {
2022-10-17 18:37:00 +08:00
WidgetsBinding . instance . addPostFrameCallback (
2023-09-03 22:18:48 +08:00
( _ ) = > setState ( ( ) = > stateGlobal . setMaximized ( maximized ) ) ) ;
2022-10-17 18:37:00 +08:00
}
} ) ;
}
} ) ;
2022-10-14 09:48:33 +08:00
}
@ override
void dispose ( ) {
DesktopMultiWindow . removeListener ( this ) ;
windowManager . removeListener ( this ) ;
2023-09-07 21:50:03 +08:00
_macOSCheckRestoreTimer ? . cancel ( ) ;
2022-10-14 09:48:33 +08:00
super . dispose ( ) ;
}
2023-08-11 15:53:47 +08:00
void _setMaximized ( bool maximize ) {
stateGlobal . setMaximized ( maximize ) ;
2023-07-27 08:03:18 +08:00
_saveFrameDebounce . call ( _saveFrame ) ;
2023-03-01 14:18:46 +08:00
setState ( ( ) { } ) ;
2023-02-27 12:01:22 +08:00
}
2024-05-08 09:59:05 +08:00
@ override
void onWindowFocus ( ) {
stateGlobal . isFocused . value = true ;
}
@ override
void onWindowBlur ( ) {
stateGlobal . isFocused . value = false ;
}
2023-08-11 15:53:47 +08:00
@ override
void onWindowMinimize ( ) {
2023-08-11 16:08:47 +08:00
stateGlobal . setMinimized ( true ) ;
stateGlobal . setMaximized ( false ) ;
2023-08-11 15:53:47 +08:00
super . onWindowMinimize ( ) ;
}
2022-10-14 09:48:33 +08:00
@ override
void onWindowMaximize ( ) {
2023-08-11 16:08:47 +08:00
stateGlobal . setMinimized ( false ) ;
2023-08-11 15:53:47 +08:00
_setMaximized ( true ) ;
2022-10-14 09:48:33 +08:00
super . onWindowMaximize ( ) ;
}
@ override
void onWindowUnmaximize ( ) {
2023-08-11 16:08:47 +08:00
stateGlobal . setMinimized ( false ) ;
2023-08-11 15:53:47 +08:00
_setMaximized ( false ) ;
2022-10-14 09:48:33 +08:00
super . onWindowUnmaximize ( ) ;
}
2023-07-26 00:40:03 +08:00
_saveFrame ( ) async {
if ( widget . tabType = = DesktopTabType . main ) {
await saveWindowPosition ( WindowType . Main ) ;
} else if ( kWindowType ! = null & & kWindowId ! = null ) {
await saveWindowPosition ( kWindowType ! , windowId: kWindowId ) ;
}
}
@ override
void onWindowMoved ( ) {
_saveFrameDebounce . call ( _saveFrame ) ;
super . onWindowMoved ( ) ;
}
@ override
void onWindowResized ( ) {
_saveFrameDebounce . call ( _saveFrame ) ;
super . onWindowMoved ( ) ;
}
2022-11-06 17:39:19 +08:00
@ override
void onWindowClose ( ) async {
2023-09-07 21:50:03 +08:00
mainWindowClose ( ) async = > await windowManager . hide ( ) ;
notMainWindowClose ( WindowController controller ) async {
2023-11-02 21:41:16 +08:00
if ( widget . tabController . length ! = 0 ) {
2024-04-02 21:41:54 +08:00
debugPrint ( " close not empty multiwindow from taskbar " ) ;
2024-03-24 11:23:06 +08:00
if ( isWindows ) {
2023-10-26 16:05:49 +08:00
await controller . show ( ) ;
await controller . focus ( ) ;
final res = await widget . onClose ? . call ( ) ? ? true ;
2023-11-02 21:41:16 +08:00
if ( ! res ) return ;
2023-10-26 16:05:49 +08:00
}
2023-11-02 21:41:16 +08:00
widget . tabController . clear ( ) ;
2023-10-26 16:05:49 +08:00
}
2023-11-02 21:41:16 +08:00
await controller . hide ( ) ;
await rustDeskWinManager
. call ( WindowType . Main , kWindowEventHide , { " id " : kWindowId ! } ) ;
2023-09-07 21:50:03 +08:00
}
macOSWindowClose (
2023-10-20 09:15:53 +08:00
Future < bool > Function ( ) checkFullscreen ,
Future < void > Function ( ) closeFunc ,
) async {
2023-09-07 21:50:03 +08:00
_macOSCheckRestoreCounter = 0 ;
_macOSCheckRestoreTimer =
Timer . periodic ( Duration ( milliseconds: 30 ) , ( timer ) async {
_macOSCheckRestoreCounter + + ;
if ( ! await checkFullscreen ( ) | | _macOSCheckRestoreCounter > = 30 ) {
_macOSCheckRestoreTimer ? . cancel ( ) ;
_macOSCheckRestoreTimer = null ;
2023-09-07 22:26:32 +08:00
Timer ( Duration ( milliseconds: 700 ) , ( ) async = > await closeFunc ( ) ) ;
2023-09-07 21:50:03 +08:00
}
} ) ;
}
2022-11-06 17:39:19 +08:00
// hide window on close
if ( widget . isMainWindow ) {
2023-02-03 17:08:40 +08:00
if ( rustDeskWinManager . getActiveWindows ( ) . contains ( kMainWindowId ) ) {
await rustDeskWinManager . unregisterActiveWindow ( kMainWindowId ) ;
}
2023-02-22 09:04:43 +08:00
// macOS specific workaround, the window is not hiding when in fullscreen.
2024-03-24 11:23:06 +08:00
if ( isMacOS & & await windowManager . isFullScreen ( ) ) {
2023-10-20 09:15:53 +08:00
await windowManager . setFullScreen ( false ) ;
2023-09-07 21:50:03 +08:00
await macOSWindowClose (
2023-10-20 09:15:53 +08:00
( ) async = > await windowManager . isFullScreen ( ) ,
mainWindowClose ,
) ;
2023-09-07 21:50:03 +08:00
} else {
await mainWindowClose ( ) ;
2023-02-21 21:39:32 +08:00
}
2022-11-06 17:39:19 +08:00
} else {
2023-01-07 12:40:29 +08:00
// it's safe to hide the subwindow
2023-02-21 21:39:32 +08:00
final controller = WindowController . fromWindowId ( kWindowId ! ) ;
2024-03-24 11:23:06 +08:00
if ( isMacOS ) {
2023-10-20 09:17:10 +08:00
// onWindowClose() maybe called multiple times because of loopCloseWindow() in remote_tab_page.dart.
2023-10-20 09:15:53 +08:00
// use ??= to make sure the value is set on first call.
if ( await widget . onClose ? . call ( ) ? ? true ) {
if ( await controller . isFullScreen ( ) ) {
await controller . setFullscreen ( false ) ;
stateGlobal . setFullscreen ( false , procWnd: false ) ;
await macOSWindowClose (
( ) async = > await controller . isFullScreen ( ) ,
( ) async = > await notMainWindowClose ( controller ) ,
) ;
} else {
await notMainWindowClose ( controller ) ;
}
}
2023-09-07 21:50:03 +08:00
} else {
await notMainWindowClose ( controller ) ;
2023-02-21 21:39:32 +08:00
}
2022-11-06 17:39:19 +08:00
}
super . onWindowClose ( ) ;
}
2024-05-06 17:10:02 +08:00
bool showTabDowndown ( ) {
return widget . tabController . state . value . tabs . length > 1 & &
( widget . tabController . tabType = = DesktopTabType . remoteScreen | |
widget . tabController . tabType = = DesktopTabType . fileTransfer | |
widget . tabController . tabType = = DesktopTabType . portForward | |
widget . tabController . tabType = = DesktopTabType . cm ) ;
}
List < String > existingInvisibleTab ( ) {
return widget . invisibleTabKeys
. where ( ( key ) = >
widget . tabController . state . value . tabs . any ( ( tab ) = > tab . key = = key ) )
. toList ( ) ;
}
2022-08-18 11:07:53 +08:00
@ override
Widget build ( BuildContext context ) {
2022-10-20 22:10:26 +08:00
return Row (
mainAxisAlignment: MainAxisAlignment . end ,
2022-08-18 11:07:53 +08:00
children: [
2024-05-06 17:10:02 +08:00
Obx ( ( ) = > Offstage (
offstage:
! ( showTabDowndown ( ) & & existingInvisibleTab ( ) . isNotEmpty ) ,
child: _TabDropDownButton (
controller: widget . tabController ,
labelGetter: widget . labelGetter ,
tabkeys: existingInvisibleTab ( ) ) ,
) ) ,
2022-10-14 19:44:57 +08:00
Offstage ( offstage: widget . tail = = null , child: widget . tail ) ,
2022-08-22 20:18:31 +08:00
Offstage (
2022-11-30 14:05:49 +08:00
offstage: kUseCompatibleUiMode ,
child: Row (
children: [
Offstage (
2024-03-24 11:23:06 +08:00
offstage: ! widget . showMinimize | | isMacOS ,
2022-11-30 14:05:49 +08:00
child: ActionIcon (
2023-03-11 19:14:47 +08:00
message: ' Minimize ' ,
2022-11-30 14:05:49 +08:00
icon: IconFont . min ,
onTap: ( ) {
if ( widget . isMainWindow ) {
windowManager . minimize ( ) ;
} else {
2023-01-23 22:07:50 +08:00
WindowController . fromWindowId ( kWindowId ! ) . minimize ( ) ;
2022-11-30 14:05:49 +08:00
}
} ,
isClose: false ,
) ) ,
Offstage (
2024-03-24 11:23:06 +08:00
offstage: ! widget . showMaximize | | isMacOS ,
2022-11-30 14:05:49 +08:00
child: Obx ( ( ) = > ActionIcon (
2023-09-03 22:18:48 +08:00
message: stateGlobal . isMaximized . isTrue
? ' Restore '
: ' Maximize ' ,
icon: stateGlobal . isMaximized . isTrue
2022-11-30 14:05:49 +08:00
? IconFont . restore
: IconFont . max ,
2024-03-16 17:33:58 +08:00
onTap: bind . isIncomingOnly ( ) & & isInHomePage ( )
? null
: _toggleMaximize ,
2022-11-30 14:05:49 +08:00
isClose: false ,
) ) ) ,
Offstage (
2024-03-24 11:23:06 +08:00
offstage: ! widget . showClose | | isMacOS ,
2022-11-30 14:05:49 +08:00
child: ActionIcon (
2023-03-11 19:14:47 +08:00
message: ' Close ' ,
2022-11-30 14:05:49 +08:00
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 {
2023-01-23 22:07:50 +08:00
await WindowController . fromWindowId ( kWindowId ! )
2022-11-30 14:05:49 +08:00
. close ( ) ;
}
} ) ;
}
} ,
isClose: true ,
) )
] ,
) ,
) ,
2022-08-18 11:07:53 +08:00
] ,
2022-10-20 22:10:26 +08:00
) ;
2022-10-14 19:44:57 +08:00
}
void _toggleMaximize ( ) {
toggleMaximize ( widget . isMainWindow ) . then ( ( maximize ) {
2023-09-03 22:18:48 +08:00
// update state for sub window, wc.unmaximize/maximize() will not invoke onWindowMaximize/Unmaximize
stateGlobal . setMaximized ( maximize ) ;
2022-10-14 19:44:57 +08:00
} ) ;
}
}
void startDragging ( bool isMainWindow ) {
if ( isMainWindow ) {
windowManager . startDragging ( ) ;
} else {
2023-01-23 22:07:50 +08:00
WindowController . fromWindowId ( kWindowId ! ) . startDragging ( ) ;
2022-10-14 19:44:57 +08:00
}
}
/// 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 {
2023-01-23 22:07:50 +08:00
final wc = WindowController . fromWindowId ( kWindowId ! ) ;
2022-10-14 19:44:57 +08:00
if ( await wc . isMaximized ( ) ) {
wc . unmaximize ( ) ;
return false ;
} else {
wc . maximize ( ) ;
return true ;
}
2022-08-18 11:07:53 +08:00
}
2022-09-09 19:29:19 +08:00
}
2022-09-01 12:07:05 +08:00
2022-09-09 19:29:19 +08:00
Future < bool > closeConfirmDialog ( ) async {
2022-10-13 20:19:05 +08:00
var confirm = true ;
2023-05-08 12:34:19 +08:00
final res = await gFFI . dialogManager . show < bool > ( ( setState , close , context ) {
2022-10-13 20:19:05 +08:00
submit ( ) {
2024-05-19 14:07:42 +08:00
String value = bool2option ( kOptionEnableConfirmClosingTabs , confirm ) ;
bind . mainSetLocalOption (
key: kOptionEnableConfirmClosingTabs , value: value ) ;
2022-10-13 20:19:05 +08:00
close ( true ) ;
}
2022-09-09 19:29:19 +08:00
return CustomAlertDialog (
title: Row ( children: [
const Icon ( Icons . warning_amber_sharp ,
color: Colors . redAccent , size: 28 ) ,
const SizedBox ( width: 10 ) ,
Text ( translate ( " Warning " ) ) ,
] ) ,
2022-10-13 20:19:05 +08:00
content: Column (
mainAxisAlignment: MainAxisAlignment . start ,
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text ( translate ( " Disconnect all devices? " ) ) ,
CheckboxListTile (
contentPadding: const EdgeInsets . all ( 0 ) ,
dense: true ,
controlAffinity: ListTileControlAffinity . leading ,
title: Text (
translate ( " Confirm before closing multiple tabs " ) ,
) ,
value: confirm ,
onChanged: ( v ) {
if ( v = = null ) return ;
setState ( ( ) = > confirm = v ) ;
} ,
)
2022-10-24 16:44:43 +08:00
] ) ,
// confirm checkbox
2022-09-09 19:29:19 +08:00
actions: [
2023-01-15 19:46:16 +08:00
dialogButton ( " Cancel " , onPressed: close , isOutline: true ) ,
dialogButton ( " OK " , onPressed: submit ) ,
2022-09-09 19:29:19 +08:00
] ,
onSubmit: submit ,
onCancel: close ,
) ;
} ) ;
return res = = true ;
2022-08-18 11:07:53 +08:00
}
2022-08-18 10:54:09 +08:00
class _ListView extends StatelessWidget {
2022-08-24 20:17:51 +08:00
final DesktopTabController controller ;
2024-05-06 17:10:02 +08:00
final RxList < String > invisibleTabKeys ;
2022-08-29 18:48:12 +08:00
final TabBuilder ? tabBuilder ;
2022-11-03 21:58:25 +08:00
final TabMenuBuilder ? tabMenuBuilder ;
2022-08-29 18:48:12 +08:00
final LabelGetter ? labelGetter ;
2022-10-20 22:10:26 +08:00
final double ? maxLabelWidth ;
2022-10-20 22:56:23 +08:00
final Color ? selectedTabBackgroundColor ;
2023-07-08 17:55:55 +08:00
final Color ? selectedBorderColor ;
2022-10-20 22:56:23 +08:00
final Color ? unSelectedTabBackgroundColor ;
2022-08-29 18:48:12 +08:00
2022-08-30 20:48:03 +08:00
Rx < DesktopTabState > get state = > controller . state ;
2022-08-18 10:54:09 +08:00
2024-05-06 17:10:02 +08:00
_ListView ( {
2022-11-03 21:58:25 +08:00
required this . controller ,
2024-05-06 17:10:02 +08:00
required this . invisibleTabKeys ,
2022-11-03 21:58:25 +08:00
this . tabBuilder ,
this . tabMenuBuilder ,
this . labelGetter ,
this . maxLabelWidth ,
this . selectedTabBackgroundColor ,
this . unSelectedTabBackgroundColor ,
2023-07-08 17:55:55 +08:00
this . selectedBorderColor ,
2022-11-03 21:58:25 +08:00
} ) ;
2022-08-18 10:54:09 +08:00
2022-09-23 11:01:33 +08:00
/// Check whether to show ListView
///
/// Conditions:
/// - hide single item when only has one item (home) on [DesktopTabPage].
bool isHideSingleItem ( ) {
return state . value . tabs . length = = 1 & &
2023-03-01 14:18:46 +08:00
controller . tabType = = DesktopTabType . main | |
controller . tabType = = DesktopTabType . install ;
2022-09-23 11:01:33 +08:00
}
2024-05-06 17:10:02 +08:00
onVisibilityChanged ( VisibilityInfo info ) {
final key = ( info . key as ValueKey ) . value ;
if ( info . visibleFraction < 0.75 ) {
if ( ! invisibleTabKeys . contains ( key ) ) {
invisibleTabKeys . add ( key ) ;
}
invisibleTabKeys . removeWhere ( ( key ) = >
controller . state . value . tabs . where ( ( e ) = > e . key = = key ) . isEmpty ) ;
} else {
invisibleTabKeys . remove ( key ) ;
}
}
2022-08-18 10:54:09 +08:00
@ override
Widget build ( BuildContext context ) {
2022-08-24 20:12:04 +08:00
return Obx ( ( ) = > ListView (
controller: state . value . scrollController ,
scrollDirection: Axis . horizontal ,
shrinkWrap: true ,
2022-09-04 11:03:16 +08:00
physics: const BouncingScrollPhysics ( ) ,
2022-09-23 11:01:33 +08:00
children: isHideSingleItem ( )
? List . empty ( )
: state . value . tabs . asMap ( ) . entries . map ( ( e ) {
final index = e . key ;
final tab = e . value ;
2024-05-06 17:10:02 +08:00
final label = labelGetter = = null
? Rx < String > ( tab . label )
: labelGetter ! ( tab . label ) ;
return VisibilityDetector (
2022-10-26 22:50:36 +08:00
key: ValueKey ( tab . key ) ,
2024-05-06 17:10:02 +08:00
onVisibilityChanged: onVisibilityChanged ,
child: _Tab (
key: ValueKey ( tab . key ) ,
index: index ,
tabInfoKey: tab . key ,
label: label ,
tabType: controller . tabType ,
selectedIcon: tab . selectedIcon ,
unselectedIcon: tab . unselectedIcon ,
closable: tab . closable ,
selected: state . value . selected ,
onClose: ( ) {
if ( tab . onTabCloseButton ! = null ) {
tab . onTabCloseButton ! ( ) ;
} else {
controller . remove ( index ) ;
}
} ,
onTap: ( ) {
controller . jumpTo ( index ) ;
tab . onTap ? . call ( ) ;
} ,
tabBuilder: tabBuilder ,
tabMenuBuilder: tabMenuBuilder ,
maxLabelWidth: maxLabelWidth ,
selectedTabBackgroundColor: selectedTabBackgroundColor ? ?
MyTheme . tabbar ( context ) . selectedTabBackgroundColor ,
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor ,
selectedBorderColor: selectedBorderColor ,
) ,
2022-09-23 11:01:33 +08:00
) ;
} ) . toList ( ) ) ) ;
2022-08-18 10:54:09 +08:00
}
}
2022-08-31 19:23:32 +08:00
class _Tab extends StatefulWidget {
2022-09-04 11:03:16 +08:00
final int index ;
2022-10-26 22:50:36 +08:00
final String tabInfoKey ;
2022-09-04 11:03:16 +08:00
final Rx < String > label ;
2024-04-10 14:04:12 +08:00
final DesktopTabType tabType ;
2022-09-04 11:03:16 +08:00
final IconData ? selectedIcon ;
final IconData ? unselectedIcon ;
final bool closable ;
final int selected ;
final Function ( ) onClose ;
2022-10-26 22:50:36 +08:00
final Function ( ) onTap ;
2022-10-20 22:56:23 +08:00
final TabBuilder ? tabBuilder ;
2022-11-03 21:58:25 +08:00
final TabMenuBuilder ? tabMenuBuilder ;
2022-10-20 22:10:26 +08:00
final double ? maxLabelWidth ;
2022-10-20 22:56:23 +08:00
final Color ? selectedTabBackgroundColor ;
final Color ? unSelectedTabBackgroundColor ;
2023-07-08 17:55:55 +08:00
final Color ? selectedBorderColor ;
2022-08-06 17:08:48 +08:00
2022-09-04 11:03:16 +08:00
const _Tab ( {
Key ? key ,
required this . index ,
2022-10-26 22:50:36 +08:00
required this . tabInfoKey ,
2022-09-04 11:03:16 +08:00
required this . label ,
2024-04-10 14:04:12 +08:00
required this . tabType ,
2022-09-04 11:03:16 +08:00
this . selectedIcon ,
this . unselectedIcon ,
this . tabBuilder ,
2022-11-03 21:58:25 +08:00
this . tabMenuBuilder ,
2022-09-04 11:03:16 +08:00
required this . closable ,
required this . selected ,
required this . onClose ,
2022-10-26 22:50:36 +08:00
required this . onTap ,
2022-10-20 22:10:26 +08:00
this . maxLabelWidth ,
2022-10-20 22:56:23 +08:00
this . selectedTabBackgroundColor ,
this . unSelectedTabBackgroundColor ,
2023-07-08 17:55:55 +08:00
this . selectedBorderColor ,
2022-09-04 11:03:16 +08:00
} ) : super ( key: key ) ;
2022-08-06 17:08:48 +08:00
2022-08-31 19:23:32 +08:00
@ override
State < _Tab > createState ( ) = > _TabState ( ) ;
}
class _TabState extends State < _Tab > with RestorationMixin {
final RestorableBool restoreHover = RestorableBool ( false ) ;
2022-08-29 18:48:12 +08:00
Widget _buildTabContent ( ) {
2022-08-31 19:23:32 +08:00
bool showIcon =
widget . selectedIcon ! = null & & widget . unselectedIcon ! = null ;
bool isSelected = widget . index = = widget . selected ;
2022-08-29 18:48:12 +08:00
final icon = Offstage (
offstage: ! showIcon ,
child: Icon (
2022-08-31 19:23:32 +08:00
isSelected ? widget . selectedIcon : widget . unselectedIcon ,
2022-08-29 18:48:12 +08:00
size: _kIconSize ,
color: isSelected
2022-09-04 11:03:16 +08:00
? MyTheme . tabbar ( context ) . selectedTabIconColor
: MyTheme . tabbar ( context ) . unSelectedTabIconColor ,
2022-08-29 18:48:12 +08:00
) . paddingOnly ( right: 5 ) ) ;
final labelWidget = Obx ( ( ) {
2022-10-20 22:10:26 +08:00
return ConstrainedBox (
constraints: BoxConstraints ( maxWidth: widget . maxLabelWidth ? ? 200 ) ,
2023-09-26 15:11:31 +08:00
child: Tooltip (
2024-04-10 14:04:12 +08:00
message: widget . tabType = = DesktopTabType . main
? ' '
: translate ( widget . label . value ) ,
2023-09-26 15:11:31 +08:00
child: Text (
translate ( widget . label . value ) ,
textAlign: TextAlign . center ,
style: TextStyle (
color: isSelected
? MyTheme . tabbar ( context ) . selectedTextColor
: MyTheme . tabbar ( context ) . unSelectedTextColor ) ,
overflow: TextOverflow . ellipsis ,
) ,
2022-10-20 22:10:26 +08:00
) ) ;
2022-08-29 18:48:12 +08:00
} ) ;
2022-11-03 21:58:25 +08:00
Widget getWidgetWithBuilder ( ) {
if ( widget . tabBuilder = = null ) {
return Row (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
icon ,
labelWidget ,
] ,
) ;
} else {
return widget . tabBuilder ! (
widget . tabInfoKey ,
2022-08-29 18:48:12 +08:00
icon ,
labelWidget ,
2022-11-03 21:58:25 +08:00
TabThemeConf ( iconSize: _kIconSize ) ,
) ;
}
2022-08-29 18:48:12 +08:00
}
2022-11-03 21:58:25 +08:00
return Listener (
onPointerDown: ( e ) {
if ( e . kind ! = ui . PointerDeviceKind . mouse ) {
return ;
}
if ( e . buttons = = 2 ) {
if ( widget . tabMenuBuilder ! = null ) {
showRightMenu (
( cacel ) {
return widget . tabMenuBuilder ! ( widget . tabInfoKey ) ;
} ,
target: e . position ,
) ;
}
}
} ,
child: getWidgetWithBuilder ( ) ,
) ;
2022-08-29 18:48:12 +08:00
}
2022-08-06 17:08:48 +08:00
@ override
Widget build ( BuildContext context ) {
2022-08-31 19:23:32 +08:00
bool isSelected = widget . index = = widget . selected ;
bool showDivider =
widget . index ! = widget . selected - 1 & & widget . index ! = widget . selected ;
RxBool hover = restoreHover . value . obs ;
2022-08-20 19:57:16 +08:00
return Ink (
child: InkWell (
2022-08-31 19:23:32 +08:00
onHover: ( value ) {
hover . value = value ;
restoreHover . value = value ;
} ,
2022-10-26 22:50:36 +08:00
onTap: ( ) = > widget . onTap ( ) ,
2022-10-20 22:56:23 +08:00
child: Container (
2023-07-08 17:55:55 +08:00
decoration: isSelected & & widget . selectedBorderColor ! = null
? BoxDecoration (
border: Border (
bottom: BorderSide (
color: widget . selectedBorderColor ! ,
width: 1 ,
) ,
) ,
)
: null ,
child: Container (
color: isSelected
? widget . selectedTabBackgroundColor
: widget . unSelectedTabBackgroundColor ,
child: Row (
children: [
SizedBox (
height: _kTabBarHeight ,
child: Row (
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
_buildTabContent ( ) ,
Obx ( ( ( ) = > _CloseButton (
visible: hover . value & & widget . closable ,
tabSelected: isSelected ,
onClose: ( ) = > widget . onClose ( ) ,
) ) )
] ) ) . paddingOnly ( left: 10 , right: 5 ) ,
Offstage (
offstage: ! showDivider ,
child: VerticalDivider (
width: 1 ,
indent: _kDividerIndent ,
endIndent: _kDividerIndent ,
color: MyTheme . tabbar ( context ) . dividerColor ,
) ,
)
] ,
) ,
) ) ,
2022-08-20 19:57:16 +08:00
) ,
2022-08-06 17:08:48 +08:00
) ;
}
2022-08-31 19:23:32 +08:00
@ override
String ? get restorationId = > " _Tab ${ widget . label . value } " ;
@ override
void restoreState ( RestorationBucket ? oldBucket , bool initialRestore ) {
registerForRestoration ( restoreHover , ' restoreHover ' ) ;
}
2022-08-06 17:08:48 +08:00
}
class _CloseButton extends StatelessWidget {
2023-01-09 15:30:39 +08:00
final bool visible ;
2022-08-09 22:35:29 +08:00
final bool tabSelected ;
2022-08-06 17:08:48 +08:00
final Function onClose ;
2022-09-04 11:03:16 +08:00
const _CloseButton ( {
2022-08-06 17:08:48 +08:00
Key ? key ,
2023-01-09 15:30:39 +08:00
required this . visible ,
2022-08-09 22:35:29 +08:00
required this . tabSelected ,
2022-08-06 17:08:48 +08:00
required this . onClose ,
} ) : super ( key: key ) ;
@ override
Widget build ( BuildContext context ) {
2022-08-11 16:03:04 +08:00
return SizedBox (
width: _kIconSize ,
child: Offstage (
2023-01-09 15:30:39 +08:00
offstage: ! visible ,
2022-08-11 16:03:04 +08:00
child: InkWell (
2023-01-29 23:30:49 +08:00
hoverColor: MyTheme . tabbar ( context ) . closeHoverColor ,
2023-02-05 17:29:54 +08:00
customBorder: const CircleBorder ( ) ,
2022-08-11 16:03:04 +08:00
onTap: ( ) = > onClose ( ) ,
child: Icon (
Icons . close ,
size: _kIconSize ,
color: tabSelected
2022-09-04 11:03:16 +08:00
? MyTheme . tabbar ( context ) . selectedIconColor
: MyTheme . tabbar ( context ) . unSelectedIconColor ,
2022-08-11 16:03:04 +08:00
) ,
) ,
2023-01-29 23:30:49 +08:00
) ) . paddingOnly ( left: 10 ) ;
2022-08-06 17:08:48 +08:00
}
}
2022-10-24 16:44:43 +08:00
class ActionIcon extends StatefulWidget {
2022-10-20 22:22:02 +08:00
final String ? message ;
2022-08-20 19:57:16 +08:00
final IconData icon ;
2024-03-16 17:33:58 +08:00
final GestureTapCallback ? onTap ;
2024-05-06 17:10:02 +08:00
final GestureTapDownCallback ? onTapDown ;
2022-09-04 11:03:16 +08:00
final bool isClose ;
2022-10-18 22:56:36 +08:00
final double iconSize ;
final double boxSize ;
2022-10-24 16:44:43 +08:00
2022-10-14 22:50:13 +08:00
const ActionIcon (
{ Key ? key ,
2022-10-20 22:22:02 +08:00
this . message ,
2022-10-14 22:50:13 +08:00
required this . icon ,
2024-03-16 17:33:58 +08:00
this . onTap ,
2024-05-06 17:10:02 +08:00
this . onTapDown ,
2022-10-18 22:56:36 +08:00
this . isClose = false ,
this . iconSize = _kActionIconSize ,
this . boxSize = _kTabBarHeight - 1 } )
2022-10-14 22:50:13 +08:00
: super ( key: key ) ;
2022-08-20 19:57:16 +08:00
2022-10-24 16:44:43 +08:00
@ override
State < ActionIcon > createState ( ) = > _ActionIconState ( ) ;
}
class _ActionIconState extends State < ActionIcon > {
var hover = false . obs ;
@ override
void initState ( ) {
super . initState ( ) ;
hover . value = false ;
}
2022-08-20 19:57:16 +08:00
@ override
Widget build ( BuildContext context ) {
2022-10-24 16:44:43 +08:00
return Tooltip (
message: widget . message ! = null ? translate ( widget . message ! ) : " " ,
waitDuration: const Duration ( seconds: 1 ) ,
2024-03-16 17:33:58 +08:00
child: InkWell (
hoverColor: widget . isClose
? const Color . fromARGB ( 255 , 196 , 43 , 28 )
: MyTheme . tabbar ( context ) . hoverColor ,
onHover: ( value ) = > hover . value = value ,
onTap: widget . onTap ,
2024-05-06 17:10:02 +08:00
onTapDown: widget . onTapDown ,
2024-03-16 17:33:58 +08:00
child: SizedBox (
height: widget . boxSize ,
width: widget . boxSize ,
child: widget . onTap = = null
? Icon (
widget . icon ,
color: Colors . grey ,
size: widget . iconSize ,
)
: Obx (
( ) = > Icon (
widget . icon ,
color: hover . value & & widget . isClose
? Colors . white
: MyTheme . tabbar ( context ) . unSelectedIconColor ,
size: widget . iconSize ,
) ,
) ,
2022-10-24 16:44:43 +08:00
) ,
) ,
) ;
2022-08-20 19:57:16 +08:00
}
}
2022-08-24 21:09:18 +08:00
class AddButton extends StatelessWidget {
2022-09-04 11:03:16 +08:00
const AddButton ( {
2022-08-24 21:09:18 +08:00
Key ? key ,
} ) : super ( key: key ) ;
@ override
Widget build ( BuildContext context ) {
return ActionIcon (
2023-03-11 19:14:47 +08:00
message: ' New Connection ' ,
2022-08-24 21:09:18 +08:00
icon: IconFont . add ,
2022-12-01 13:52:12 +08:00
onTap: ( ) = > rustDeskWinManager . call (
WindowType . Main , kWindowMainWindowOnTop , " " ) ,
2022-09-04 11:03:16 +08:00
isClose: false ) ;
2022-08-24 21:09:18 +08:00
}
}
2024-05-06 17:10:02 +08:00
class _TabDropDownButton extends StatefulWidget {
final DesktopTabController controller ;
final List < String > tabkeys ;
final LabelGetter ? labelGetter ;
const _TabDropDownButton (
{ required this . controller , required this . tabkeys , this . labelGetter } ) ;
@ override
State < _TabDropDownButton > createState ( ) = > _TabDropDownButtonState ( ) ;
}
class _TabDropDownButtonState extends State < _TabDropDownButton > {
var position = RelativeRect . fromLTRB ( 0 , 0 , 0 , 0 ) ;
@ override
Widget build ( BuildContext context ) {
List < String > sortedKeys = widget . controller . state . value . tabs
. where ( ( e ) = > widget . tabkeys . contains ( e . key ) )
. map ( ( e ) = > e . key )
. toList ( ) ;
return ActionIcon (
onTapDown: ( details ) {
final x = details . globalPosition . dx ;
final y = details . globalPosition . dy ;
position = RelativeRect . fromLTRB ( x , y , x , y ) ;
} ,
icon: Icons . arrow_drop_down ,
onTap: ( ) {
showMenu (
context: context ,
position: position ,
items: sortedKeys . map ( ( e ) {
var label = e ;
final tabInfo = widget . controller . state . value . tabs
. firstWhereOrNull ( ( element ) = > element . key = = e ) ;
if ( tabInfo ! = null ) {
label = tabInfo . label ;
}
if ( widget . labelGetter ! = null ) {
label = widget . labelGetter ! ( e ) . value ;
}
var index = widget . controller . state . value . tabs
. indexWhere ( ( t ) = > t . key = = e ) ;
label = ' ${ index + 1 } . $ label ' ;
final menuHover = false . obs ;
final btnHover = false . obs ;
return PopupMenuItem < String > (
value: e ,
height: 32 ,
onTap: ( ) {
widget . controller . jumpToByKey ( e ) ;
if ( Navigator . of ( context ) . canPop ( ) ) {
Navigator . of ( context ) . pop ( ) ;
}
} ,
child: MouseRegion (
onHover: ( event ) = > setState ( ( ) = > menuHover . value = true ) ,
onExit: ( event ) = > setState ( ( ) = > menuHover . value = false ) ,
child: Row (
children: [
Expanded (
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 ) ) ) ,
) ,
)
] ,
) ,
) ,
) ;
} ) . toList ( ) ,
) ;
} ,
) ;
}
}
2022-09-04 11:03:16 +08:00
class TabbarTheme extends ThemeExtension < TabbarTheme > {
final Color ? selectedTabIconColor ;
final Color ? unSelectedTabIconColor ;
final Color ? selectedTextColor ;
final Color ? unSelectedTextColor ;
final Color ? selectedIconColor ;
final Color ? unSelectedIconColor ;
final Color ? dividerColor ;
final Color ? hoverColor ;
2023-01-29 23:30:49 +08:00
final Color ? closeHoverColor ;
final Color ? selectedTabBackgroundColor ;
2022-09-04 11:03:16 +08:00
const TabbarTheme (
{ required this . selectedTabIconColor ,
required this . unSelectedTabIconColor ,
required this . selectedTextColor ,
required this . unSelectedTextColor ,
required this . selectedIconColor ,
required this . unSelectedIconColor ,
required this . dividerColor ,
2023-01-29 23:30:49 +08:00
required this . hoverColor ,
required this . closeHoverColor ,
required this . selectedTabBackgroundColor } ) ;
2022-09-04 11:03:16 +08:00
static const light = TabbarTheme (
selectedTabIconColor: MyTheme . accent ,
unSelectedTabIconColor: Color . fromARGB ( 255 , 162 , 203 , 241 ) ,
2023-01-29 23:30:49 +08:00
selectedTextColor: Colors . black ,
unSelectedTextColor: Color . fromARGB ( 255 , 112 , 112 , 112 ) ,
2022-09-04 11:03:16 +08:00
selectedIconColor: Color . fromARGB ( 255 , 26 , 26 , 26 ) ,
unSelectedIconColor: Color . fromARGB ( 255 , 96 , 96 , 96 ) ,
dividerColor: Color . fromARGB ( 255 , 238 , 238 , 238 ) ,
2023-07-09 14:26:46 +08:00
hoverColor: Colors . white54 ,
closeHoverColor: Colors . white ,
selectedTabBackgroundColor: Colors . white54 ) ;
2022-09-04 11:03:16 +08:00
static const dark = TabbarTheme (
selectedTabIconColor: MyTheme . accent ,
unSelectedTabIconColor: Color . fromARGB ( 255 , 30 , 65 , 98 ) ,
2023-07-09 14:26:46 +08:00
selectedTextColor: Colors . white ,
2023-01-29 23:30:49 +08:00
unSelectedTextColor: Color . fromARGB ( 255 , 192 , 192 , 192 ) ,
selectedIconColor: Color . fromARGB ( 255 , 192 , 192 , 192 ) ,
2022-09-04 11:03:16 +08:00
unSelectedIconColor: Color . fromARGB ( 255 , 255 , 255 , 255 ) ,
dividerColor: Color . fromARGB ( 255 , 64 , 64 , 64 ) ,
2023-01-29 23:30:49 +08:00
hoverColor: Colors . black26 ,
closeHoverColor: Colors . black ,
selectedTabBackgroundColor: Colors . black26 ) ;
2022-09-04 11:03:16 +08:00
@ override
ThemeExtension < TabbarTheme > copyWith ( {
Color ? selectedTabIconColor ,
Color ? unSelectedTabIconColor ,
Color ? selectedTextColor ,
Color ? unSelectedTextColor ,
Color ? selectedIconColor ,
Color ? unSelectedIconColor ,
Color ? dividerColor ,
Color ? hoverColor ,
2023-01-29 23:30:49 +08:00
Color ? closeHoverColor ,
Color ? selectedTabBackgroundColor ,
2022-09-04 11:03:16 +08:00
} ) {
return TabbarTheme (
selectedTabIconColor: selectedTabIconColor ? ? this . selectedTabIconColor ,
unSelectedTabIconColor:
unSelectedTabIconColor ? ? this . unSelectedTabIconColor ,
selectedTextColor: selectedTextColor ? ? this . selectedTextColor ,
unSelectedTextColor: unSelectedTextColor ? ? this . unSelectedTextColor ,
selectedIconColor: selectedIconColor ? ? this . selectedIconColor ,
unSelectedIconColor: unSelectedIconColor ? ? this . unSelectedIconColor ,
dividerColor: dividerColor ? ? this . dividerColor ,
hoverColor: hoverColor ? ? this . hoverColor ,
2023-01-29 23:30:49 +08:00
closeHoverColor: closeHoverColor ? ? this . closeHoverColor ,
2023-02-08 09:11:53 +08:00
selectedTabBackgroundColor:
selectedTabBackgroundColor ? ? this . selectedTabBackgroundColor ,
2022-09-04 11:03:16 +08:00
) ;
}
@ override
ThemeExtension < TabbarTheme > lerp (
ThemeExtension < TabbarTheme > ? other , double t ) {
if ( other is ! TabbarTheme ) {
return this ;
}
return TabbarTheme (
selectedTabIconColor:
Color . lerp ( selectedTabIconColor , other . selectedTabIconColor , t ) ,
unSelectedTabIconColor:
Color . lerp ( unSelectedTabIconColor , other . unSelectedTabIconColor , t ) ,
selectedTextColor:
Color . lerp ( selectedTextColor , other . selectedTextColor , t ) ,
unSelectedTextColor:
Color . lerp ( unSelectedTextColor , other . unSelectedTextColor , t ) ,
selectedIconColor:
Color . lerp ( selectedIconColor , other . selectedIconColor , t ) ,
unSelectedIconColor:
Color . lerp ( unSelectedIconColor , other . unSelectedIconColor , t ) ,
dividerColor: Color . lerp ( dividerColor , other . dividerColor , t ) ,
hoverColor: Color . lerp ( hoverColor , other . hoverColor , t ) ,
2023-01-29 23:30:49 +08:00
closeHoverColor: Color . lerp ( closeHoverColor , other . closeHoverColor , t ) ,
2023-02-08 09:11:53 +08:00
selectedTabBackgroundColor: Color . lerp (
selectedTabBackgroundColor , other . selectedTabBackgroundColor , t ) ,
2022-09-04 11:03:16 +08:00
) ;
}
static color ( BuildContext context ) {
return Theme . of ( context ) . extension < ColorThemeExtension > ( ) ! ;
}
2022-08-09 22:35:29 +08:00
}