| 
									
										
										
										
											2022-08-30 20:48:03 +08:00
										 |  |  | import 'dart:io'; | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 11:07:53 +08:00
										 |  |  | import 'package:desktop_multi_window/desktop_multi_window.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09: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'; | 
					
						
							| 
									
										
										
										
											2022-08-18 11:07:53 +08:00
										 |  |  | import 'package:flutter_hbb/main.dart'; | 
					
						
							| 
									
										
										
										
											2022-11-12 22:33:10 +08:00
										 |  |  | import 'package:flutter_hbb/common/shared_state.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 23:10:26 +09: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'; | 
					
						
							| 
									
										
										
										
											2022-09-27 18:34:05 +08:00
										 |  |  | import 'package:flutter_svg/flutter_svg.dart'; | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  | import 'package:bot_toast/bot_toast.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 { | 
					
						
							| 
									
										
										
										
											2022-08-24 20:12:04 +08:00
										 |  |  |   final String key; | 
					
						
							|  |  |  |   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 23:50:36 +09: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 23:50:36 +09: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, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 23:50:36 +09: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, | 
					
						
							|  |  |  |     verticalOffset: 0, | 
					
						
							|  |  |  |     horizontalOffset: 0, | 
					
						
							|  |  |  |     duration: Duration(seconds: 4), | 
					
						
							|  |  |  |     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; | 
					
						
							|  |  |  |   Function(int, 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 21:39:28 +09: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 { | 
					
						
							| 
									
										
										
										
											2022-08-24 20:12:04 +08:00
										 |  |  |       jumpTo(toIndex); | 
					
						
							| 
									
										
										
										
											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
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 20:12:04 +08:00
										 |  |  |   void jumpTo(int index) { | 
					
						
							| 
									
										
										
										
											2022-09-01 21:18:53 +08:00
										 |  |  |     if (!isDesktop || index < 0) return; | 
					
						
							| 
									
										
										
										
											2022-08-24 20:12:04 +08:00
										 |  |  |     state.update((val) { | 
					
						
							|  |  |  |       val!.selected = index; | 
					
						
							| 
									
										
										
										
											2022-10-26 21:02:20 +09:00
										 |  |  |       Future.delayed(Duration(milliseconds: 100), (() { | 
					
						
							| 
									
										
										
										
											2022-08-30 14:43:57 +08:00
										 |  |  |         if (val.pageController.hasClients) { | 
					
						
							|  |  |  |           val.pageController.jumpToPage(index); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-10-26 21:02:20 +09:00
										 |  |  |         val.scrollController.itemCount = val.tabs.length; | 
					
						
							| 
									
										
										
										
											2022-08-30 14:43:57 +08:00
										 |  |  |         if (val.scrollController.hasClients && | 
					
						
							| 
									
										
										
										
											2022-09-01 21:18:53 +08:00
										 |  |  |             val.scrollController.itemCount > index) { | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09:00
										 |  |  |           val.scrollController | 
					
						
							|  |  |  |               .scrollToItem(index, center: false, animate: true); | 
					
						
							| 
									
										
										
										
											2022-08-30 14:43:57 +08:00
										 |  |  |         } | 
					
						
							|  |  |  |       })); | 
					
						
							| 
									
										
										
										
											2022-08-24 20:12:04 +08:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-09-01 21:18:53 +08:00
										 |  |  |     if (state.value.tabs.length > index) { | 
					
						
							| 
									
										
										
										
											2022-10-27 10:56:14 +08:00
										 |  |  |       final key = state.value.tabs[index].key; | 
					
						
							|  |  |  |       onSelected?.call(index, key); | 
					
						
							| 
									
										
										
										
											2022-09-01 21:18:53 +08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-08-11 18:08:35 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-08-24 20:56:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 19:26:55 +08:00
										 |  |  |   void jumpBy(String key) { | 
					
						
							|  |  |  |     if (!isDesktop) return; | 
					
						
							|  |  |  |     final index = state.value.tabs.indexWhere((tab) => tab.key == key); | 
					
						
							|  |  |  |     jumpTo(index); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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(); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											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 23:56:23 +09: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 23:56:23 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 23:10:26 +09:00
										 |  |  |   final double? maxLabelWidth; | 
					
						
							| 
									
										
										
										
											2022-10-20 23:56:23 +09:00
										 |  |  |   final Color? selectedTabBackgroundColor; | 
					
						
							|  |  |  |   final Color? unSelectedTabBackgroundColor; | 
					
						
							| 
									
										
										
										
											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-17 19:37:00 +09:00
										 |  |  |   final isMaximized = false.obs; | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09: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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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, | 
					
						
							|  |  |  |     this.showTitle = true, | 
					
						
							|  |  |  |     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 23:10:26 +09:00
										 |  |  |     this.maxLabelWidth, | 
					
						
							| 
									
										
										
										
											2022-10-20 23:56:23 +09:00
										 |  |  |     this.selectedTabBackgroundColor, | 
					
						
							|  |  |  |     this.unSelectedTabBackgroundColor, | 
					
						
							| 
									
										
										
										
											2022-09-01 21:18:53 +08:00
										 |  |  |   }) : super(key: key) { | 
					
						
							|  |  |  |     tabType = controller.tabType; | 
					
						
							|  |  |  |     isMainWindow = | 
					
						
							|  |  |  |         tabType == DesktopTabType.main || tabType == DesktopTabType.cm; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-08-24 20:12:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-12 22:33:10 +08:00
										 |  |  |   static RxString labelGetterAlias(String peerId) { | 
					
						
							|  |  |  |     final opt = 'alias'; | 
					
						
							|  |  |  |     PeerStringOption.init(peerId, opt, () { | 
					
						
							|  |  |  |       final alias = bind.mainGetPeerOptionSync(id: peerId, key: opt); | 
					
						
							|  |  |  |       return alias.isEmpty ? peerId : alias; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     return PeerStringOption.find(peerId, opt); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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, | 
					
						
							|  |  |  |                   thickness: 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; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     var block = false.obs; | 
					
						
							|  |  |  |     return Obx(() => MouseRegion( | 
					
						
							|  |  |  |           onEnter: (_) async { | 
					
						
							| 
									
										
										
										
											2022-10-17 14:35:44 +08:00
										 |  |  |             var access_mode = await bind.mainGetOption(key: 'access-mode'); | 
					
						
							|  |  |  |             var option = option2bool( | 
					
						
							| 
									
										
										
										
											2022-08-30 16:50:25 +08:00
										 |  |  |                 'allow-remote-config-modification', | 
					
						
							|  |  |  |                 await bind.mainGetOption( | 
					
						
							| 
									
										
										
										
											2022-10-17 14:35:44 +08:00
										 |  |  |                     key: 'allow-remote-config-modification')); | 
					
						
							|  |  |  |             if (access_mode == 'view' || (access_mode.isEmpty && !option)) { | 
					
						
							| 
									
										
										
										
											2022-08-30 16:50:25 +08:00
										 |  |  |               var time0 = DateTime.now().millisecondsSinceEpoch; | 
					
						
							|  |  |  |               await bind.mainCheckMouseTime(); | 
					
						
							|  |  |  |               Timer(const Duration(milliseconds: 120), () async { | 
					
						
							|  |  |  |                 var d = time0 - await bind.mainGetMouseTime(); | 
					
						
							|  |  |  |                 if (d < 120) { | 
					
						
							|  |  |  |                   block.value = true; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               }); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           onExit: (_) => block.value = false, | 
					
						
							|  |  |  |           child: Stack( | 
					
						
							|  |  |  |             children: [ | 
					
						
							|  |  |  |               child, | 
					
						
							|  |  |  |               Offstage( | 
					
						
							|  |  |  |                   offstage: !block.value, | 
					
						
							|  |  |  |                   child: Container( | 
					
						
							|  |  |  |                     color: Colors.black.withOpacity(0.5), | 
					
						
							|  |  |  |                   )), | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         )); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 20:12:04 +08:00
										 |  |  |   Widget _buildPageView() { | 
					
						
							| 
									
										
										
										
											2022-08-30 16:50:25 +08:00
										 |  |  |     return _buildBlock( | 
					
						
							|  |  |  |         child: Obx(() => PageView( | 
					
						
							|  |  |  |             controller: state.value.pageController, | 
					
						
							|  |  |  |             children: state.value.tabs | 
					
						
							|  |  |  |                 .map((tab) => tab.page) | 
					
						
							|  |  |  |                 .toList(growable: false)))); | 
					
						
							| 
									
										
										
										
											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 && | 
					
						
							|  |  |  |         controller.tabType == DesktopTabType.main; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 20:12:04 +08:00
										 |  |  |   Widget _buildBar() { | 
					
						
							|  |  |  |     return Row( | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09:00
										 |  |  |       mainAxisAlignment: MainAxisAlignment.spaceBetween, | 
					
						
							| 
									
										
										
										
											2022-08-24 20:12:04 +08:00
										 |  |  |       children: [ | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09:00
										 |  |  |         Expanded( | 
					
						
							|  |  |  |             child: GestureDetector( | 
					
						
							|  |  |  |                 // custom double tap handler
 | 
					
						
							|  |  |  |                 onTap: showMaximize | 
					
						
							|  |  |  |                     ? () { | 
					
						
							|  |  |  |                         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 23:10:26 +09:00
										 |  |  |                           // onDoubleTap
 | 
					
						
							|  |  |  |                           toggleMaximize(isMainWindow) | 
					
						
							|  |  |  |                               .then((value) => isMaximized.value = value); | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                       } | 
					
						
							| 
									
										
										
										
											2022-10-17 19:37:00 +09:00
										 |  |  |                     : null, | 
					
						
							| 
									
										
										
										
											2022-10-14 20:44:57 +09:00
										 |  |  |                 onPanStart: (_) => startDragging(isMainWindow), | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09:00
										 |  |  |                 child: Row( | 
					
						
							|  |  |  |                   children: [ | 
					
						
							|  |  |  |                     Offstage( | 
					
						
							|  |  |  |                         offstage: !Platform.isMacOS, | 
					
						
							|  |  |  |                         child: const SizedBox( | 
					
						
							|  |  |  |                           width: 78, | 
					
						
							|  |  |  |                         )), | 
					
						
							| 
									
										
										
										
											2022-11-30 13:56:02 +08:00
										 |  |  |                     Offstage( | 
					
						
							|  |  |  |                       offstage: kUseCompatibleUiMode, | 
					
						
							|  |  |  |                       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, | 
					
						
							|  |  |  |                       ), | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09: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( | 
					
						
							|  |  |  |                                 controller: controller, | 
					
						
							|  |  |  |                                 tabBuilder: tabBuilder, | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |                                 tabMenuBuilder: tabMenuBuilder, | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09:00
										 |  |  |                                 labelGetter: labelGetter, | 
					
						
							| 
									
										
										
										
											2022-10-20 23:56:23 +09:00
										 |  |  |                                 maxLabelWidth: maxLabelWidth, | 
					
						
							|  |  |  |                                 selectedTabBackgroundColor: | 
					
						
							|  |  |  |                                     selectedTabBackgroundColor, | 
					
						
							|  |  |  |                                 unSelectedTabBackgroundColor: | 
					
						
							|  |  |  |                                     unSelectedTabBackgroundColor))), | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09: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, | 
					
						
							|  |  |  |           tail: tail, | 
					
						
							|  |  |  |           isMaximized: isMaximized, | 
					
						
							|  |  |  |           showMinimize: showMinimize, | 
					
						
							|  |  |  |           showMaximize: showMaximize, | 
					
						
							|  |  |  |           showClose: showClose, | 
					
						
							|  |  |  |           onClose: onWindowCloseButton, | 
					
						
							| 
									
										
										
										
											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 10:48:33 +09:00
										 |  |  | class WindowActionPanel extends StatefulWidget { | 
					
						
							| 
									
										
										
										
											2022-10-14 20:44:57 +09:00
										 |  |  |   final bool isMainWindow; | 
					
						
							| 
									
										
										
										
											2022-09-01 12:07:05 +08:00
										 |  |  |   final DesktopTabType tabType; | 
					
						
							|  |  |  |   final Rx<DesktopTabState> state; | 
					
						
							| 
									
										
										
										
											2022-10-17 19:37:00 +09:00
										 |  |  |   final RxBool isMaximized; | 
					
						
							| 
									
										
										
										
											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 20:44:57 +09: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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 11:07:53 +08:00
										 |  |  |   const WindowActionPanel( | 
					
						
							| 
									
										
										
										
											2022-08-22 20:18:31 +08:00
										 |  |  |       {Key? key, | 
					
						
							| 
									
										
										
										
											2022-10-14 20:44:57 +09:00
										 |  |  |       required this.isMainWindow, | 
					
						
							| 
									
										
										
										
											2022-09-01 12:07:05 +08:00
										 |  |  |       required this.tabType, | 
					
						
							|  |  |  |       required this.state, | 
					
						
							| 
									
										
										
										
											2022-10-17 19:37:00 +09:00
										 |  |  |       required this.isMaximized, | 
					
						
							| 
									
										
										
										
											2022-10-14 20:44:57 +09: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, | 
					
						
							|  |  |  |       this.onClose}) | 
					
						
							| 
									
										
										
										
											2022-08-18 11:07:53 +08:00
										 |  |  |       : super(key: key); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-14 10:48:33 +09:00
										 |  |  |   @override | 
					
						
							|  |  |  |   State<StatefulWidget> createState() { | 
					
						
							|  |  |  |     return WindowActionPanelState(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class WindowActionPanelState extends State<WindowActionPanel> | 
					
						
							|  |  |  |     with MultiWindowListener, WindowListener { | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void initState() { | 
					
						
							|  |  |  |     super.initState(); | 
					
						
							|  |  |  |     DesktopMultiWindow.addListener(this); | 
					
						
							|  |  |  |     windowManager.addListener(this); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-17 19:37:00 +09:00
										 |  |  |     Future.delayed(Duration(milliseconds: 500), () { | 
					
						
							|  |  |  |       if (widget.isMainWindow) { | 
					
						
							|  |  |  |         windowManager.isMaximized().then((maximized) { | 
					
						
							|  |  |  |           if (widget.isMaximized.value != maximized) { | 
					
						
							|  |  |  |             WidgetsBinding.instance.addPostFrameCallback( | 
					
						
							|  |  |  |                 (_) => setState(() => widget.isMaximized.value = maximized)); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         final wc = WindowController.fromWindowId(windowId!); | 
					
						
							|  |  |  |         wc.isMaximized().then((maximized) { | 
					
						
							|  |  |  |           debugPrint("isMaximized $maximized"); | 
					
						
							|  |  |  |           if (widget.isMaximized.value != maximized) { | 
					
						
							|  |  |  |             WidgetsBinding.instance.addPostFrameCallback( | 
					
						
							|  |  |  |                 (_) => setState(() => widget.isMaximized.value = maximized)); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-10-14 10:48:33 +09:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void dispose() { | 
					
						
							|  |  |  |     DesktopMultiWindow.removeListener(this); | 
					
						
							|  |  |  |     windowManager.removeListener(this); | 
					
						
							|  |  |  |     super.dispose(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void onWindowMaximize() { | 
					
						
							|  |  |  |     // catch maximize from system
 | 
					
						
							| 
									
										
										
										
											2022-10-17 19:37:00 +09:00
										 |  |  |     if (!widget.isMaximized.value) { | 
					
						
							|  |  |  |       widget.isMaximized.value = true; | 
					
						
							| 
									
										
										
										
											2022-10-14 10:48:33 +09:00
										 |  |  |     } | 
					
						
							|  |  |  |     super.onWindowMaximize(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   void onWindowUnmaximize() { | 
					
						
							|  |  |  |     // catch unmaximize from system
 | 
					
						
							| 
									
										
										
										
											2022-10-17 19:37:00 +09:00
										 |  |  |     if (widget.isMaximized.value) { | 
					
						
							|  |  |  |       widget.isMaximized.value = false; | 
					
						
							| 
									
										
										
										
											2022-10-14 10:48:33 +09:00
										 |  |  |     } | 
					
						
							|  |  |  |     super.onWindowUnmaximize(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-06 17:39:19 +08:00
										 |  |  |   @override | 
					
						
							|  |  |  |   void onWindowClose() async { | 
					
						
							|  |  |  |     // hide window on close
 | 
					
						
							|  |  |  |     if (widget.isMainWindow) { | 
					
						
							|  |  |  |       await windowManager.hide(); | 
					
						
							|  |  |  |       rustDeskWinManager.unregisterActiveWindow(0); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       widget.onClose?.call(); | 
					
						
							| 
									
										
										
										
											2022-11-09 15:14:11 +08:00
										 |  |  |       await WindowController.fromWindowId(windowId!).hide(); | 
					
						
							| 
									
										
										
										
											2022-11-06 17:39:19 +08:00
										 |  |  |       rustDeskWinManager | 
					
						
							|  |  |  |           .call(WindowType.Main, kWindowEventHide, {"id": windowId!}); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     super.onWindowClose(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-18 11:07:53 +08:00
										 |  |  |   @override | 
					
						
							|  |  |  |   Widget build(BuildContext context) { | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09:00
										 |  |  |     return Row( | 
					
						
							|  |  |  |       mainAxisAlignment: MainAxisAlignment.end, | 
					
						
							| 
									
										
										
										
											2022-08-18 11:07:53 +08:00
										 |  |  |       children: [ | 
					
						
							| 
									
										
										
										
											2022-10-14 20:44:57 +09: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( | 
					
						
							|  |  |  |                   offstage: !widget.showMinimize, | 
					
						
							|  |  |  |                   child: ActionIcon( | 
					
						
							|  |  |  |                     message: 'Minimize', | 
					
						
							|  |  |  |                     icon: IconFont.min, | 
					
						
							|  |  |  |                     onTap: () { | 
					
						
							|  |  |  |                       if (widget.isMainWindow) { | 
					
						
							|  |  |  |                         windowManager.minimize(); | 
					
						
							|  |  |  |                       } else { | 
					
						
							|  |  |  |                         WindowController.fromWindowId(windowId!).minimize(); | 
					
						
							|  |  |  |                       } | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     isClose: false, | 
					
						
							|  |  |  |                   )), | 
					
						
							|  |  |  |               Offstage( | 
					
						
							|  |  |  |                   offstage: !widget.showMaximize, | 
					
						
							|  |  |  |                   child: Obx(() => ActionIcon( | 
					
						
							|  |  |  |                         message: | 
					
						
							|  |  |  |                             widget.isMaximized.value ? "Restore" : "Maximize", | 
					
						
							|  |  |  |                         icon: widget.isMaximized.value | 
					
						
							|  |  |  |                             ? IconFont.restore | 
					
						
							|  |  |  |                             : IconFont.max, | 
					
						
							|  |  |  |                         onTap: _toggleMaximize, | 
					
						
							|  |  |  |                         isClose: false, | 
					
						
							|  |  |  |                       ))), | 
					
						
							|  |  |  |               Offstage( | 
					
						
							|  |  |  |                   offstage: !widget.showClose, | 
					
						
							|  |  |  |                   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(windowId!) | 
					
						
							|  |  |  |                                 .close(); | 
					
						
							|  |  |  |                           } | 
					
						
							|  |  |  |                         }); | 
					
						
							|  |  |  |                       } | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     isClose: true, | 
					
						
							|  |  |  |                   )) | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ), | 
					
						
							| 
									
										
										
										
											2022-08-18 11:07:53 +08:00
										 |  |  |       ], | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2022-10-14 20:44:57 +09:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void _toggleMaximize() { | 
					
						
							|  |  |  |     toggleMaximize(widget.isMainWindow).then((maximize) { | 
					
						
							| 
									
										
										
										
											2022-10-17 19:37:00 +09:00
										 |  |  |       if (widget.isMaximized.value != maximize) { | 
					
						
							|  |  |  |         // update state for sub window, wc.unmaximize/maximize() will not invoke onWindowMaximize/Unmaximize
 | 
					
						
							|  |  |  |         widget.isMaximized.value = maximize; | 
					
						
							| 
									
										
										
										
											2022-10-14 20:44:57 +09:00
										 |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void startDragging(bool isMainWindow) { | 
					
						
							|  |  |  |   if (isMainWindow) { | 
					
						
							|  |  |  |     windowManager.startDragging(); | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     WindowController.fromWindowId(windowId!).startDragging(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /// return true -> window will be maximize
 | 
					
						
							|  |  |  | /// return false -> window will be unmaximize
 | 
					
						
							|  |  |  | Future<bool> toggleMaximize(bool isMainWindow) async { | 
					
						
							|  |  |  |   if (isMainWindow) { | 
					
						
							|  |  |  |     if (await windowManager.isMaximized()) { | 
					
						
							|  |  |  |       windowManager.unmaximize(); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       windowManager.maximize(); | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } else { | 
					
						
							|  |  |  |     final wc = WindowController.fromWindowId(windowId!); | 
					
						
							|  |  |  |     if (await wc.isMaximized()) { | 
					
						
							|  |  |  |       wc.unmaximize(); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       wc.maximize(); | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											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 21:19:05 +09:00
										 |  |  |   var confirm = true; | 
					
						
							| 
									
										
										
										
											2022-09-09 19:29:19 +08:00
										 |  |  |   final res = await gFFI.dialogManager.show<bool>((setState, close) { | 
					
						
							| 
									
										
										
										
											2022-10-13 21:19:05 +09:00
										 |  |  |     submit() { | 
					
						
							|  |  |  |       final opt = "enable-confirm-closing-tabs"; | 
					
						
							|  |  |  |       String value = bool2option(opt, confirm); | 
					
						
							|  |  |  |       bind.mainSetOption(key: opt, value: value); | 
					
						
							|  |  |  |       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 21:19:05 +09: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: [ | 
					
						
							|  |  |  |         TextButton(onPressed: close, child: Text(translate("Cancel"))), | 
					
						
							|  |  |  |         ElevatedButton(onPressed: submit, child: Text(translate("OK"))), | 
					
						
							|  |  |  |       ], | 
					
						
							|  |  |  |       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; | 
					
						
							| 
									
										
										
										
											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 23:10:26 +09:00
										 |  |  |   final double? maxLabelWidth; | 
					
						
							| 
									
										
										
										
											2022-10-20 23:56:23 +09:00
										 |  |  |   final Color? selectedTabBackgroundColor; | 
					
						
							|  |  |  |   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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |   const _ListView({ | 
					
						
							|  |  |  |     required this.controller, | 
					
						
							|  |  |  |     this.tabBuilder, | 
					
						
							|  |  |  |     this.tabMenuBuilder, | 
					
						
							|  |  |  |     this.labelGetter, | 
					
						
							|  |  |  |     this.maxLabelWidth, | 
					
						
							|  |  |  |     this.selectedTabBackgroundColor, | 
					
						
							|  |  |  |     this.unSelectedTabBackgroundColor, | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											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 && | 
					
						
							|  |  |  |         controller.tabType == DesktopTabType.main; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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; | 
					
						
							|  |  |  |                 return _Tab( | 
					
						
							| 
									
										
										
										
											2022-10-26 23:50:36 +09:00
										 |  |  |                   key: ValueKey(tab.key), | 
					
						
							| 
									
										
										
										
											2022-09-23 11:01:33 +08:00
										 |  |  |                   index: index, | 
					
						
							| 
									
										
										
										
											2022-10-26 23:50:36 +09:00
										 |  |  |                   tabInfoKey: tab.key, | 
					
						
							| 
									
										
										
										
											2022-09-23 11:01:33 +08:00
										 |  |  |                   label: labelGetter == null | 
					
						
							|  |  |  |                       ? Rx<String>(tab.label) | 
					
						
							|  |  |  |                       : labelGetter!(tab.label), | 
					
						
							|  |  |  |                   selectedIcon: tab.selectedIcon, | 
					
						
							|  |  |  |                   unselectedIcon: tab.unselectedIcon, | 
					
						
							|  |  |  |                   closable: tab.closable, | 
					
						
							|  |  |  |                   selected: state.value.selected, | 
					
						
							|  |  |  |                   onClose: () { | 
					
						
							|  |  |  |                     if (tab.onTabCloseButton != null) { | 
					
						
							|  |  |  |                       tab.onTabCloseButton!(); | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                       controller.remove(index); | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-08-29 18:48:12 +08:00
										 |  |  |                   }, | 
					
						
							| 
									
										
										
										
											2022-10-26 23:50:36 +09:00
										 |  |  |                   onTap: () { | 
					
						
							|  |  |  |                     controller.jumpTo(index); | 
					
						
							|  |  |  |                     tab.onTap?.call(); | 
					
						
							|  |  |  |                   }, | 
					
						
							|  |  |  |                   tabBuilder: tabBuilder, | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |                   tabMenuBuilder: tabMenuBuilder, | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09:00
										 |  |  |                   maxLabelWidth: maxLabelWidth, | 
					
						
							| 
									
										
										
										
											2022-10-20 23:56:23 +09:00
										 |  |  |                   selectedTabBackgroundColor: selectedTabBackgroundColor, | 
					
						
							|  |  |  |                   unSelectedTabBackgroundColor: unSelectedTabBackgroundColor, | 
					
						
							| 
									
										
										
										
											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 23:50:36 +09:00
										 |  |  |   final String tabInfoKey; | 
					
						
							| 
									
										
										
										
											2022-09-04 11:03:16 +08:00
										 |  |  |   final Rx<String> label; | 
					
						
							|  |  |  |   final IconData? selectedIcon; | 
					
						
							|  |  |  |   final IconData? unselectedIcon; | 
					
						
							|  |  |  |   final bool closable; | 
					
						
							|  |  |  |   final int selected; | 
					
						
							|  |  |  |   final Function() onClose; | 
					
						
							| 
									
										
										
										
											2022-10-26 23:50:36 +09:00
										 |  |  |   final Function() onTap; | 
					
						
							| 
									
										
										
										
											2022-10-20 23:56:23 +09:00
										 |  |  |   final TabBuilder? tabBuilder; | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |   final TabMenuBuilder? tabMenuBuilder; | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09:00
										 |  |  |   final double? maxLabelWidth; | 
					
						
							| 
									
										
										
										
											2022-10-20 23:56:23 +09:00
										 |  |  |   final Color? selectedTabBackgroundColor; | 
					
						
							|  |  |  |   final Color? unSelectedTabBackgroundColor; | 
					
						
							| 
									
										
										
										
											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 23:50:36 +09:00
										 |  |  |     required this.tabInfoKey, | 
					
						
							| 
									
										
										
										
											2022-09-04 11:03:16 +08:00
										 |  |  |     required this.label, | 
					
						
							|  |  |  |     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 23:50:36 +09:00
										 |  |  |     required this.onTap, | 
					
						
							| 
									
										
										
										
											2022-10-20 23:10:26 +09:00
										 |  |  |     this.maxLabelWidth, | 
					
						
							| 
									
										
										
										
											2022-10-20 23:56:23 +09:00
										 |  |  |     this.selectedTabBackgroundColor, | 
					
						
							|  |  |  |     this.unSelectedTabBackgroundColor, | 
					
						
							| 
									
										
										
										
											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 23:10:26 +09:00
										 |  |  |       return ConstrainedBox( | 
					
						
							|  |  |  |           constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200), | 
					
						
							|  |  |  |           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-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 23:50:36 +09:00
										 |  |  |         onTap: () => widget.onTap(), | 
					
						
							| 
									
										
										
										
											2022-10-20 23:56:23 +09:00
										 |  |  |         child: Container( | 
					
						
							| 
									
										
										
										
											2022-11-03 21:58:25 +08:00
										 |  |  |           color: isSelected | 
					
						
							|  |  |  |               ? widget.selectedTabBackgroundColor | 
					
						
							|  |  |  |               : widget.unSelectedTabBackgroundColor, | 
					
						
							|  |  |  |           child: Row( | 
					
						
							|  |  |  |             children: [ | 
					
						
							|  |  |  |               SizedBox( | 
					
						
							|  |  |  |                   height: _kTabBarHeight, | 
					
						
							|  |  |  |                   child: Row( | 
					
						
							|  |  |  |                       crossAxisAlignment: CrossAxisAlignment.center, | 
					
						
							|  |  |  |                       children: [ | 
					
						
							|  |  |  |                         _buildTabContent(), | 
					
						
							|  |  |  |                         Obx((() => _CloseButton( | 
					
						
							|  |  |  |                               visiable: hover.value && widget.closable, | 
					
						
							|  |  |  |                               tabSelected: isSelected, | 
					
						
							|  |  |  |                               onClose: () => widget.onClose(), | 
					
						
							|  |  |  |                             ))) | 
					
						
							|  |  |  |                       ])).paddingSymmetric(horizontal: 10), | 
					
						
							|  |  |  |               Offstage( | 
					
						
							|  |  |  |                 offstage: !showDivider, | 
					
						
							|  |  |  |                 child: VerticalDivider( | 
					
						
							|  |  |  |                   width: 1, | 
					
						
							|  |  |  |                   indent: _kDividerIndent, | 
					
						
							|  |  |  |                   endIndent: _kDividerIndent, | 
					
						
							|  |  |  |                   color: MyTheme.tabbar(context).dividerColor, | 
					
						
							|  |  |  |                   thickness: 1, | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  |               ) | 
					
						
							|  |  |  |             ], | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ), | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							| 
									
										
										
										
											2022-08-11 16:03:04 +08:00
										 |  |  |   final bool visiable; | 
					
						
							| 
									
										
										
										
											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, | 
					
						
							| 
									
										
										
										
											2022-08-11 16:03:04 +08:00
										 |  |  |     required this.visiable, | 
					
						
							| 
									
										
										
										
											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( | 
					
						
							|  |  |  |           offstage: !visiable, | 
					
						
							|  |  |  |           child: InkWell( | 
					
						
							| 
									
										
										
										
											2022-08-31 19:23:32 +08:00
										 |  |  |             customBorder: const RoundedRectangleBorder(), | 
					
						
							| 
									
										
										
										
											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
										 |  |  |             ), | 
					
						
							|  |  |  |           ), | 
					
						
							| 
									
										
										
										
											2022-08-11 21:29:43 +08:00
										 |  |  |         )).paddingOnly(left: 5); | 
					
						
							| 
									
										
										
										
											2022-08-06 17:08:48 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-24 16:44:43 +08:00
										 |  |  | class ActionIcon extends StatefulWidget { | 
					
						
							| 
									
										
										
										
											2022-10-20 23:22:02 +09:00
										 |  |  |   final String? message; | 
					
						
							| 
									
										
										
										
											2022-08-20 19:57:16 +08:00
										 |  |  |   final IconData icon; | 
					
						
							|  |  |  |   final Function() onTap; | 
					
						
							| 
									
										
										
										
											2022-09-04 11:03:16 +08:00
										 |  |  |   final bool isClose; | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |   final double iconSize; | 
					
						
							|  |  |  |   final double boxSize; | 
					
						
							| 
									
										
										
										
											2022-10-24 16:44:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-14 23:50:13 +09:00
										 |  |  |   const ActionIcon( | 
					
						
							|  |  |  |       {Key? key, | 
					
						
							| 
									
										
										
										
											2022-10-20 23:22:02 +09:00
										 |  |  |       this.message, | 
					
						
							| 
									
										
										
										
											2022-10-14 23:50:13 +09:00
										 |  |  |       required this.icon, | 
					
						
							|  |  |  |       required this.onTap, | 
					
						
							| 
									
										
										
										
											2022-10-18 23:56:36 +09:00
										 |  |  |       this.isClose = false, | 
					
						
							|  |  |  |       this.iconSize = _kActionIconSize, | 
					
						
							|  |  |  |       this.boxSize = _kTabBarHeight - 1}) | 
					
						
							| 
									
										
										
										
											2022-10-14 23:50:13 +09: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), | 
					
						
							|  |  |  |       child: Obx( | 
					
						
							|  |  |  |         () => InkWell( | 
					
						
							|  |  |  |           hoverColor: widget.isClose | 
					
						
							|  |  |  |               ? const Color.fromARGB(255, 196, 43, 28) | 
					
						
							|  |  |  |               : MyTheme.tabbar(context).hoverColor, | 
					
						
							|  |  |  |           onHover: (value) => hover.value = value, | 
					
						
							|  |  |  |           onTap: widget.onTap, | 
					
						
							|  |  |  |           child: SizedBox( | 
					
						
							|  |  |  |             height: widget.boxSize, | 
					
						
							|  |  |  |             width: widget.boxSize, | 
					
						
							|  |  |  |             child: Icon( | 
					
						
							|  |  |  |               widget.icon, | 
					
						
							|  |  |  |               color: hover.value && widget.isClose | 
					
						
							|  |  |  |                   ? Colors.white | 
					
						
							|  |  |  |                   : MyTheme.tabbar(context).unSelectedIconColor, | 
					
						
							|  |  |  |               size: widget.iconSize, | 
					
						
							| 
									
										
										
										
											2022-08-20 19:57:16 +08:00
										 |  |  |             ), | 
					
						
							|  |  |  |           ), | 
					
						
							| 
									
										
										
										
											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( | 
					
						
							|  |  |  |         message: 'New Connection', | 
					
						
							|  |  |  |         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
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const TabbarTheme( | 
					
						
							|  |  |  |       {required this.selectedTabIconColor, | 
					
						
							|  |  |  |       required this.unSelectedTabIconColor, | 
					
						
							|  |  |  |       required this.selectedTextColor, | 
					
						
							|  |  |  |       required this.unSelectedTextColor, | 
					
						
							|  |  |  |       required this.selectedIconColor, | 
					
						
							|  |  |  |       required this.unSelectedIconColor, | 
					
						
							|  |  |  |       required this.dividerColor, | 
					
						
							|  |  |  |       required this.hoverColor}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static const light = TabbarTheme( | 
					
						
							|  |  |  |       selectedTabIconColor: MyTheme.accent, | 
					
						
							|  |  |  |       unSelectedTabIconColor: Color.fromARGB(255, 162, 203, 241), | 
					
						
							|  |  |  |       selectedTextColor: Color.fromARGB(255, 26, 26, 26), | 
					
						
							|  |  |  |       unSelectedTextColor: Color.fromARGB(255, 96, 96, 96), | 
					
						
							|  |  |  |       selectedIconColor: Color.fromARGB(255, 26, 26, 26), | 
					
						
							|  |  |  |       unSelectedIconColor: Color.fromARGB(255, 96, 96, 96), | 
					
						
							|  |  |  |       dividerColor: Color.fromARGB(255, 238, 238, 238), | 
					
						
							|  |  |  |       hoverColor: Color.fromARGB(51, 158, 158, 158)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static const dark = TabbarTheme( | 
					
						
							|  |  |  |       selectedTabIconColor: MyTheme.accent, | 
					
						
							|  |  |  |       unSelectedTabIconColor: Color.fromARGB(255, 30, 65, 98), | 
					
						
							|  |  |  |       selectedTextColor: Color.fromARGB(255, 255, 255, 255), | 
					
						
							|  |  |  |       unSelectedTextColor: Color.fromARGB(255, 207, 207, 207), | 
					
						
							|  |  |  |       selectedIconColor: Color.fromARGB(255, 215, 215, 215), | 
					
						
							|  |  |  |       unSelectedIconColor: Color.fromARGB(255, 255, 255, 255), | 
					
						
							|  |  |  |       dividerColor: Color.fromARGB(255, 64, 64, 64), | 
					
						
							|  |  |  |       hoverColor: Colors.black26); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   ThemeExtension<TabbarTheme> copyWith({ | 
					
						
							|  |  |  |     Color? selectedTabIconColor, | 
					
						
							|  |  |  |     Color? unSelectedTabIconColor, | 
					
						
							|  |  |  |     Color? selectedTextColor, | 
					
						
							|  |  |  |     Color? unSelectedTextColor, | 
					
						
							|  |  |  |     Color? selectedIconColor, | 
					
						
							|  |  |  |     Color? unSelectedIconColor, | 
					
						
							|  |  |  |     Color? dividerColor, | 
					
						
							|  |  |  |     Color? hoverColor, | 
					
						
							|  |  |  |   }) { | 
					
						
							|  |  |  |     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, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @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), | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static color(BuildContext context) { | 
					
						
							|  |  |  |     return Theme.of(context).extension<ColorThemeExtension>()!; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-08-09 22:35:29 +08:00
										 |  |  | } |