Skip to content

Commit

Permalink
dropdown menu for tabs that cannot be displayed (rustdesk#7918)
Browse files Browse the repository at this point in the history
* The visibility threshold is 75%
* Used in remote, file transfer, port forward and cm

Signed-off-by: 21pages <[email protected]>
  • Loading branch information
21pages authored May 6, 2024
1 parent 937cea5 commit f6223a6
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 36 deletions.
2 changes: 1 addition & 1 deletion flutter/lib/desktop/pages/file_manager_tab_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
body: DesktopTab(
controller: tabController,
onWindowCloseButton: handleWindowCloseButton,
tail: const AddButton().paddingOnly(left: 10),
tail: const AddButton(),
labelGetter: DesktopTab.tablabelGetter,
)),
);
Expand Down
2 changes: 1 addition & 1 deletion flutter/lib/desktop/pages/port_forward_tab_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
tabController.clear();
return true;
},
tail: AddButton().paddingOnly(left: 10),
tail: AddButton(),
labelGetter: DesktopTab.tablabelGetter,
)),
);
Expand Down
2 changes: 1 addition & 1 deletion flutter/lib/desktop/pages/remote_tab_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
body: DesktopTab(
controller: tabController,
onWindowCloseButton: handleWindowCloseButton,
tail: const AddButton().paddingOnly(left: 10),
tail: const AddButton(),
pageViewBuilder: (pageView) => pageView,
labelGetter: DesktopTab.tablabelGetter,
tabBuilder: (key, icon, label, themeConf) => Obx(() {
Expand Down
2 changes: 1 addition & 1 deletion flutter/lib/desktop/pages/server_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
controller: serverModel.tabController,
selectedBorderColor: MyTheme.accent,
maxLabelWidth: 100,
tail: buildScrollJumper(),
tail: null, //buildScrollJumper(),
selectedTabBackgroundColor:
Theme.of(context).hintColor.withOpacity(0),
tabBuilder: (key, icon, label, themeConf) {
Expand Down
218 changes: 186 additions & 32 deletions flutter/lib/desktop/widgets/tabbar_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
import 'package:scroll_pos/scroll_pos.dart';
import 'package:window_manager/window_manager.dart';
import 'package:visibility_detector/visibility_detector.dart';

import '../../utils/multi_window_manager.dart';

Expand Down Expand Up @@ -256,6 +257,8 @@ class DesktopTab extends StatelessWidget {
late final DesktopTabType tabType;
late final bool isMainWindow;

final RxList<String> invisibleTabKeys = RxList.empty();

DesktopTab({
Key? key,
required this.controller,
Expand Down Expand Up @@ -430,6 +433,7 @@ class DesktopTab extends StatelessWidget {
},
child: _ListView(
controller: controller,
invisibleTabKeys: invisibleTabKeys,
tabBuilder: tabBuilder,
tabMenuBuilder: tabMenuBuilder,
labelGetter: labelGetter,
Expand All @@ -448,12 +452,14 @@ class DesktopTab extends StatelessWidget {
tabType: tabType,
state: state,
tabController: controller,
invisibleTabKeys: invisibleTabKeys,
tail: tail,
showMinimize: showMinimize,
showMaximize: showMaximize,
showClose: showClose,
onClose: onWindowCloseButton,
)
labelGetter: labelGetter,
).paddingOnly(left: 10)
],
);
}
Expand All @@ -471,17 +477,22 @@ class WindowActionPanel extends StatefulWidget {
final Widget? tail;
final Future<bool> Function()? onClose;

final RxList<String> invisibleTabKeys;
final LabelGetter? labelGetter;

const WindowActionPanel(
{Key? key,
required this.isMainWindow,
required this.tabType,
required this.state,
required this.tabController,
required this.invisibleTabKeys,
this.tail,
this.showMinimize = true,
this.showMaximize = true,
this.showClose = true,
this.onClose})
this.onClose,
this.labelGetter})
: super(key: key);

@override
Expand Down Expand Up @@ -658,11 +669,34 @@ class WindowActionPanelState extends State<WindowActionPanel>
super.onWindowClose();
}

bool showTabDowndown() {
return widget.tabController.state.value.tabs.length > 1 &&
(widget.tabController.tabType == DesktopTabType.remoteScreen ||
widget.tabController.tabType == DesktopTabType.fileTransfer ||
widget.tabController.tabType == DesktopTabType.portForward ||
widget.tabController.tabType == DesktopTabType.cm);
}

List<String> existingInvisibleTab() {
return widget.invisibleTabKeys
.where((key) =>
widget.tabController.state.value.tabs.any((tab) => tab.key == key))
.toList();
}

@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Obx(() => Offstage(
offstage:
!(showTabDowndown() && existingInvisibleTab().isNotEmpty),
child: _TabDropDownButton(
controller: widget.tabController,
labelGetter: widget.labelGetter,
tabkeys: existingInvisibleTab()),
)),
Offstage(offstage: widget.tail == null, child: widget.tail),
Offstage(
offstage: kUseCompatibleUiMode,
Expand Down Expand Up @@ -814,6 +848,7 @@ Future<bool> closeConfirmDialog() async {

class _ListView extends StatelessWidget {
final DesktopTabController controller;
final RxList<String> invisibleTabKeys;

final TabBuilder? tabBuilder;
final TabMenuBuilder? tabMenuBuilder;
Expand All @@ -825,8 +860,9 @@ class _ListView extends StatelessWidget {

Rx<DesktopTabState> get state => controller.state;

const _ListView({
_ListView({
required this.controller,
required this.invisibleTabKeys,
this.tabBuilder,
this.tabMenuBuilder,
this.labelGetter,
Expand All @@ -846,6 +882,19 @@ class _ListView extends StatelessWidget {
controller.tabType == DesktopTabType.install;
}

onVisibilityChanged(VisibilityInfo info) {
final key = (info.key as ValueKey).value;
if (info.visibleFraction < 0.75) {
if (!invisibleTabKeys.contains(key)) {
invisibleTabKeys.add(key);
}
invisibleTabKeys.removeWhere((key) =>
controller.state.value.tabs.where((e) => e.key == key).isEmpty);
} else {
invisibleTabKeys.remove(key);
}
}

@override
Widget build(BuildContext context) {
return Obx(() => ListView(
Expand All @@ -858,36 +907,41 @@ class _ListView extends StatelessWidget {
: state.value.tabs.asMap().entries.map((e) {
final index = e.key;
final tab = e.value;
return _Tab(
final label = labelGetter == null
? Rx<String>(tab.label)
: labelGetter!(tab.label);
return VisibilityDetector(
key: ValueKey(tab.key),
index: index,
tabInfoKey: tab.key,
label: labelGetter == null
? Rx<String>(tab.label)
: labelGetter!(tab.label),
tabType: controller.tabType,
selectedIcon: tab.selectedIcon,
unselectedIcon: tab.unselectedIcon,
closable: tab.closable,
selected: state.value.selected,
onClose: () {
if (tab.onTabCloseButton != null) {
tab.onTabCloseButton!();
} else {
controller.remove(index);
}
},
onTap: () {
controller.jumpTo(index);
tab.onTap?.call();
},
tabBuilder: tabBuilder,
tabMenuBuilder: tabMenuBuilder,
maxLabelWidth: maxLabelWidth,
selectedTabBackgroundColor: selectedTabBackgroundColor ??
MyTheme.tabbar(context).selectedTabBackgroundColor,
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
selectedBorderColor: selectedBorderColor,
onVisibilityChanged: onVisibilityChanged,
child: _Tab(
key: ValueKey(tab.key),
index: index,
tabInfoKey: tab.key,
label: label,
tabType: controller.tabType,
selectedIcon: tab.selectedIcon,
unselectedIcon: tab.unselectedIcon,
closable: tab.closable,
selected: state.value.selected,
onClose: () {
if (tab.onTabCloseButton != null) {
tab.onTabCloseButton!();
} else {
controller.remove(index);
}
},
onTap: () {
controller.jumpTo(index);
tab.onTap?.call();
},
tabBuilder: tabBuilder,
tabMenuBuilder: tabMenuBuilder,
maxLabelWidth: maxLabelWidth,
selectedTabBackgroundColor: selectedTabBackgroundColor ??
MyTheme.tabbar(context).selectedTabBackgroundColor,
unSelectedTabBackgroundColor: unSelectedTabBackgroundColor,
selectedBorderColor: selectedBorderColor,
),
);
}).toList()));
}
Expand Down Expand Up @@ -1115,6 +1169,7 @@ class ActionIcon extends StatefulWidget {
final String? message;
final IconData icon;
final GestureTapCallback? onTap;
final GestureTapDownCallback? onTapDown;
final bool isClose;
final double iconSize;
final double boxSize;
Expand All @@ -1124,6 +1179,7 @@ class ActionIcon extends StatefulWidget {
this.message,
required this.icon,
this.onTap,
this.onTapDown,
this.isClose = false,
this.iconSize = _kActionIconSize,
this.boxSize = _kTabBarHeight - 1})
Expand Down Expand Up @@ -1153,6 +1209,7 @@ class _ActionIconState extends State<ActionIcon> {
: MyTheme.tabbar(context).hoverColor,
onHover: (value) => hover.value = value,
onTap: widget.onTap,
onTapDown: widget.onTapDown,
child: SizedBox(
height: widget.boxSize,
width: widget.boxSize,
Expand Down Expand Up @@ -1193,6 +1250,103 @@ class AddButton extends StatelessWidget {
}
}

class _TabDropDownButton extends StatefulWidget {
final DesktopTabController controller;
final List<String> tabkeys;
final LabelGetter? labelGetter;

const _TabDropDownButton(
{required this.controller, required this.tabkeys, this.labelGetter});

@override
State<_TabDropDownButton> createState() => _TabDropDownButtonState();
}

class _TabDropDownButtonState extends State<_TabDropDownButton> {
var position = RelativeRect.fromLTRB(0, 0, 0, 0);

@override
Widget build(BuildContext context) {
List<String> sortedKeys = widget.controller.state.value.tabs
.where((e) => widget.tabkeys.contains(e.key))
.map((e) => e.key)
.toList();
return ActionIcon(
onTapDown: (details) {
final x = details.globalPosition.dx;
final y = details.globalPosition.dy;
position = RelativeRect.fromLTRB(x, y, x, y);
},
icon: Icons.arrow_drop_down,
onTap: () {
showMenu(
context: context,
position: position,
items: sortedKeys.map((e) {
var label = e;
final tabInfo = widget.controller.state.value.tabs
.firstWhereOrNull((element) => element.key == e);
if (tabInfo != null) {
label = tabInfo.label;
}
if (widget.labelGetter != null) {
label = widget.labelGetter!(e).value;
}
var index = widget.controller.state.value.tabs
.indexWhere((t) => t.key == e);
label = '${index + 1}. $label';
final menuHover = false.obs;
final btnHover = false.obs;
return PopupMenuItem<String>(
value: e,
height: 32,
onTap: () {
widget.controller.jumpToByKey(e);
if (Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
},
child: MouseRegion(
onHover: (event) => setState(() => menuHover.value = true),
onExit: (event) => setState(() => menuHover.value = false),
child: Row(
children: [
Expanded(
child: InkWell(child: Text(label)),
),
Obx(
() => Offstage(
offstage: !(tabInfo?.onTabCloseButton != null &&
menuHover.value),
child: InkWell(
onTap: () {
tabInfo?.onTabCloseButton?.call();
if (Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
},
child: MouseRegion(
cursor: SystemMouseCursors.click,
onHover: (event) =>
setState(() => btnHover.value = true),
onExit: (event) =>
setState(() => btnHover.value = false),
child: Icon(Icons.close,
color:
btnHover.value ? Colors.red : null))),
),
)
],
),
),
);
}).toList(),
);
},
);
}
}

class TabbarTheme extends ThemeExtension<TabbarTheme> {
final Color? selectedTabIconColor;
final Color? unSelectedTabIconColor;
Expand Down

0 comments on commit f6223a6

Please sign in to comment.