mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-08 21:21:26 +03:00
flutter_desktop: custom image quality
Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
@@ -97,6 +97,9 @@ class MenuConfig {
|
||||
}
|
||||
|
||||
abstract class MenuEntryBase<T> {
|
||||
bool dismissOnClicked;
|
||||
|
||||
MenuEntryBase({this.dismissOnClicked = false});
|
||||
List<mod_menu.PopupMenuEntry<T>> build(BuildContext context, MenuConfig conf);
|
||||
}
|
||||
|
||||
@@ -112,9 +115,19 @@ class MenuEntryDivider<T> extends MenuEntryBase<T> {
|
||||
}
|
||||
}
|
||||
|
||||
typedef RadioOptionsGetter = List<Tuple2<String, String>> Function();
|
||||
class MenuEntryRadioOption {
|
||||
String text;
|
||||
String value;
|
||||
bool dismissOnClicked;
|
||||
|
||||
MenuEntryRadioOption(
|
||||
{required this.text, required this.value, this.dismissOnClicked = false});
|
||||
}
|
||||
|
||||
typedef RadioOptionsGetter = List<MenuEntryRadioOption> Function();
|
||||
typedef RadioCurOptionGetter = Future<String> Function();
|
||||
typedef RadioOptionSetter = Future<void> Function(String);
|
||||
typedef RadioOptionSetter = Future<void> Function(
|
||||
String oldValue, String newValue);
|
||||
|
||||
class MenuEntryRadioUtils<T> {}
|
||||
|
||||
@@ -129,24 +142,28 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
||||
{required this.text,
|
||||
required this.optionsGetter,
|
||||
required this.curOptionGetter,
|
||||
required this.optionSetter}) {
|
||||
required this.optionSetter,
|
||||
dismissOnClicked = false})
|
||||
: super(dismissOnClicked: dismissOnClicked) {
|
||||
() async {
|
||||
_curOption.value = await curOptionGetter();
|
||||
}();
|
||||
}
|
||||
|
||||
List<Tuple2<String, String>> get options => optionsGetter();
|
||||
List<MenuEntryRadioOption> get options => optionsGetter();
|
||||
RxString get curOption => _curOption;
|
||||
setOption(String option) async {
|
||||
await optionSetter(option);
|
||||
final opt = await curOptionGetter();
|
||||
if (_curOption.value != opt) {
|
||||
_curOption.value = opt;
|
||||
await optionSetter(_curOption.value, option);
|
||||
if (_curOption.value != option) {
|
||||
final opt = await curOptionGetter();
|
||||
if (_curOption.value != opt) {
|
||||
_curOption.value = opt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod_menu.PopupMenuEntry<T> _buildMenuItem(
|
||||
BuildContext context, MenuConfig conf, Tuple2<String, String> opt) {
|
||||
BuildContext context, MenuConfig conf, MenuEntryRadioOption opt) {
|
||||
return mod_menu.PopupMenuItem(
|
||||
padding: EdgeInsets.zero,
|
||||
height: conf.height,
|
||||
@@ -157,7 +174,7 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
opt.item1,
|
||||
opt.text,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
@@ -169,7 +186,7 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
||||
child: SizedBox(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
child: Obx(() => opt.item2 == curOption.value
|
||||
child: Obx(() => opt.value == curOption.value
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: conf.commonColor,
|
||||
@@ -180,9 +197,10 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
if (opt.item2 != curOption.value) {
|
||||
setOption(opt.item2);
|
||||
if (opt.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
setOption(opt.value);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -206,24 +224,28 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||
{required this.text,
|
||||
required this.optionsGetter,
|
||||
required this.curOptionGetter,
|
||||
required this.optionSetter}) {
|
||||
required this.optionSetter,
|
||||
dismissOnClicked = false})
|
||||
: super(dismissOnClicked: dismissOnClicked) {
|
||||
() async {
|
||||
_curOption.value = await curOptionGetter();
|
||||
}();
|
||||
}
|
||||
|
||||
List<Tuple2<String, String>> get options => optionsGetter();
|
||||
List<MenuEntryRadioOption> get options => optionsGetter();
|
||||
RxString get curOption => _curOption;
|
||||
setOption(String option) async {
|
||||
await optionSetter(option);
|
||||
final opt = await curOptionGetter();
|
||||
if (_curOption.value != opt) {
|
||||
_curOption.value = opt;
|
||||
await optionSetter(_curOption.value, option);
|
||||
if (_curOption.value != option) {
|
||||
final opt = await curOptionGetter();
|
||||
if (_curOption.value != opt) {
|
||||
_curOption.value = opt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod_menu.PopupMenuEntry<T> _buildSecondMenu(
|
||||
BuildContext context, MenuConfig conf, Tuple2<String, String> opt) {
|
||||
BuildContext context, MenuConfig conf, MenuEntryRadioOption opt) {
|
||||
return mod_menu.PopupMenuItem(
|
||||
padding: EdgeInsets.zero,
|
||||
height: conf.height,
|
||||
@@ -234,7 +256,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
opt.item1,
|
||||
opt.text,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: MenuConfig.fontSize,
|
||||
@@ -246,7 +268,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||
child: SizedBox(
|
||||
width: 20.0,
|
||||
height: 20.0,
|
||||
child: Obx(() => opt.item2 == curOption.value
|
||||
child: Obx(() => opt.value == curOption.value
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: conf.commonColor,
|
||||
@@ -257,9 +279,10 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
if (opt.item2 != curOption.value) {
|
||||
setOption(opt.item2);
|
||||
if (opt.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
setOption(opt.value);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -303,7 +326,8 @@ typedef SwitchSetter = Future<void> Function(bool);
|
||||
abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
final String text;
|
||||
|
||||
MenuEntrySwitchBase({required this.text});
|
||||
MenuEntrySwitchBase({required this.text, required dismissOnClicked})
|
||||
: super(dismissOnClicked: dismissOnClicked);
|
||||
|
||||
RxBool get curOption;
|
||||
Future<void> setOption(bool option);
|
||||
@@ -333,11 +357,20 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
|
||||
alignment: Alignment.centerRight,
|
||||
child: Obx(() => Switch(
|
||||
value: curOption.value,
|
||||
onChanged: (v) => setOption(v),
|
||||
onChanged: (v) {
|
||||
if (super.dismissOnClicked &&
|
||||
Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
setOption(v);
|
||||
},
|
||||
)),
|
||||
))
|
||||
])),
|
||||
onPressed: () {
|
||||
if (super.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
setOption(!curOption.value);
|
||||
},
|
||||
),
|
||||
@@ -352,8 +385,11 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
|
||||
final RxBool _curOption = false.obs;
|
||||
|
||||
MenuEntrySwitch(
|
||||
{required String text, required this.getter, required this.setter})
|
||||
: super(text: text) {
|
||||
{required String text,
|
||||
required this.getter,
|
||||
required this.setter,
|
||||
dismissOnClicked = false})
|
||||
: super(text: text, dismissOnClicked: dismissOnClicked) {
|
||||
() async {
|
||||
_curOption.value = await getter();
|
||||
}();
|
||||
@@ -379,8 +415,11 @@ class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
|
||||
final SwitchSetter setter;
|
||||
|
||||
MenuEntrySwitch2(
|
||||
{required String text, required this.getter, required this.setter})
|
||||
: super(text: text);
|
||||
{required String text,
|
||||
required this.getter,
|
||||
required this.setter,
|
||||
dismissOnClicked = false})
|
||||
: super(text: text, dismissOnClicked: dismissOnClicked);
|
||||
|
||||
@override
|
||||
RxBool get curOption => getter();
|
||||
@@ -394,10 +433,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
|
||||
final String text;
|
||||
final List<MenuEntryBase<T>> entries;
|
||||
|
||||
MenuEntrySubMenu({
|
||||
required this.text,
|
||||
required this.entries,
|
||||
});
|
||||
MenuEntrySubMenu({required this.text, required this.entries});
|
||||
|
||||
@override
|
||||
List<mod_menu.PopupMenuEntry<T>> build(
|
||||
@@ -438,10 +474,11 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||
final Widget Function(TextStyle? style) childBuilder;
|
||||
Function() proc;
|
||||
|
||||
MenuEntryButton({
|
||||
required this.childBuilder,
|
||||
required this.proc,
|
||||
});
|
||||
MenuEntryButton(
|
||||
{required this.childBuilder,
|
||||
required this.proc,
|
||||
dismissOnClicked = false})
|
||||
: super(dismissOnClicked: dismissOnClicked);
|
||||
|
||||
@override
|
||||
List<mod_menu.PopupMenuEntry<T>> build(
|
||||
@@ -461,6 +498,9 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
||||
fontWeight: FontWeight.normal),
|
||||
)),
|
||||
onPressed: () {
|
||||
if (super.dismissOnClicked && Navigator.canPop(context)) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
proc();
|
||||
},
|
||||
),
|
||||
|
||||
@@ -290,9 +290,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
Navigator.pop(context);
|
||||
bind.sessionRefresh(id: widget.id);
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
displayMenu.add(MenuEntryButton<String>(
|
||||
@@ -301,9 +301,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
Navigator.pop(context);
|
||||
showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
|
||||
if (!isWebDesktop) {
|
||||
@@ -314,7 +314,6 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
Navigator.pop(context);
|
||||
() async {
|
||||
ClipboardData? data =
|
||||
await Clipboard.getData(Clipboard.kTextPlain);
|
||||
@@ -323,6 +322,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
}
|
||||
}();
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -332,9 +332,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
Navigator.pop(context);
|
||||
widget.ffi.cursorModel.reset();
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -346,9 +346,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
Navigator.pop(context);
|
||||
bind.sessionCtrlAltDel(id: widget.id);
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -358,9 +358,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
Navigator.pop(context);
|
||||
bind.sessionLockScreen(id: widget.id);
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
|
||||
if (pi.platform == 'Windows') {
|
||||
@@ -371,13 +371,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
style: style,
|
||||
)),
|
||||
proc: () {
|
||||
Navigator.pop(context);
|
||||
RxBool blockInput = BlockInputState.find(widget.id);
|
||||
bind.sessionToggleOption(
|
||||
id: widget.id,
|
||||
value: '${blockInput.value ? "un" : ""}block-input');
|
||||
blockInput.value = !blockInput.value;
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -392,9 +392,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
Navigator.pop(context);
|
||||
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
|
||||
},
|
||||
dismissOnClicked: true,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -406,44 +406,54 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
MenuEntryRadios<String>(
|
||||
text: translate('Ratio'),
|
||||
optionsGetter: () => [
|
||||
Tuple2<String, String>(translate('Scale original'), 'original'),
|
||||
Tuple2<String, String>(translate('Scale adaptive'), 'adaptive'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale original'), value: 'original'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scale adaptive'), value: 'adaptive'),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetOption(
|
||||
id: widget.id, arg: 'view-style') ??
|
||||
'adaptive';
|
||||
},
|
||||
optionSetter: (String v) async {
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionPeerOption(
|
||||
id: widget.id, name: "view-style", value: v);
|
||||
id: widget.id, name: "view-style", value: newValue);
|
||||
widget.ffi.canvasModel.updateViewStyle();
|
||||
}),
|
||||
MenuEntryDivider<String>(),
|
||||
MenuEntryRadios<String>(
|
||||
text: translate('Scroll Style'),
|
||||
optionsGetter: () => [
|
||||
Tuple2<String, String>(translate('ScrollAuto'), 'scrollauto'),
|
||||
Tuple2<String, String>(translate('Scrollbar'), 'scrollbar'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('ScrollAuto'), value: 'scrollauto'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Scrollbar'), value: 'scrollbar'),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
return await bind.sessionGetOption(
|
||||
id: widget.id, arg: 'scroll-style') ??
|
||||
'';
|
||||
},
|
||||
optionSetter: (String v) async {
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
await bind.sessionPeerOption(
|
||||
id: widget.id, name: "scroll-style", value: v);
|
||||
id: widget.id, name: "scroll-style", value: newValue);
|
||||
widget.ffi.canvasModel.updateScrollStyle();
|
||||
}),
|
||||
MenuEntryDivider<String>(),
|
||||
MenuEntryRadios<String>(
|
||||
text: translate('Image Quality'),
|
||||
optionsGetter: () => [
|
||||
Tuple2<String, String>(translate('Good image quality'), 'best'),
|
||||
Tuple2<String, String>(translate('Balanced'), 'balanced'),
|
||||
Tuple2<String, String>(
|
||||
translate('Optimize reaction time'), 'low'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Good image quality'), value: 'best'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Balanced'), value: 'balanced'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Optimize reaction time'), value: 'low'),
|
||||
MenuEntryRadioOption(
|
||||
text: translate('Custom'),
|
||||
value: 'custom',
|
||||
dismissOnClicked: true),
|
||||
],
|
||||
curOptionGetter: () async {
|
||||
String quality =
|
||||
@@ -451,8 +461,43 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
|
||||
if (quality == '') quality = 'balanced';
|
||||
return quality;
|
||||
},
|
||||
optionSetter: (String v) async {
|
||||
await bind.sessionSetImageQuality(id: widget.id, value: v);
|
||||
optionSetter: (String oldValue, String newValue) async {
|
||||
if (oldValue != newValue) {
|
||||
await bind.sessionSetImageQuality(id: widget.id, value: newValue);
|
||||
}
|
||||
|
||||
if (newValue == 'custom') {
|
||||
final btnCancel = getMsgBoxButton(translate('Cancel'), () {
|
||||
widget.ffi.dialogManager.dismissAll();
|
||||
});
|
||||
final quality =
|
||||
await bind.sessionGetCustomImageQuality(id: widget.id);
|
||||
final double initValue = quality != null && quality.isNotEmpty
|
||||
? quality[0].toDouble()
|
||||
: 50.0;
|
||||
// final slider = _ImageCustomQualitySlider(
|
||||
// id: widget.id, v: RxDouble(initValue));
|
||||
final RxDouble sliderValue = RxDouble(initValue);
|
||||
final slider = Obx(() => Slider(
|
||||
value: sliderValue.value,
|
||||
max: 100,
|
||||
label: sliderValue.value.round().toString(),
|
||||
onChanged: (double value) {
|
||||
() async {
|
||||
await bind.sessionSetCustomImageQuality(
|
||||
id: widget.id, value: value.toInt());
|
||||
final quality = await bind.sessionGetCustomImageQuality(
|
||||
id: widget.id);
|
||||
sliderValue.value =
|
||||
quality != null && quality.isNotEmpty
|
||||
? quality[0].toDouble()
|
||||
: 50.0;
|
||||
}();
|
||||
},
|
||||
));
|
||||
msgBoxCommon(widget.ffi.dialogManager, 'Custom Image Quality',
|
||||
slider, [btnCancel]);
|
||||
}
|
||||
}),
|
||||
MenuEntryDivider<String>(),
|
||||
MenuEntrySwitch<String>(
|
||||
|
||||
Reference in New Issue
Block a user