Merge pull request #1608 from fufesou/flutter_desktop_popupmenu_adjust

flutter_desktop: remove animation & adjust popup menu
This commit is contained in:
RustDesk 2022-09-23 13:55:59 +08:00 committed by GitHub
commit 95a241bdf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 478 additions and 279 deletions

View File

@ -177,6 +177,12 @@ class MyTheme {
), ),
splashColor: Colors.transparent, splashColor: Colors.transparent,
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
splashFactory: isDesktop ? NoSplash.splashFactory : null,
textButtonTheme: isDesktop
? TextButtonThemeData(
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
)
: null,
).copyWith( ).copyWith(
extensions: <ThemeExtension<dynamic>>[ extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.light, ColorThemeExtension.light,
@ -192,6 +198,12 @@ class MyTheme {
), ),
splashColor: Colors.transparent, splashColor: Colors.transparent,
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
splashFactory: isDesktop ? NoSplash.splashFactory : null,
textButtonTheme: isDesktop
? TextButtonThemeData(
style: ButtonStyle(splashFactory: NoSplash.splashFactory),
)
: null,
).copyWith( ).copyWith(
extensions: <ThemeExtension<dynamic>>[ extensions: <ThemeExtension<dynamic>>[
ColorThemeExtension.dark, ColorThemeExtension.dark,

View File

@ -11,7 +11,8 @@ import '../../mobile/pages/settings_page.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
class AddressBook extends StatefulWidget { class AddressBook extends StatefulWidget {
const AddressBook({Key? key}) : super(key: key); final EdgeInsets? menuPadding;
const AddressBook({Key? key, this.menuPadding}) : super(key: key);
@override @override
State<StatefulWidget> createState() { State<StatefulWidget> createState() {
@ -180,7 +181,9 @@ class _AddressBookState extends State<AddressBook> {
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: AddressBookPeersView()), child: AddressBookPeersView(
menuPadding: widget.menuPadding,
)),
) )
], ],
)); ));

View File

@ -14,7 +14,7 @@ import '../../desktop/widgets/popup_menu.dart';
class _PopupMenuTheme { class _PopupMenuTheme {
static const Color commonColor = MyTheme.accent; static const Color commonColor = MyTheme.accent;
// kMinInteractiveDimension // kMinInteractiveDimension
static const double height = 25.0; static const double height = 20.0;
static const double dividerHeight = 3.0; static const double dividerHeight = 3.0;
} }
@ -319,8 +319,10 @@ class _PeerCardState extends State<_PeerCard>
abstract class BasePeerCard extends StatelessWidget { abstract class BasePeerCard extends StatelessWidget {
final Peer peer; final Peer peer;
final EdgeInsets? menuPadding;
BasePeerCard({required this.peer, Key? key}) : super(key: key); BasePeerCard({required this.peer, this.menuPadding, Key? key})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -365,6 +367,7 @@ abstract class BasePeerCard extends StatelessWidget {
isRDP: isRDP, isRDP: isRDP,
); );
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -414,17 +417,25 @@ abstract class BasePeerCard extends StatelessWidget {
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Transform.scale(
scale: 0.8,
child: IconButton( child: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.edit), icon: const Icon(Icons.edit),
onPressed: () => _rdpDialog(id), padding: EdgeInsets.zero,
), onPressed: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
_rdpDialog(id);
},
)),
)) ))
], ],
)), )),
proc: () { proc: () {
connect(context, id, isRDP: true); connect(context, id, isRDP: true);
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -439,6 +450,7 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () { proc: () {
bind.mainWol(id: id); bind.mainWol(id: id);
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -447,6 +459,7 @@ abstract class BasePeerCard extends StatelessWidget {
Future<MenuEntryBase<String>> _forceAlwaysRelayAction(String id) async { Future<MenuEntryBase<String>> _forceAlwaysRelayAction(String id) async {
const option = 'force-always-relay'; const option = 'force-always-relay';
return MenuEntrySwitch<String>( return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox,
text: translate('Always connect via relay'), text: translate('Always connect via relay'),
getter: () async { getter: () async {
return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty; return (await bind.mainGetPeerOption(id: id, key: option)).isNotEmpty;
@ -461,7 +474,8 @@ abstract class BasePeerCard extends StatelessWidget {
} }
await bind.mainSetPeerOption(id: id, key: option, value: value); await bind.mainSetPeerOption(id: id, key: option, value: value);
}, },
dismissOnClicked: false, padding: menuPadding,
dismissOnClicked: true,
); );
} }
@ -475,6 +489,7 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () { proc: () {
_rename(id, isAddressBook); _rename(id, isAddressBook);
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -494,6 +509,7 @@ abstract class BasePeerCard extends StatelessWidget {
await reloadFunc(); await reloadFunc();
}(); }();
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -508,6 +524,7 @@ abstract class BasePeerCard extends StatelessWidget {
proc: () { proc: () {
bind.mainForgetPassword(id: id); bind.mainForgetPassword(id: id);
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -528,6 +545,7 @@ abstract class BasePeerCard extends StatelessWidget {
} }
}(); }();
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -549,6 +567,7 @@ abstract class BasePeerCard extends StatelessWidget {
} }
}(); }();
}, },
padding: menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -616,7 +635,8 @@ abstract class BasePeerCard extends StatelessWidget {
} }
class RecentPeerCard extends BasePeerCard { class RecentPeerCard extends BasePeerCard {
RecentPeerCard({required Peer peer, Key? key}) : super(peer: peer, key: key); RecentPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, menuPadding: menuPadding, key: key);
@override @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -645,8 +665,8 @@ class RecentPeerCard extends BasePeerCard {
} }
class FavoritePeerCard extends BasePeerCard { class FavoritePeerCard extends BasePeerCard {
FavoritePeerCard({required Peer peer, Key? key}) FavoritePeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, key: key); : super(peer: peer, menuPadding: menuPadding, key: key);
@override @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -677,8 +697,8 @@ class FavoritePeerCard extends BasePeerCard {
} }
class DiscoveredPeerCard extends BasePeerCard { class DiscoveredPeerCard extends BasePeerCard {
DiscoveredPeerCard({required Peer peer, Key? key}) DiscoveredPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, key: key); : super(peer: peer, menuPadding: menuPadding, key: key);
@override @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -706,8 +726,8 @@ class DiscoveredPeerCard extends BasePeerCard {
} }
class AddressBookPeerCard extends BasePeerCard { class AddressBookPeerCard extends BasePeerCard {
AddressBookPeerCard({required Peer peer, Key? key}) AddressBookPeerCard({required Peer peer, EdgeInsets? menuPadding, Key? key})
: super(peer: peer, key: key); : super(peer: peer, menuPadding: menuPadding, key: key);
@override @override
Future<List<MenuEntryBase<String>>> _buildMenuItems( Future<List<MenuEntryBase<String>>> _buildMenuItems(
@ -748,6 +768,7 @@ class AddressBookPeerCard extends BasePeerCard {
await gFFI.abModel.updateAb(); await gFFI.abModel.updateAb();
}(); }();
}, },
padding: super.menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }
@ -762,6 +783,7 @@ class AddressBookPeerCard extends BasePeerCard {
proc: () { proc: () {
_abEditTag(id); _abEditTag(id);
}, },
padding: super.menuPadding,
dismissOnClicked: true, dismissOnClicked: true,
); );
} }

View File

@ -224,7 +224,7 @@ abstract class BasePeersView extends StatelessWidget {
} }
class RecentPeersView extends BasePeersView { class RecentPeersView extends BasePeersView {
RecentPeersView({Key? key}) RecentPeersView({Key? key, EdgeInsets? menuPadding})
: super( : super(
key: key, key: key,
name: 'recent peer', name: 'recent peer',
@ -232,6 +232,7 @@ class RecentPeersView extends BasePeersView {
offstageFunc: (Peer peer) => false, offstageFunc: (Peer peer) => false,
peerCardBuilder: (Peer peer) => RecentPeerCard( peerCardBuilder: (Peer peer) => RecentPeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding,
), ),
initPeers: [], initPeers: [],
); );
@ -245,7 +246,7 @@ class RecentPeersView extends BasePeersView {
} }
class FavoritePeersView extends BasePeersView { class FavoritePeersView extends BasePeersView {
FavoritePeersView({Key? key}) FavoritePeersView({Key? key, EdgeInsets? menuPadding})
: super( : super(
key: key, key: key,
name: 'favorite peer', name: 'favorite peer',
@ -253,6 +254,7 @@ class FavoritePeersView extends BasePeersView {
offstageFunc: (Peer peer) => false, offstageFunc: (Peer peer) => false,
peerCardBuilder: (Peer peer) => FavoritePeerCard( peerCardBuilder: (Peer peer) => FavoritePeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding,
), ),
initPeers: [], initPeers: [],
); );
@ -266,7 +268,7 @@ class FavoritePeersView extends BasePeersView {
} }
class DiscoveredPeersView extends BasePeersView { class DiscoveredPeersView extends BasePeersView {
DiscoveredPeersView({Key? key}) DiscoveredPeersView({Key? key, EdgeInsets? menuPadding})
: super( : super(
key: key, key: key,
name: 'discovered peer', name: 'discovered peer',
@ -274,6 +276,7 @@ class DiscoveredPeersView extends BasePeersView {
offstageFunc: (Peer peer) => false, offstageFunc: (Peer peer) => false,
peerCardBuilder: (Peer peer) => DiscoveredPeerCard( peerCardBuilder: (Peer peer) => DiscoveredPeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding,
), ),
initPeers: [], initPeers: [],
); );
@ -287,7 +290,7 @@ class DiscoveredPeersView extends BasePeersView {
} }
class AddressBookPeersView extends BasePeersView { class AddressBookPeersView extends BasePeersView {
AddressBookPeersView({Key? key}) AddressBookPeersView({Key? key, EdgeInsets? menuPadding})
: super( : super(
key: key, key: key,
name: 'address book peer', name: 'address book peer',
@ -296,6 +299,7 @@ class AddressBookPeersView extends BasePeersView {
!_hitTag(gFFI.abModel.selectedTags, peer.tags), !_hitTag(gFFI.abModel.selectedTags, peer.tags),
peerCardBuilder: (Peer peer) => AddressBookPeerCard( peerCardBuilder: (Peer peer) => AddressBookPeerCard(
peer: peer, peer: peer,
menuPadding: menuPadding,
), ),
initPeers: _loadPeers(), initPeers: _loadPeers(),
); );

View File

@ -74,10 +74,18 @@ class _ConnectionPageState extends State<ConnectionPage> {
translate('Address Book') translate('Address Book')
], ],
children: [ children: [
RecentPeersView(), RecentPeersView(
FavoritePeersView(), menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
DiscoveredPeersView(), ),
const AddressBook(), FavoritePeersView(
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
),
DiscoveredPeersView(
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
),
const AddressBook(
menuPadding: EdgeInsets.only(left: 12.0, right: 3.0),
),
], ],
)), )),
], ],

View File

@ -14,7 +14,8 @@ import 'package:flutter/material.dart';
// void setState(VoidCallback fn) { } // void setState(VoidCallback fn) { }
// enum Menu { itemOne, itemTwo, itemThree, itemFour } // enum Menu { itemOne, itemTwo, itemThree, itemFour }
const Duration _kMenuDuration = Duration(milliseconds: 300); // const Duration _kMenuDuration = Duration(milliseconds: 300);
const Duration _kMenuDuration = Duration(milliseconds: 0);
const double _kMenuCloseIntervalEnd = 2.0 / 3.0; const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
const double _kMenuHorizontalPadding = 16.0; const double _kMenuHorizontalPadding = 16.0;
const double _kMenuDividerHeight = 16.0; const double _kMenuDividerHeight = 16.0;
@ -22,7 +23,7 @@ const double _kMenuDividerHeight = 16.0;
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep; const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
const double _kMenuMaxWidth = double.infinity; const double _kMenuMaxWidth = double.infinity;
// const double _kMenuVerticalPadding = 8.0; // const double _kMenuVerticalPadding = 8.0;
const double _kMenuVerticalPadding = 0.0; const double _kMenuVerticalPadding = 8.0;
const double _kMenuWidthStep = 0.0; const double _kMenuWidthStep = 0.0;
//const double _kMenuScreenPadding = 8.0; //const double _kMenuScreenPadding = 8.0;
const double _kMenuScreenPadding = 0.0; const double _kMenuScreenPadding = 0.0;

View File

@ -78,7 +78,8 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
duration: kThemeChangeDuration, duration: kThemeChangeDuration,
child: Container( child: Container(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: widget.height), constraints: BoxConstraints(
minHeight: widget.height, maxHeight: widget.height),
padding: padding:
widget.padding ?? const EdgeInsets.symmetric(horizontal: 16), widget.padding ?? const EdgeInsets.symmetric(horizontal: 16),
child: widget.child, child: widget.child,
@ -156,12 +157,14 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
final RadioCurOptionGetter curOptionGetter; final RadioCurOptionGetter curOptionGetter;
final RadioOptionSetter optionSetter; final RadioOptionSetter optionSetter;
final RxString _curOption = "".obs; final RxString _curOption = "".obs;
final EdgeInsets? padding;
MenuEntryRadios({ MenuEntryRadios({
required this.text, required this.text,
required this.optionsGetter, required this.optionsGetter,
required this.curOptionGetter, required this.curOptionGetter,
required this.optionSetter, required this.optionSetter,
this.padding,
dismissOnClicked = false, dismissOnClicked = false,
RxBool? enabled, RxBool? enabled,
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled) { }) : super(dismissOnClicked: dismissOnClicked, enabled: enabled) {
@ -189,8 +192,10 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
height: conf.height, height: conf.height,
child: TextButton( child: TextButton(
child: Container( child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height), constraints:
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
child: Row( child: Row(
children: [ children: [
Text( Text(
@ -203,16 +208,21 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: SizedBox( child: Transform.scale(
width: 20.0, scale: MenuConfig.iconScale,
height: 20.0,
child: Obx(() => opt.value == curOption.value child: Obx(() => opt.value == curOption.value
? Icon( ? IconButton(
padding: const EdgeInsets.fromLTRB(
8.0, 0.0, 8.0, 0.0),
hoverColor: Colors.transparent,
focusColor: Colors.transparent,
onPressed: () {},
icon: Icon(
Icons.check, Icons.check,
color: conf.commonColor, color: conf.commonColor,
) ))
: const SizedBox.shrink())), : const SizedBox.shrink()),
)), ))),
], ],
), ),
), ),
@ -239,12 +249,14 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
final RadioCurOptionGetter curOptionGetter; final RadioCurOptionGetter curOptionGetter;
final RadioOptionSetter optionSetter; final RadioOptionSetter optionSetter;
final RxString _curOption = "".obs; final RxString _curOption = "".obs;
final EdgeInsets? padding;
MenuEntrySubRadios({ MenuEntrySubRadios({
required this.text, required this.text,
required this.optionsGetter, required this.optionsGetter,
required this.curOptionGetter, required this.curOptionGetter,
required this.optionSetter, required this.optionSetter,
this.padding,
dismissOnClicked = false, dismissOnClicked = false,
RxBool? enabled, RxBool? enabled,
}) : super( }) : super(
@ -275,8 +287,10 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
height: conf.height, height: conf.height,
child: TextButton( child: TextButton(
child: Container( child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height), constraints:
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
child: Row( child: Row(
children: [ children: [
Text( Text(
@ -289,14 +303,18 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: SizedBox( child: Transform.scale(
width: 20.0, scale: MenuConfig.iconScale,
height: 20.0,
child: Obx(() => opt.value == curOption.value child: Obx(() => opt.value == curOption.value
? Icon( ? IconButton(
padding: EdgeInsets.zero,
hoverColor: Colors.transparent,
focusColor: Colors.transparent,
onPressed: () {},
icon: Icon(
Icons.check, Icons.check,
color: conf.commonColor, color: conf.commonColor,
) ))
: const SizedBox.shrink())), : const SizedBox.shrink())),
)), )),
], ],
@ -318,7 +336,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
return [ return [
PopupMenuChildrenItem( PopupMenuChildrenItem(
enabled: super.enabled, enabled: super.enabled,
padding: EdgeInsets.zero, padding: padding,
height: conf.height, height: conf.height,
itemBuilder: (BuildContext context) => itemBuilder: (BuildContext context) =>
options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(), options.map((opt) => _buildSecondMenu(context, conf, opt)).toList(),
@ -345,22 +363,31 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
} }
} }
enum SwitchType {
sswitch,
scheckbox,
}
typedef SwitchGetter = Future<bool> Function(); typedef SwitchGetter = Future<bool> Function();
typedef SwitchSetter = Future<void> Function(bool); typedef SwitchSetter = Future<void> Function(bool);
abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> { abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
final SwitchType switchType;
final String text; final String text;
final EdgeInsets? padding;
Rx<TextStyle>? textStyle; Rx<TextStyle>? textStyle;
MenuEntrySwitchBase({ MenuEntrySwitchBase({
required this.switchType,
required this.text, required this.text,
required dismissOnClicked, required dismissOnClicked,
this.textStyle, this.textStyle,
this.padding,
RxBool? enabled, RxBool? enabled,
}) : super(dismissOnClicked: dismissOnClicked, enabled: enabled); }) : super(dismissOnClicked: dismissOnClicked, enabled: enabled);
RxBool get curOption; RxBool get curOption;
Future<void> setOption(bool option); Future<void> setOption(bool? option);
@override @override
List<mod_menu.PopupMenuEntry<T>> build( List<mod_menu.PopupMenuEntry<T>> build(
@ -376,6 +403,7 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
height: conf.height, height: conf.height,
child: TextButton( child: TextButton(
child: Container( child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
height: conf.height, height: conf.height,
child: Row(children: [ child: Row(children: [
@ -386,7 +414,11 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Obx(() => Switch( child: Transform.scale(
scale: MenuConfig.iconScale,
child: Obx(() {
if (switchType == SwitchType.sswitch) {
return Switch(
value: curOption.value, value: curOption.value,
onChanged: (v) { onChanged: (v) {
if (super.dismissOnClicked && if (super.dismissOnClicked &&
@ -395,7 +427,20 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
} }
setOption(v); setOption(v);
}, },
)), );
} else {
return Checkbox(
value: curOption.value,
onChanged: (v) {
if (super.dismissOnClicked &&
Navigator.canPop(context)) {
Navigator.pop(context);
}
setOption(v);
},
);
}
})),
)) ))
])), ])),
onPressed: () { onPressed: () {
@ -416,15 +461,19 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
final RxBool _curOption = false.obs; final RxBool _curOption = false.obs;
MenuEntrySwitch({ MenuEntrySwitch({
required SwitchType switchType,
required String text, required String text,
required this.getter, required this.getter,
required this.setter, required this.setter,
Rx<TextStyle>? textStyle, Rx<TextStyle>? textStyle,
EdgeInsets? padding,
dismissOnClicked = false, dismissOnClicked = false,
RxBool? enabled, RxBool? enabled,
}) : super( }) : super(
switchType: switchType,
text: text, text: text,
textStyle: textStyle, textStyle: textStyle,
padding: padding,
dismissOnClicked: dismissOnClicked, dismissOnClicked: dismissOnClicked,
enabled: enabled, enabled: enabled,
) { ) {
@ -436,7 +485,8 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
@override @override
RxBool get curOption => _curOption; RxBool get curOption => _curOption;
@override @override
setOption(bool option) async { setOption(bool? option) async {
if (option != null) {
await setter(option); await setter(option);
final opt = await getter(); final opt = await getter();
if (_curOption.value != opt) { if (_curOption.value != opt) {
@ -444,6 +494,7 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
} }
} }
} }
}
typedef Switch2Getter = RxBool Function(); typedef Switch2Getter = RxBool Function();
typedef Switch2Setter = Future<void> Function(bool); typedef Switch2Setter = Future<void> Function(bool);
@ -453,32 +504,40 @@ class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
final SwitchSetter setter; final SwitchSetter setter;
MenuEntrySwitch2({ MenuEntrySwitch2({
required SwitchType switchType,
required String text, required String text,
required this.getter, required this.getter,
required this.setter, required this.setter,
Rx<TextStyle>? textStyle, Rx<TextStyle>? textStyle,
EdgeInsets? padding,
dismissOnClicked = false, dismissOnClicked = false,
RxBool? enabled, RxBool? enabled,
}) : super( }) : super(
switchType: switchType,
text: text, text: text,
textStyle: textStyle, textStyle: textStyle,
padding: padding,
dismissOnClicked: dismissOnClicked); dismissOnClicked: dismissOnClicked);
@override @override
RxBool get curOption => getter(); RxBool get curOption => getter();
@override @override
setOption(bool option) async { setOption(bool? option) async {
if (option != null) {
await setter(option); await setter(option);
} }
} }
}
class MenuEntrySubMenu<T> extends MenuEntryBase<T> { class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
final String text; final String text;
final List<MenuEntryBase<T>> entries; final List<MenuEntryBase<T>> entries;
final EdgeInsets? padding;
MenuEntrySubMenu({ MenuEntrySubMenu({
required this.text, required this.text,
required this.entries, required this.entries,
this.padding,
RxBool? enabled, RxBool? enabled,
}) : super(enabled: enabled); }) : super(enabled: enabled);
@ -490,7 +549,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
PopupMenuChildrenItem( PopupMenuChildrenItem(
enabled: super.enabled, enabled: super.enabled,
height: conf.height, height: conf.height,
padding: EdgeInsets.zero, padding: padding,
position: mod_menu.PopupMenuPosition.overSide, position: mod_menu.PopupMenuPosition.overSide,
itemBuilder: (BuildContext context) => entries itemBuilder: (BuildContext context) => entries
.map((entry) => entry.build(context, conf)) .map((entry) => entry.build(context, conf))
@ -522,10 +581,12 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
class MenuEntryButton<T> extends MenuEntryBase<T> { class MenuEntryButton<T> extends MenuEntryBase<T> {
final Widget Function(TextStyle? style) childBuilder; final Widget Function(TextStyle? style) childBuilder;
Function() proc; Function() proc;
final EdgeInsets? padding;
MenuEntryButton({ MenuEntryButton({
required this.childBuilder, required this.childBuilder,
required this.proc, required this.proc,
this.padding,
dismissOnClicked = false, dismissOnClicked = false,
RxBool? enabled, RxBool? enabled,
}) : super( }) : super(
@ -553,8 +614,10 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
} }
: null, : null,
child: Container( child: Container(
padding: padding,
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height), constraints:
BoxConstraints(minHeight: conf.height, maxHeight: conf.height),
child: childBuilder( child: childBuilder(
super.enabled!.value ? enabledStyle : disabledStyle), super.enabled!.value ? enabledStyle : disabledStyle),
), ),

View File

@ -19,7 +19,7 @@ import './material_mod_popup_menu.dart' as mod_menu;
class _MenubarTheme { class _MenubarTheme {
static const Color commonColor = MyTheme.accent; static const Color commonColor = MyTheme.accent;
// kMinInteractiveDimension // kMinInteractiveDimension
static const double height = 25.0; static const double height = 20.0;
static const double dividerHeight = 12.0; static const double dividerHeight = 12.0;
} }
@ -367,11 +367,14 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
List<MenuEntryBase<String>> _getControlMenu(BuildContext context) { List<MenuEntryBase<String>> _getControlMenu(BuildContext context) {
final pi = widget.ffi.ffiModel.pi; final pi = widget.ffi.ffiModel.pi;
final perms = widget.ffi.ffiModel.permissions; final perms = widget.ffi.ffiModel.permissions;
const EdgeInsets padding = EdgeInsets.only(left: 14.0, right: 5.0);
final List<MenuEntryBase<String>> displayMenu = []; final List<MenuEntryBase<String>> displayMenu = [];
displayMenu.addAll([ displayMenu.addAll([
MenuEntryButton<String>( MenuEntryButton<String>(
childBuilder: (TextStyle? style) => Row( childBuilder: (TextStyle? style) => Container(
alignment: AlignmentDirectional.center,
height: _MenubarTheme.height,
child: Row(
children: [ children: [
Text( Text(
translate('OS Password'), translate('OS Password'),
@ -380,18 +383,25 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Transform.scale(
scale: 0.8,
child: IconButton( child: IconButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
icon: const Icon(Icons.edit), icon: const Icon(Icons.edit),
onPressed: () => showSetOSPassword( onPressed: () {
widget.id, false, widget.ffi.dialogManager), if (Navigator.canPop(context)) {
), Navigator.pop(context);
}
showSetOSPassword(
widget.id, false, widget.ffi.dialogManager);
})),
)) ))
], ],
), )),
proc: () { proc: () {
showSetOSPassword(widget.id, false, widget.ffi.dialogManager); showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
), ),
MenuEntryButton<String>( MenuEntryButton<String>(
@ -402,6 +412,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
connect(context, widget.id, isFileTransfer: true); connect(context, widget.id, isFileTransfer: true);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
), ),
MenuEntryButton<String>( MenuEntryButton<String>(
@ -409,6 +420,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
translate('TCP Tunneling'), translate('TCP Tunneling'),
style: style, style: style,
), ),
padding: padding,
proc: () { proc: () {
connect(context, widget.id, isTcpTunneling: true); connect(context, widget.id, isTcpTunneling: true);
}, },
@ -427,6 +439,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
showAuditDialog(widget.id, widget.ffi.dialogManager); showAuditDialog(widget.id, widget.ffi.dialogManager);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
), ),
); );
@ -443,6 +456,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
bind.sessionCtrlAltDel(id: widget.id); bind.sessionCtrlAltDel(id: widget.id);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
} }
@ -459,6 +473,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager); showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
} }
@ -472,6 +487,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
bind.sessionLockScreen(id: widget.id); bind.sessionLockScreen(id: widget.id);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
@ -489,6 +505,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
value: '${blockInput.value ? "un" : ""}block-input'); value: '${blockInput.value ? "un" : ""}block-input');
blockInput.value = !blockInput.value; blockInput.value = !blockInput.value;
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
} }
@ -503,6 +520,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
bind.sessionRefresh(id: widget.id); bind.sessionRefresh(id: widget.id);
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
} }
@ -523,6 +541,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
// } // }
// }(); // }();
// }, // },
// padding: padding,
// dismissOnClicked: true, // dismissOnClicked: true,
// )); // ));
// } // }
@ -535,6 +554,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
proc: () { proc: () {
widget.ffi.cursorModel.reset(); widget.ffi.cursorModel.reset();
}, },
padding: padding,
dismissOnClicked: true, dismissOnClicked: true,
)); ));
} }
@ -543,14 +563,21 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
} }
List<MenuEntryBase<String>> _getDisplayMenu(dynamic futureData) { List<MenuEntryBase<String>> _getDisplayMenu(dynamic futureData) {
const EdgeInsets padding = EdgeInsets.only(left: 18.0, right: 8.0);
final displayMenu = [ final displayMenu = [
MenuEntryRadios<String>( MenuEntryRadios<String>(
text: translate('Ratio'), text: translate('Ratio'),
optionsGetter: () => [ optionsGetter: () => [
MenuEntryRadioOption( MenuEntryRadioOption(
text: translate('Scale original'), value: 'original'), text: translate('Scale original'),
value: 'original',
dismissOnClicked: true,
),
MenuEntryRadioOption( MenuEntryRadioOption(
text: translate('Scale adaptive'), value: 'adaptive'), text: translate('Scale adaptive'),
value: 'adaptive',
dismissOnClicked: true,
),
], ],
curOptionGetter: () async { curOptionGetter: () async {
return await bind.sessionGetOption( return await bind.sessionGetOption(
@ -561,15 +588,24 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
await bind.sessionPeerOption( await bind.sessionPeerOption(
id: widget.id, name: "view-style", value: newValue); id: widget.id, name: "view-style", value: newValue);
widget.ffi.canvasModel.updateViewStyle(); widget.ffi.canvasModel.updateViewStyle();
}), },
padding: padding,
dismissOnClicked: true,
),
MenuEntryDivider<String>(), MenuEntryDivider<String>(),
MenuEntryRadios<String>( MenuEntryRadios<String>(
text: translate('Scroll Style'), text: translate('Scroll Style'),
optionsGetter: () => [ optionsGetter: () => [
MenuEntryRadioOption( MenuEntryRadioOption(
text: translate('ScrollAuto'), value: 'scrollauto'), text: translate('ScrollAuto'),
value: 'scrollauto',
dismissOnClicked: true,
),
MenuEntryRadioOption( MenuEntryRadioOption(
text: translate('Scrollbar'), value: 'scrollbar'), text: translate('Scrollbar'),
value: 'scrollbar',
dismissOnClicked: true,
),
], ],
curOptionGetter: () async { curOptionGetter: () async {
return await bind.sessionGetOption( return await bind.sessionGetOption(
@ -580,17 +616,29 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
await bind.sessionPeerOption( await bind.sessionPeerOption(
id: widget.id, name: "scroll-style", value: newValue); id: widget.id, name: "scroll-style", value: newValue);
widget.ffi.canvasModel.updateScrollStyle(); widget.ffi.canvasModel.updateScrollStyle();
}), },
padding: padding,
dismissOnClicked: true,
),
MenuEntryDivider<String>(), MenuEntryDivider<String>(),
MenuEntryRadios<String>( MenuEntryRadios<String>(
text: translate('Image Quality'), text: translate('Image Quality'),
optionsGetter: () => [ optionsGetter: () => [
MenuEntryRadioOption( MenuEntryRadioOption(
text: translate('Good image quality'), value: 'best'), text: translate('Good image quality'),
value: 'best',
dismissOnClicked: true,
),
MenuEntryRadioOption( MenuEntryRadioOption(
text: translate('Balanced'), value: 'balanced'), text: translate('Balanced'),
value: 'balanced',
dismissOnClicked: true,
),
MenuEntryRadioOption( MenuEntryRadioOption(
text: translate('Optimize reaction time'), value: 'low'), text: translate('Optimize reaction time'),
value: 'low',
dismissOnClicked: true,
),
MenuEntryRadioOption( MenuEntryRadioOption(
text: translate('Custom'), text: translate('Custom'),
value: 'custom', value: 'custom',
@ -661,7 +709,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality', msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
content, [btnCancel]); content, [btnCancel]);
} }
}), },
padding: padding,
),
MenuEntryDivider<String>(), MenuEntryDivider<String>(),
]; ];
@ -680,14 +730,30 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
text: translate('Codec Preference'), text: translate('Codec Preference'),
optionsGetter: () { optionsGetter: () {
final list = [ final list = [
MenuEntryRadioOption(text: translate('Auto'), value: 'auto'), MenuEntryRadioOption(
MenuEntryRadioOption(text: 'VP9', value: 'vp9'), text: translate('Auto'),
value: 'auto',
dismissOnClicked: true,
),
MenuEntryRadioOption(
text: 'VP9',
value: 'vp9',
dismissOnClicked: true,
),
]; ];
if (codecs[0]) { if (codecs[0]) {
list.add(MenuEntryRadioOption(text: 'H264', value: 'h264')); list.add(MenuEntryRadioOption(
text: 'H264',
value: 'h264',
dismissOnClicked: true,
));
} }
if (codecs[1]) { if (codecs[1]) {
list.add(MenuEntryRadioOption(text: 'H265', value: 'h265')); list.add(MenuEntryRadioOption(
text: 'H265',
value: 'h265',
dismissOnClicked: true,
));
} }
return list; return list;
}, },
@ -700,7 +766,10 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
await bind.sessionPeerOption( await bind.sessionPeerOption(
id: widget.id, name: "codec-preference", value: newValue); id: widget.id, name: "codec-preference", value: newValue);
bind.sessionChangePreferCodec(id: widget.id); bind.sessionChangePreferCodec(id: widget.id);
})); },
padding: padding,
dismissOnClicked: true,
));
} }
} }
@ -708,6 +777,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
displayMenu.add(() { displayMenu.add(() {
final state = ShowRemoteCursorState.find(widget.id); final state = ShowRemoteCursorState.find(widget.id);
return MenuEntrySwitch2<String>( return MenuEntrySwitch2<String>(
switchType: SwitchType.scheckbox,
text: translate('Show remote cursor'), text: translate('Show remote cursor'),
getter: () { getter: () {
return state; return state;
@ -716,11 +786,15 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
state.value = v; state.value = v;
await bind.sessionToggleOption( await bind.sessionToggleOption(
id: widget.id, value: 'show-remote-cursor'); id: widget.id, value: 'show-remote-cursor');
}); },
padding: padding,
dismissOnClicked: true,
);
}()); }());
/// Show quality monitor /// Show quality monitor
displayMenu.add(MenuEntrySwitch<String>( displayMenu.add(MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox,
text: translate('Show quality monitor'), text: translate('Show quality monitor'),
getter: () async { getter: () async {
return bind.sessionGetToggleOptionSync( return bind.sessionGetToggleOptionSync(
@ -730,32 +804,36 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
await bind.sessionToggleOption( await bind.sessionToggleOption(
id: widget.id, value: 'show-quality-monitor'); id: widget.id, value: 'show-quality-monitor');
widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id); widget.ffi.qualityMonitorModel.checkShowQualityMonitor(widget.id);
})); },
padding: padding,
dismissOnClicked: true,
));
final perms = widget.ffi.ffiModel.permissions; final perms = widget.ffi.ffiModel.permissions;
final pi = widget.ffi.ffiModel.pi; final pi = widget.ffi.ffiModel.pi;
if (perms['audio'] != false) { if (perms['audio'] != false) {
displayMenu.add(_createSwitchMenuEntry('Mute', 'disable-audio')); displayMenu
.add(_createSwitchMenuEntry('Mute', 'disable-audio', padding, true));
} }
if (Platform.isWindows && if (Platform.isWindows &&
pi.platform == 'Windows' && pi.platform == 'Windows' &&
perms['file'] != false) { perms['file'] != false) {
displayMenu.add(_createSwitchMenuEntry( displayMenu.add(_createSwitchMenuEntry(
'Allow file copy and paste', 'enable-file-transfer')); 'Allow file copy and paste', 'enable-file-transfer', padding, true));
} }
if (perms['keyboard'] != false) { if (perms['keyboard'] != false) {
if (perms['clipboard'] != false) { if (perms['clipboard'] != false) {
displayMenu.add( displayMenu.add(_createSwitchMenuEntry(
_createSwitchMenuEntry('Disable clipboard', 'disable-clipboard')); 'Disable clipboard', 'disable-clipboard', padding, true));
} }
displayMenu.add(_createSwitchMenuEntry( displayMenu.add(_createSwitchMenuEntry(
'Lock after session end', 'lock-after-session-end')); 'Lock after session end', 'lock-after-session-end', padding, true));
if (pi.platform == 'Windows') { if (pi.platform == 'Windows') {
displayMenu.add(MenuEntrySwitch2<String>( displayMenu.add(MenuEntrySwitch2<String>(
dismissOnClicked: true, switchType: SwitchType.scheckbox,
text: translate('Privacy mode'), text: translate('Privacy mode'),
getter: () { getter: () {
return PrivacyModeState.find(widget.id); return PrivacyModeState.find(widget.id);
@ -763,7 +841,10 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
setter: (bool v) async { setter: (bool v) async {
await bind.sessionToggleOption( await bind.sessionToggleOption(
id: widget.id, value: 'privacy-mode'); id: widget.id, value: 'privacy-mode');
})); },
padding: padding,
dismissOnClicked: true,
));
} }
} }
return displayMenu; return displayMenu;
@ -774,8 +855,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
MenuEntryRadios<String>( MenuEntryRadios<String>(
text: translate('Ratio'), text: translate('Ratio'),
optionsGetter: () => [ optionsGetter: () => [
MenuEntryRadioOption( MenuEntryRadioOption(text: translate('Legacy mode'), value: 'legacy'),
text: translate('Legacy mode'), value: 'legacy'),
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'), MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
], ],
curOptionGetter: () async { curOptionGetter: () async {
@ -785,21 +865,27 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
await bind.sessionSetKeyboardMode( await bind.sessionSetKeyboardMode(
id: widget.id, keyboardMode: newValue); id: widget.id, keyboardMode: newValue);
widget.ffi.canvasModel.updateViewStyle(); widget.ffi.canvasModel.updateViewStyle();
}) },
)
]; ];
return keyboardMenu; return keyboardMenu;
} }
MenuEntrySwitch<String> _createSwitchMenuEntry(String text, String option) { MenuEntrySwitch<String> _createSwitchMenuEntry(
String text, String option, EdgeInsets? padding, bool dismissOnClicked) {
return MenuEntrySwitch<String>( return MenuEntrySwitch<String>(
switchType: SwitchType.scheckbox,
text: translate(text), text: translate(text),
getter: () async { getter: () async {
return bind.sessionGetToggleOptionSync(id: widget.id, arg: option); return bind.sessionGetToggleOptionSync(id: widget.id, arg: option);
}, },
setter: (bool v) async { setter: (bool v) async {
await bind.sessionToggleOption(id: widget.id, value: option); await bind.sessionToggleOption(id: widget.id, value: option);
}); },
padding: padding,
dismissOnClicked: dismissOnClicked,
);
} }
} }