opt: DesktopTab tabs handle mouse wheel, add maxLabelWidth constraint, update cm
This commit is contained in:
parent
afa94d5907
commit
94c8b117ef
@ -124,6 +124,7 @@ class ConnectionManagerState extends State<ConnectionManager> {
|
|||||||
showMinimize: true,
|
showMinimize: true,
|
||||||
showClose: true,
|
showClose: true,
|
||||||
controller: serverModel.tabController,
|
controller: serverModel.tabController,
|
||||||
|
maxLabelWidth: 100,
|
||||||
pageViewBuilder: (pageView) => Row(children: [
|
pageViewBuilder: (pageView) => Row(children: [
|
||||||
Expanded(child: pageView),
|
Expanded(child: pageView),
|
||||||
Consumer<ChatModel>(
|
Consumer<ChatModel>(
|
||||||
|
@ -3,12 +3,14 @@ import 'dart:async';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart' hide TabBarTheme;
|
import 'package:flutter/material.dart' hide TabBarTheme;
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/main.dart';
|
import 'package:flutter_hbb/main.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:get/get.dart';
|
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:scroll_pos/scroll_pos.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
@ -132,7 +134,8 @@ class DesktopTabController {
|
|||||||
if (val.scrollController.hasClients &&
|
if (val.scrollController.hasClients &&
|
||||||
val.scrollController.canScroll &&
|
val.scrollController.canScroll &&
|
||||||
val.scrollController.itemCount > index) {
|
val.scrollController.itemCount > index) {
|
||||||
val.scrollController.scrollToItem(index, center: true, animate: true);
|
val.scrollController
|
||||||
|
.scrollToItem(index, center: false, animate: true);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
@ -188,10 +191,15 @@ class DesktopTab extends StatelessWidget {
|
|||||||
final Future<bool> Function()? onWindowCloseButton;
|
final Future<bool> Function()? onWindowCloseButton;
|
||||||
final TabBuilder? tabBuilder;
|
final TabBuilder? tabBuilder;
|
||||||
final LabelGetter? labelGetter;
|
final LabelGetter? labelGetter;
|
||||||
|
final double? maxLabelWidth;
|
||||||
|
|
||||||
final DesktopTabController controller;
|
final DesktopTabController controller;
|
||||||
Rx<DesktopTabState> get state => controller.state;
|
Rx<DesktopTabState> get state => controller.state;
|
||||||
final isMaximized = false.obs;
|
final isMaximized = false.obs;
|
||||||
|
final _scrollDebounce = Debouncer(delay: Duration(milliseconds: 50));
|
||||||
|
|
||||||
|
/// [_lastClickTime], help to handle double click
|
||||||
|
int _lastClickTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
late final DesktopTabType tabType;
|
late final DesktopTabType tabType;
|
||||||
late final bool isMainWindow;
|
late final bool isMainWindow;
|
||||||
@ -211,6 +219,7 @@ class DesktopTab extends StatelessWidget {
|
|||||||
this.onWindowCloseButton,
|
this.onWindowCloseButton,
|
||||||
this.tabBuilder,
|
this.tabBuilder,
|
||||||
this.labelGetter,
|
this.labelGetter,
|
||||||
|
this.maxLabelWidth,
|
||||||
}) : super(key: key) {
|
}) : super(key: key) {
|
||||||
tabType = controller.tabType;
|
tabType = controller.tabType;
|
||||||
isMainWindow =
|
isMainWindow =
|
||||||
@ -292,46 +301,72 @@ class DesktopTab extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildBar() {
|
Widget _buildBar() {
|
||||||
return Row(
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Expanded(
|
||||||
children: [
|
child: GestureDetector(
|
||||||
Offstage(
|
// custom double tap handler
|
||||||
offstage: !Platform.isMacOS,
|
onTap: showMaximize
|
||||||
child: const SizedBox(
|
? () {
|
||||||
width: 78,
|
final current = DateTime.now().millisecondsSinceEpoch;
|
||||||
)),
|
final elapsed = current - _lastClickTime;
|
||||||
GestureDetector(
|
_lastClickTime = current;
|
||||||
onDoubleTap: showMaximize
|
if (elapsed < kDesktopDoubleClickTimeMilli) {
|
||||||
? () => toggleMaximize(isMainWindow)
|
// onDoubleTap
|
||||||
.then((value) => isMaximized.value = value)
|
toggleMaximize(isMainWindow)
|
||||||
|
.then((value) => isMaximized.value = value);
|
||||||
|
}
|
||||||
|
}
|
||||||
: null,
|
: null,
|
||||||
onPanStart: (_) => startDragging(isMainWindow),
|
onPanStart: (_) => startDragging(isMainWindow),
|
||||||
child: Row(children: [
|
child: Row(
|
||||||
Offstage(
|
children: [
|
||||||
offstage: !showLogo,
|
Offstage(
|
||||||
child: SvgPicture.asset(
|
offstage: !Platform.isMacOS,
|
||||||
'assets/logo.svg',
|
child: const SizedBox(
|
||||||
width: 16,
|
width: 78,
|
||||||
height: 16,
|
)),
|
||||||
)),
|
Row(children: [
|
||||||
Offstage(
|
Offstage(
|
||||||
offstage: !showTitle,
|
offstage: !showLogo,
|
||||||
child: const Text(
|
child: SvgPicture.asset(
|
||||||
"RustDesk",
|
'assets/logo.svg',
|
||||||
style: TextStyle(fontSize: 13),
|
width: 16,
|
||||||
).marginOnly(left: 2))
|
height: 16,
|
||||||
]).marginOnly(
|
)),
|
||||||
left: 5,
|
Offstage(
|
||||||
right: 10,
|
offstage: !showTitle,
|
||||||
)),
|
child: const Text(
|
||||||
_ListView(
|
"RustDesk",
|
||||||
controller: controller,
|
style: TextStyle(fontSize: 13),
|
||||||
onTabClose: onTabClose,
|
).marginOnly(left: 2))
|
||||||
tabBuilder: tabBuilder,
|
]).marginOnly(
|
||||||
labelGetter: labelGetter,
|
left: 5,
|
||||||
),
|
right: 10,
|
||||||
],
|
),
|
||||||
),
|
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,
|
||||||
|
onTabClose: onTabClose,
|
||||||
|
tabBuilder: tabBuilder,
|
||||||
|
labelGetter: labelGetter,
|
||||||
|
maxLabelWidth: maxLabelWidth))),
|
||||||
|
],
|
||||||
|
))),
|
||||||
WindowActionPanel(
|
WindowActionPanel(
|
||||||
isMainWindow: isMainWindow,
|
isMainWindow: isMainWindow,
|
||||||
tabType: tabType,
|
tabType: tabType,
|
||||||
@ -435,14 +470,9 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return Row(
|
||||||
child: Row(
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
|
||||||
child: GestureDetector(
|
|
||||||
onDoubleTap: widget.showMaximize ? _toggleMaximize : null,
|
|
||||||
onPanStart: (_) => startDragging(widget.isMainWindow),
|
|
||||||
)),
|
|
||||||
Offstage(offstage: widget.tail == null, child: widget.tail),
|
Offstage(offstage: widget.tail == null, child: widget.tail),
|
||||||
Offstage(
|
Offstage(
|
||||||
offstage: !widget.showMinimize,
|
offstage: !widget.showMinimize,
|
||||||
@ -489,7 +519,7 @@ class WindowActionPanelState extends State<WindowActionPanel>
|
|||||||
isClose: true,
|
isClose: true,
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _toggleMaximize() {
|
void _toggleMaximize() {
|
||||||
@ -580,13 +610,13 @@ Future<bool> closeConfirmDialog() async {
|
|||||||
return res == true;
|
return res == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
|
||||||
class _ListView extends StatelessWidget {
|
class _ListView extends StatelessWidget {
|
||||||
final DesktopTabController controller;
|
final DesktopTabController controller;
|
||||||
final Function(String key)? onTabClose;
|
final Function(String key)? onTabClose;
|
||||||
|
|
||||||
final TabBuilder? tabBuilder;
|
final TabBuilder? tabBuilder;
|
||||||
final LabelGetter? labelGetter;
|
final LabelGetter? labelGetter;
|
||||||
|
final double? maxLabelWidth;
|
||||||
|
|
||||||
Rx<DesktopTabState> get state => controller.state;
|
Rx<DesktopTabState> get state => controller.state;
|
||||||
|
|
||||||
@ -594,7 +624,8 @@ class _ListView extends StatelessWidget {
|
|||||||
{required this.controller,
|
{required this.controller,
|
||||||
required this.onTabClose,
|
required this.onTabClose,
|
||||||
this.tabBuilder,
|
this.tabBuilder,
|
||||||
this.labelGetter});
|
this.labelGetter,
|
||||||
|
this.maxLabelWidth});
|
||||||
|
|
||||||
/// Check whether to show ListView
|
/// Check whether to show ListView
|
||||||
///
|
///
|
||||||
@ -645,6 +676,7 @@ class _ListView extends StatelessWidget {
|
|||||||
themeConf,
|
themeConf,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
maxLabelWidth: maxLabelWidth,
|
||||||
);
|
);
|
||||||
}).toList()));
|
}).toList()));
|
||||||
}
|
}
|
||||||
@ -661,6 +693,7 @@ class _Tab extends StatefulWidget {
|
|||||||
final Function() onSelected;
|
final Function() onSelected;
|
||||||
final Widget Function(Widget icon, Widget label, TabThemeConf themeConf)?
|
final Widget Function(Widget icon, Widget label, TabThemeConf themeConf)?
|
||||||
tabBuilder;
|
tabBuilder;
|
||||||
|
final double? maxLabelWidth;
|
||||||
|
|
||||||
const _Tab({
|
const _Tab({
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -673,6 +706,7 @@ class _Tab extends StatefulWidget {
|
|||||||
required this.selected,
|
required this.selected,
|
||||||
required this.onClose,
|
required this.onClose,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
|
this.maxLabelWidth,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -697,14 +731,17 @@ class _TabState extends State<_Tab> with RestorationMixin {
|
|||||||
: MyTheme.tabbar(context).unSelectedTabIconColor,
|
: MyTheme.tabbar(context).unSelectedTabIconColor,
|
||||||
).paddingOnly(right: 5));
|
).paddingOnly(right: 5));
|
||||||
final labelWidget = Obx(() {
|
final labelWidget = Obx(() {
|
||||||
return Text(
|
return ConstrainedBox(
|
||||||
translate(widget.label.value),
|
constraints: BoxConstraints(maxWidth: widget.maxLabelWidth ?? 200),
|
||||||
textAlign: TextAlign.center,
|
child: Text(
|
||||||
style: TextStyle(
|
translate(widget.label.value),
|
||||||
color: isSelected
|
textAlign: TextAlign.center,
|
||||||
? MyTheme.tabbar(context).selectedTextColor
|
style: TextStyle(
|
||||||
: MyTheme.tabbar(context).unSelectedTextColor),
|
color: isSelected
|
||||||
);
|
? MyTheme.tabbar(context).selectedTextColor
|
||||||
|
: MyTheme.tabbar(context).unSelectedTextColor),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (widget.tabBuilder == null) {
|
if (widget.tabBuilder == null) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user