flutter_desktop: custom image quality

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2022-08-31 18:41:55 +08:00
parent 59f0ffa82f
commit 4b9805b0f3
8 changed files with 240 additions and 121 deletions

View File

@ -427,7 +427,45 @@ class CustomAlertDialog extends StatelessWidget {
void msgBox( void msgBox(
String type, String title, String text, OverlayDialogManager dialogManager, String type, String title, String text, OverlayDialogManager dialogManager,
{bool? hasCancel}) { {bool? hasCancel}) {
var wrap = (String text, void Function() onPressed) => ButtonTheme( dialogManager.dismissAll();
List<Widget> buttons = [];
if (type != "connecting" && type != "success" && !type.contains("nook")) {
buttons.insert(
0,
getMsgBoxButton(translate('OK'), () {
dialogManager.dismissAll();
// https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263
if (!type.contains("custom")) {
closeConnection();
}
}));
}
hasCancel ??= !type.contains("error") &&
!type.contains("nocancel") &&
type != "restarting";
if (hasCancel) {
buttons.insert(
0,
getMsgBoxButton(translate('Cancel'), () {
dialogManager.dismissAll();
}));
}
// TODO: test this button
if (type.contains("hasclose")) {
buttons.insert(
0,
getMsgBoxButton(translate('Close'), () {
dialogManager.dismissAll();
}));
}
dialogManager.show((setState, close) => CustomAlertDialog(
title: Text(translate(title), style: TextStyle(fontSize: 21)),
content: Text(translate(text), style: TextStyle(fontSize: 15)),
actions: buttons));
}
Widget getMsgBoxButton(String text, void Function() onPressed) {
return ButtonTheme(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
//limits the touch area to the button area //limits the touch area to the button area
@ -439,44 +477,14 @@ void msgBox(
onPressed: onPressed, onPressed: onPressed,
child: child:
Text(translate(text), style: TextStyle(color: MyTheme.accent)))); Text(translate(text), style: TextStyle(color: MyTheme.accent))));
}
void msgBoxCommon(OverlayDialogManager dialogManager, String title,
Widget content, List<Widget> buttons) {
dialogManager.dismissAll(); dialogManager.dismissAll();
List<Widget> buttons = [];
if (type != "connecting" && type != "success" && type.indexOf("nook") < 0) {
buttons.insert(
0,
wrap(translate('OK'), () {
dialogManager.dismissAll();
// https://github.com/fufesou/rustdesk/blob/5e9a31340b899822090a3731769ae79c6bf5f3e5/src/ui/common.tis#L263
if (type.indexOf("custom") < 0) {
closeConnection();
}
}));
}
if (hasCancel == null) {
// hasCancel = type != 'error';
hasCancel = type.indexOf("error") < 0 &&
type.indexOf("nocancel") < 0 &&
type != "restarting";
}
if (hasCancel) {
buttons.insert(
0,
wrap(translate('Cancel'), () {
dialogManager.dismissAll();
}));
}
// TODO: test this button
if (type.indexOf("hasclose") >= 0) {
buttons.insert(
0,
wrap(translate('Close'), () {
dialogManager.dismissAll();
}));
}
dialogManager.show((setState, close) => CustomAlertDialog( dialogManager.show((setState, close) => CustomAlertDialog(
title: Text(translate(title), style: TextStyle(fontSize: 21)), title: Text(translate(title), style: TextStyle(fontSize: 21)),
content: Text(translate(text), style: TextStyle(fontSize: 15)), content: content,
actions: buttons)); actions: buttons));
} }
@ -495,13 +503,13 @@ const G = M * K;
String readableFileSize(double size) { String readableFileSize(double size) {
if (size < K) { if (size < K) {
return size.toStringAsFixed(2) + " B"; return "${size.toStringAsFixed(2)} B";
} else if (size < M) { } else if (size < M) {
return (size / K).toStringAsFixed(2) + " KB"; return "${(size / K).toStringAsFixed(2)} KB";
} else if (size < G) { } else if (size < G) {
return (size / M).toStringAsFixed(2) + " MB"; return "${(size / M).toStringAsFixed(2)} MB";
} else { } else {
return (size / G).toStringAsFixed(2) + " GB"; return "${(size / G).toStringAsFixed(2)} GB";
} }
} }

View File

@ -8,7 +8,10 @@ const String kAppTypeDesktopPortForward = "port forward";
const String kTabLabelHomePage = "Home"; const String kTabLabelHomePage = "Home";
const String kTabLabelSettingPage = "Settings"; const String kTabLabelSettingPage = "Settings";
const int kDefaultDisplayWidth = 1280; const int kMobileDefaultDisplayWidth = 720;
const int kDefaultDisplayHeight = 720; const int kMobileDefaultDisplayHeight = 1280;
const int kDesktopDefaultDisplayWidth = 1080;
const int kDesktopDefaultDisplayHeight = 720;
const kInvalidValueStr = "InvalidValueStr"; const kInvalidValueStr = "InvalidValueStr";

View File

@ -61,6 +61,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
final args = jsonDecode(call.arguments); final args = jsonDecode(call.arguments);
final id = args['id']; final id = args['id'];
window_on_top(windowId()); window_on_top(windowId());
ConnectionTypeState.init(id);
tabController.add(TabInfo( tabController.add(TabInfo(
key: id, key: id,
label: id, label: id,
@ -108,7 +109,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
}, },
tabBuilder: (key, icon, label, themeConf) => Obx(() { tabBuilder: (key, icon, label, themeConf) => Obx(() {
final connectionType = ConnectionTypeState.find(key); final connectionType = ConnectionTypeState.find(key);
if (!ConnectionTypeState.find(key).isValid()) { if (!connectionType.isValid()) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [

View File

@ -689,11 +689,11 @@ class ImagePaint extends StatelessWidget {
width: c.getDisplayWidth() * s, width: c.getDisplayWidth() * s,
height: c.getDisplayHeight() * s, height: c.getDisplayHeight() * s,
child: CustomPaint( child: CustomPaint(
painter: new ImagePainter(image: m.image, x: 0, y: 0, scale: s), painter: ImagePainter(image: m.image, x: 0, y: 0, scale: s),
)); ));
return Center( return Center(
child: NotificationListener<ScrollNotification>( child: NotificationListener<ScrollNotification>(
onNotification: (_notification) { onNotification: (notification) {
final percentX = _horizontal.position.extentBefore / final percentX = _horizontal.position.extentBefore /
(_horizontal.position.extentBefore + (_horizontal.position.extentBefore +
_horizontal.position.extentInside + _horizontal.position.extentInside +
@ -716,8 +716,8 @@ class ImagePaint extends StatelessWidget {
width: c.size.width, width: c.size.width,
height: c.size.height, height: c.size.height,
child: CustomPaint( child: CustomPaint(
painter: new ImagePainter( painter:
image: m.image, x: c.x / s, y: c.y / s, scale: s), ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s),
)); ));
return _buildListener(imageWidget); return _buildListener(imageWidget);
} }
@ -771,7 +771,7 @@ class CursorPaint extends StatelessWidget {
// final adjust = m.adjustForKeyboard(); // final adjust = m.adjustForKeyboard();
var s = c.scale; var s = c.scale;
return CustomPaint( return CustomPaint(
painter: new ImagePainter( painter: ImagePainter(
image: m.image, image: m.image,
x: m.x * s - m.hotx + c.x, x: m.x * s - m.hotx + c.x,
y: m.y * s - m.hoty + c.y, y: m.y * s - m.hoty + c.y,
@ -796,15 +796,16 @@ class ImagePainter extends CustomPainter {
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
if (image == null) return; if (image == null) return;
if (x.isNaN || y.isNaN) return;
canvas.scale(scale, scale); canvas.scale(scale, scale);
// https://github.com/flutter/flutter/issues/76187#issuecomment-784628161 // https://github.com/flutter/flutter/issues/76187#issuecomment-784628161
// https://api.flutter-io.cn/flutter/dart-ui/FilterQuality.html // https://api.flutter-io.cn/flutter/dart-ui/FilterQuality.html
var paint = new Paint(); var paint = Paint();
paint.filterQuality = FilterQuality.medium; paint.filterQuality = FilterQuality.medium;
if (scale > 10.00000) { if (scale > 10.00000) {
paint.filterQuality = FilterQuality.high; paint.filterQuality = FilterQuality.high;
} }
canvas.drawImage(image!, new Offset(x, y), paint); canvas.drawImage(image!, Offset(x, y), paint);
} }
@override @override

View File

@ -97,6 +97,9 @@ class MenuConfig {
} }
abstract class MenuEntryBase<T> { abstract class MenuEntryBase<T> {
bool dismissOnClicked;
MenuEntryBase({this.dismissOnClicked = false});
List<mod_menu.PopupMenuEntry<T>> build(BuildContext context, MenuConfig conf); 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 RadioCurOptionGetter = Future<String> Function();
typedef RadioOptionSetter = Future<void> Function(String); typedef RadioOptionSetter = Future<void> Function(
String oldValue, String newValue);
class MenuEntryRadioUtils<T> {} class MenuEntryRadioUtils<T> {}
@ -129,24 +142,28 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
{required this.text, {required this.text,
required this.optionsGetter, required this.optionsGetter,
required this.curOptionGetter, required this.curOptionGetter,
required this.optionSetter}) { required this.optionSetter,
dismissOnClicked = false})
: super(dismissOnClicked: dismissOnClicked) {
() async { () async {
_curOption.value = await curOptionGetter(); _curOption.value = await curOptionGetter();
}(); }();
} }
List<Tuple2<String, String>> get options => optionsGetter(); List<MenuEntryRadioOption> get options => optionsGetter();
RxString get curOption => _curOption; RxString get curOption => _curOption;
setOption(String option) async { setOption(String option) async {
await optionSetter(option); await optionSetter(_curOption.value, option);
if (_curOption.value != option) {
final opt = await curOptionGetter(); final opt = await curOptionGetter();
if (_curOption.value != opt) { if (_curOption.value != opt) {
_curOption.value = opt; _curOption.value = opt;
} }
} }
}
mod_menu.PopupMenuEntry<T> _buildMenuItem( mod_menu.PopupMenuEntry<T> _buildMenuItem(
BuildContext context, MenuConfig conf, Tuple2<String, String> opt) { BuildContext context, MenuConfig conf, MenuEntryRadioOption opt) {
return mod_menu.PopupMenuItem( return mod_menu.PopupMenuItem(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
height: conf.height, height: conf.height,
@ -157,7 +174,7 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
child: Row( child: Row(
children: [ children: [
Text( Text(
opt.item1, opt.text,
style: const TextStyle( style: const TextStyle(
color: Colors.black, color: Colors.black,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
@ -169,7 +186,7 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
child: SizedBox( child: SizedBox(
width: 20.0, width: 20.0,
height: 20.0, height: 20.0,
child: Obx(() => opt.item2 == curOption.value child: Obx(() => opt.value == curOption.value
? Icon( ? Icon(
Icons.check, Icons.check,
color: conf.commonColor, color: conf.commonColor,
@ -180,9 +197,10 @@ class MenuEntryRadios<T> extends MenuEntryBase<T> {
), ),
), ),
onPressed: () { onPressed: () {
if (opt.item2 != curOption.value) { if (opt.dismissOnClicked && Navigator.canPop(context)) {
setOption(opt.item2); Navigator.pop(context);
} }
setOption(opt.value);
}, },
), ),
); );
@ -206,24 +224,28 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
{required this.text, {required this.text,
required this.optionsGetter, required this.optionsGetter,
required this.curOptionGetter, required this.curOptionGetter,
required this.optionSetter}) { required this.optionSetter,
dismissOnClicked = false})
: super(dismissOnClicked: dismissOnClicked) {
() async { () async {
_curOption.value = await curOptionGetter(); _curOption.value = await curOptionGetter();
}(); }();
} }
List<Tuple2<String, String>> get options => optionsGetter(); List<MenuEntryRadioOption> get options => optionsGetter();
RxString get curOption => _curOption; RxString get curOption => _curOption;
setOption(String option) async { setOption(String option) async {
await optionSetter(option); await optionSetter(_curOption.value, option);
if (_curOption.value != option) {
final opt = await curOptionGetter(); final opt = await curOptionGetter();
if (_curOption.value != opt) { if (_curOption.value != opt) {
_curOption.value = opt; _curOption.value = opt;
} }
} }
}
mod_menu.PopupMenuEntry<T> _buildSecondMenu( mod_menu.PopupMenuEntry<T> _buildSecondMenu(
BuildContext context, MenuConfig conf, Tuple2<String, String> opt) { BuildContext context, MenuConfig conf, MenuEntryRadioOption opt) {
return mod_menu.PopupMenuItem( return mod_menu.PopupMenuItem(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
height: conf.height, height: conf.height,
@ -234,7 +256,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
child: Row( child: Row(
children: [ children: [
Text( Text(
opt.item1, opt.text,
style: const TextStyle( style: const TextStyle(
color: Colors.black, color: Colors.black,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
@ -246,7 +268,7 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
child: SizedBox( child: SizedBox(
width: 20.0, width: 20.0,
height: 20.0, height: 20.0,
child: Obx(() => opt.item2 == curOption.value child: Obx(() => opt.value == curOption.value
? Icon( ? Icon(
Icons.check, Icons.check,
color: conf.commonColor, color: conf.commonColor,
@ -257,9 +279,10 @@ class MenuEntrySubRadios<T> extends MenuEntryBase<T> {
), ),
), ),
onPressed: () { onPressed: () {
if (opt.item2 != curOption.value) { if (opt.dismissOnClicked && Navigator.canPop(context)) {
setOption(opt.item2); Navigator.pop(context);
} }
setOption(opt.value);
}, },
), ),
); );
@ -303,7 +326,8 @@ typedef SwitchSetter = Future<void> Function(bool);
abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> { abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
final String text; final String text;
MenuEntrySwitchBase({required this.text}); MenuEntrySwitchBase({required this.text, required dismissOnClicked})
: super(dismissOnClicked: dismissOnClicked);
RxBool get curOption; RxBool get curOption;
Future<void> setOption(bool option); Future<void> setOption(bool option);
@ -333,11 +357,20 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Obx(() => Switch( child: Obx(() => Switch(
value: curOption.value, value: curOption.value,
onChanged: (v) => setOption(v), onChanged: (v) {
if (super.dismissOnClicked &&
Navigator.canPop(context)) {
Navigator.pop(context);
}
setOption(v);
},
)), )),
)) ))
])), ])),
onPressed: () { onPressed: () {
if (super.dismissOnClicked && Navigator.canPop(context)) {
Navigator.pop(context);
}
setOption(!curOption.value); setOption(!curOption.value);
}, },
), ),
@ -352,8 +385,11 @@ class MenuEntrySwitch<T> extends MenuEntrySwitchBase<T> {
final RxBool _curOption = false.obs; final RxBool _curOption = false.obs;
MenuEntrySwitch( MenuEntrySwitch(
{required String text, required this.getter, required this.setter}) {required String text,
: super(text: text) { required this.getter,
required this.setter,
dismissOnClicked = false})
: super(text: text, dismissOnClicked: dismissOnClicked) {
() async { () async {
_curOption.value = await getter(); _curOption.value = await getter();
}(); }();
@ -379,8 +415,11 @@ class MenuEntrySwitch2<T> extends MenuEntrySwitchBase<T> {
final SwitchSetter setter; final SwitchSetter setter;
MenuEntrySwitch2( MenuEntrySwitch2(
{required String text, required this.getter, required this.setter}) {required String text,
: super(text: text); required this.getter,
required this.setter,
dismissOnClicked = false})
: super(text: text, dismissOnClicked: dismissOnClicked);
@override @override
RxBool get curOption => getter(); RxBool get curOption => getter();
@ -394,10 +433,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
final String text; final String text;
final List<MenuEntryBase<T>> entries; final List<MenuEntryBase<T>> entries;
MenuEntrySubMenu({ MenuEntrySubMenu({required this.text, required this.entries});
required this.text,
required this.entries,
});
@override @override
List<mod_menu.PopupMenuEntry<T>> build( List<mod_menu.PopupMenuEntry<T>> build(
@ -438,10 +474,11 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
final Widget Function(TextStyle? style) childBuilder; final Widget Function(TextStyle? style) childBuilder;
Function() proc; Function() proc;
MenuEntryButton({ MenuEntryButton(
required this.childBuilder, {required this.childBuilder,
required this.proc, required this.proc,
}); dismissOnClicked = false})
: super(dismissOnClicked: dismissOnClicked);
@override @override
List<mod_menu.PopupMenuEntry<T>> build( List<mod_menu.PopupMenuEntry<T>> build(
@ -461,6 +498,9 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
fontWeight: FontWeight.normal), fontWeight: FontWeight.normal),
)), )),
onPressed: () { onPressed: () {
if (super.dismissOnClicked && Navigator.canPop(context)) {
Navigator.pop(context);
}
proc(); proc();
}, },
), ),

View File

@ -290,9 +290,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
style: style, style: style,
), ),
proc: () { proc: () {
Navigator.pop(context);
bind.sessionRefresh(id: widget.id); bind.sessionRefresh(id: widget.id);
}, },
dismissOnClicked: true,
)); ));
} }
displayMenu.add(MenuEntryButton<String>( displayMenu.add(MenuEntryButton<String>(
@ -301,9 +301,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
style: style, style: style,
), ),
proc: () { proc: () {
Navigator.pop(context);
showSetOSPassword(widget.id, false, widget.ffi.dialogManager); showSetOSPassword(widget.id, false, widget.ffi.dialogManager);
}, },
dismissOnClicked: true,
)); ));
if (!isWebDesktop) { if (!isWebDesktop) {
@ -314,7 +314,6 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
style: style, style: style,
), ),
proc: () { proc: () {
Navigator.pop(context);
() async { () async {
ClipboardData? data = ClipboardData? data =
await Clipboard.getData(Clipboard.kTextPlain); 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, style: style,
), ),
proc: () { proc: () {
Navigator.pop(context);
widget.ffi.cursorModel.reset(); widget.ffi.cursorModel.reset();
}, },
dismissOnClicked: true,
)); ));
} }
@ -346,9 +346,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
style: style, style: style,
), ),
proc: () { proc: () {
Navigator.pop(context);
bind.sessionCtrlAltDel(id: widget.id); bind.sessionCtrlAltDel(id: widget.id);
}, },
dismissOnClicked: true,
)); ));
} }
@ -358,9 +358,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
style: style, style: style,
), ),
proc: () { proc: () {
Navigator.pop(context);
bind.sessionLockScreen(id: widget.id); bind.sessionLockScreen(id: widget.id);
}, },
dismissOnClicked: true,
)); ));
if (pi.platform == 'Windows') { if (pi.platform == 'Windows') {
@ -371,13 +371,13 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
style: style, style: style,
)), )),
proc: () { proc: () {
Navigator.pop(context);
RxBool blockInput = BlockInputState.find(widget.id); RxBool blockInput = BlockInputState.find(widget.id);
bind.sessionToggleOption( bind.sessionToggleOption(
id: widget.id, id: widget.id,
value: '${blockInput.value ? "un" : ""}block-input'); value: '${blockInput.value ? "un" : ""}block-input');
blockInput.value = !blockInput.value; blockInput.value = !blockInput.value;
}, },
dismissOnClicked: true,
)); ));
} }
} }
@ -392,9 +392,9 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
style: style, style: style,
), ),
proc: () { proc: () {
Navigator.pop(context);
showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager); showRestartRemoteDevice(pi, widget.id, gFFI.dialogManager);
}, },
dismissOnClicked: true,
)); ));
} }
@ -406,44 +406,54 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
MenuEntryRadios<String>( MenuEntryRadios<String>(
text: translate('Ratio'), text: translate('Ratio'),
optionsGetter: () => [ optionsGetter: () => [
Tuple2<String, String>(translate('Scale original'), 'original'), MenuEntryRadioOption(
Tuple2<String, String>(translate('Scale adaptive'), 'adaptive'), text: translate('Scale original'), value: 'original'),
MenuEntryRadioOption(
text: translate('Scale adaptive'), value: 'adaptive'),
], ],
curOptionGetter: () async { curOptionGetter: () async {
return await bind.sessionGetOption( return await bind.sessionGetOption(
id: widget.id, arg: 'view-style') ?? id: widget.id, arg: 'view-style') ??
'adaptive'; 'adaptive';
}, },
optionSetter: (String v) async { optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption( await bind.sessionPeerOption(
id: widget.id, name: "view-style", value: v); id: widget.id, name: "view-style", value: newValue);
widget.ffi.canvasModel.updateViewStyle(); widget.ffi.canvasModel.updateViewStyle();
}), }),
MenuEntryDivider<String>(), MenuEntryDivider<String>(),
MenuEntryRadios<String>( MenuEntryRadios<String>(
text: translate('Scroll Style'), text: translate('Scroll Style'),
optionsGetter: () => [ optionsGetter: () => [
Tuple2<String, String>(translate('ScrollAuto'), 'scrollauto'), MenuEntryRadioOption(
Tuple2<String, String>(translate('Scrollbar'), 'scrollbar'), text: translate('ScrollAuto'), value: 'scrollauto'),
MenuEntryRadioOption(
text: translate('Scrollbar'), value: 'scrollbar'),
], ],
curOptionGetter: () async { curOptionGetter: () async {
return await bind.sessionGetOption( return await bind.sessionGetOption(
id: widget.id, arg: 'scroll-style') ?? id: widget.id, arg: 'scroll-style') ??
''; '';
}, },
optionSetter: (String v) async { optionSetter: (String oldValue, String newValue) async {
await bind.sessionPeerOption( await bind.sessionPeerOption(
id: widget.id, name: "scroll-style", value: v); id: widget.id, name: "scroll-style", value: newValue);
widget.ffi.canvasModel.updateScrollStyle(); widget.ffi.canvasModel.updateScrollStyle();
}), }),
MenuEntryDivider<String>(), MenuEntryDivider<String>(),
MenuEntryRadios<String>( MenuEntryRadios<String>(
text: translate('Image Quality'), text: translate('Image Quality'),
optionsGetter: () => [ optionsGetter: () => [
Tuple2<String, String>(translate('Good image quality'), 'best'), MenuEntryRadioOption(
Tuple2<String, String>(translate('Balanced'), 'balanced'), text: translate('Good image quality'), value: 'best'),
Tuple2<String, String>( MenuEntryRadioOption(
translate('Optimize reaction time'), 'low'), text: translate('Balanced'), value: 'balanced'),
MenuEntryRadioOption(
text: translate('Optimize reaction time'), value: 'low'),
MenuEntryRadioOption(
text: translate('Custom'),
value: 'custom',
dismissOnClicked: true),
], ],
curOptionGetter: () async { curOptionGetter: () async {
String quality = String quality =
@ -451,8 +461,43 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
if (quality == '') quality = 'balanced'; if (quality == '') quality = 'balanced';
return quality; return quality;
}, },
optionSetter: (String v) async { optionSetter: (String oldValue, String newValue) async {
await bind.sessionSetImageQuality(id: widget.id, value: v); 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>(), MenuEntryDivider<String>(),
MenuEntrySwitch<String>( MenuEntrySwitch<String>(

View File

@ -7,6 +7,7 @@ import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/generated_bridge.dart'; import 'package:flutter_hbb/generated_bridge.dart';
import 'package:flutter_hbb/models/ab_model.dart'; import 'package:flutter_hbb/models/ab_model.dart';
import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/chat_model.dart';
@ -499,8 +500,8 @@ class CanvasModel with ChangeNotifier {
_scale = 1.0; _scale = 1.0;
if (style == 'adaptive') { if (style == 'adaptive') {
final s1 = size.width / (parent.target?.ffiModel.display.width ?? 720); final s1 = size.width / getDisplayWidth();
final s2 = size.height / (parent.target?.ffiModel.display.height ?? 1280); final s2 = size.height / getDisplayHeight();
_scale = s1 < s2 ? s1 : s2; _scale = s1 < s2 ? s1 : s2;
} }
@ -529,11 +530,17 @@ class CanvasModel with ChangeNotifier {
} }
int getDisplayWidth() { int getDisplayWidth() {
return parent.target?.ffiModel.display.width ?? 1080; final defaultWidth = (isDesktop || isWebDesktop)
? kDesktopDefaultDisplayWidth
: kMobileDefaultDisplayWidth;
return parent.target?.ffiModel.display.width ?? defaultWidth;
} }
int getDisplayHeight() { int getDisplayHeight() {
return parent.target?.ffiModel.display.height ?? 720; final defaultHeight = (isDesktop || isWebDesktop)
? kDesktopDefaultDisplayHeight
: kMobileDefaultDisplayHeight;
return parent.target?.ffiModel.display.height ?? defaultHeight;
} }
Size get size { Size get size {

View File

@ -143,14 +143,6 @@ pub fn session_get_toggle_option_sync(id: String, arg: String) -> SyncReturn<boo
SyncReturn(res) SyncReturn(res)
} }
pub fn session_get_image_quality(id: String) -> Option<String> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_image_quality())
} else {
None
}
}
pub fn session_get_option(id: String, arg: String) -> Option<String> { pub fn session_get_option(id: String, arg: String) -> Option<String> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) { if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_option(arg)) Some(session.get_option(arg))
@ -190,12 +182,34 @@ pub fn session_toggle_option(id: String, value: String) {
} }
} }
pub fn session_get_image_quality(id: String) -> Option<String> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_image_quality())
} else {
None
}
}
pub fn session_set_image_quality(id: String, value: String) { pub fn session_set_image_quality(id: String, value: String) {
if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) { if let Some(session) = SESSIONS.write().unwrap().get_mut(&id) {
session.save_image_quality(value); session.save_image_quality(value);
} }
} }
pub fn session_get_custom_image_quality(id: String) -> Option<Vec<i32>> {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
Some(session.get_custom_image_quality())
} else {
None
}
}
pub fn session_set_custom_image_quality(id: String, value: i32) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.set_custom_image_quality(value);
}
}
pub fn session_lock_screen(id: String) { pub fn session_lock_screen(id: String) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) { if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.lock_screen(); session.lock_screen();