Merge pull request #1273 from fufesou/flutter_desktop_remote_menus_rebase

Flutter desktop remote menus rebase
This commit is contained in:
RustDesk 2022-08-14 19:16:54 +08:00 committed by GitHub
commit f812adedff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 634 additions and 426 deletions

View File

@ -4,3 +4,6 @@ const String kAppTypeDesktopRemote = "remote";
const String kAppTypeDesktopFileTransfer = "file transfer";
const String kTabLabelHomePage = "Home";
const String kTabLabelSettingPage = "Settings";
const int kDefaultDisplayWidth = 1280;
const int kDefaultDisplayHeight = 720;

View File

@ -41,6 +41,7 @@ class _RemotePageState extends State<RemotePage>
String _value = '';
double _scale = 1;
double _mouseScrollIntegral = 0; // mouse scroll speed controller
var _cursorOverImage = false.obs;
var _more = true;
var _fn = false;
@ -256,7 +257,8 @@ class _RemotePageState extends State<RemotePage>
}
});
}),
bottomNavigationBar: _showBar && hasDisplays ? getBottomAppBar() : null,
bottomNavigationBar:
_showBar && hasDisplays ? getBottomAppBar(ffiModel) : null,
body: Overlay(
initialEntries: [
OverlayEntry(builder: (context) {
@ -274,7 +276,6 @@ class _RemotePageState extends State<RemotePage>
@override
Widget build(BuildContext context) {
super.build(context);
_ffi.canvasModel.tabBarHeight = super.widget.tabBarHeight;
return WillPopScope(
onWillPop: () async {
clientClose(_ffi.dialogManager);
@ -292,201 +293,148 @@ class _RemotePageState extends State<RemotePage>
}
Widget getRawPointerAndKeyBody(Widget child) {
return Listener(
onPointerHover: (e) {
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (!_isPhysicalMouse) {
setState(() {
_isPhysicalMouse = true;
});
}
if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mousemove'),
tabBarHeight: super.widget.tabBarHeight);
}
},
onPointerDown: (e) {
if (e.kind != ui.PointerDeviceKind.mouse) {
if (_isPhysicalMouse) {
setState(() {
_isPhysicalMouse = false;
});
}
}
if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mousedown'),
tabBarHeight: super.widget.tabBarHeight);
}
},
onPointerUp: (e) {
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mouseup'),
tabBarHeight: super.widget.tabBarHeight);
}
},
onPointerMove: (e) {
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mousemove'),
tabBarHeight: super.widget.tabBarHeight);
}
},
onPointerSignal: (e) {
if (e is PointerScrollEvent) {
var dx = e.scrollDelta.dx;
var dy = e.scrollDelta.dy;
if (dx > 0)
dx = -1;
else if (dx < 0) dx = 1;
if (dy > 0)
dy = -1;
else if (dy < 0) dy = 1;
bind.sessionSendMouse(
id: widget.id,
msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
}
},
child: Consumer<FfiModel>(
builder: (context, FfiModel, _child) => MouseRegion(
cursor: FfiModel.permissions['keyboard'] != false
? SystemMouseCursors.none
: MouseCursor.defer,
child: FocusScope(
return Consumer<FfiModel>(
builder: (context, FfiModel, _child) => MouseRegion(
cursor: FfiModel.permissions['keyboard'] != false
? SystemMouseCursors.none
: MouseCursor.defer,
child: FocusScope(
autofocus: true,
child: Focus(
autofocus: true,
child: Focus(
autofocus: true,
canRequestFocus: true,
focusNode: _physicalFocusNode,
onKey: (data, e) {
final key = e.logicalKey;
if (e is RawKeyDownEvent) {
if (e.repeat) {
sendRawKey(e, press: true);
} else {
if (e.isAltPressed && !_ffi.alt) {
_ffi.alt = true;
} else if (e.isControlPressed && !_ffi.ctrl) {
_ffi.ctrl = true;
} else if (e.isShiftPressed && !_ffi.shift) {
_ffi.shift = true;
} else if (e.isMetaPressed && !_ffi.command) {
_ffi.command = true;
}
sendRawKey(e, down: true);
}
canRequestFocus: true,
focusNode: _physicalFocusNode,
onKey: (data, e) {
final key = e.logicalKey;
if (e is RawKeyDownEvent) {
if (e.repeat) {
sendRawKey(e, press: true);
} else {
if (e.isAltPressed && !_ffi.alt) {
_ffi.alt = true;
} else if (e.isControlPressed && !_ffi.ctrl) {
_ffi.ctrl = true;
} else if (e.isShiftPressed && !_ffi.shift) {
_ffi.shift = true;
} else if (e.isMetaPressed && !_ffi.command) {
_ffi.command = true;
}
// [!_showEdit] workaround for soft-keyboard's control_key like Backspace / Enter
if (!_showEdit && e is RawKeyUpEvent) {
if (key == LogicalKeyboardKey.altLeft ||
key == LogicalKeyboardKey.altRight) {
_ffi.alt = false;
} else if (key == LogicalKeyboardKey.controlLeft ||
key == LogicalKeyboardKey.controlRight) {
_ffi.ctrl = false;
} else if (key == LogicalKeyboardKey.shiftRight ||
key == LogicalKeyboardKey.shiftLeft) {
_ffi.shift = false;
} else if (key == LogicalKeyboardKey.metaLeft ||
key == LogicalKeyboardKey.metaRight) {
_ffi.command = false;
}
sendRawKey(e);
}
return KeyEventResult.handled;
},
child: child)))));
sendRawKey(e, down: true);
}
}
// [!_showEdit] workaround for soft-keyboard's control_key like Backspace / Enter
if (!_showEdit && e is RawKeyUpEvent) {
if (key == LogicalKeyboardKey.altLeft ||
key == LogicalKeyboardKey.altRight) {
_ffi.alt = false;
} else if (key == LogicalKeyboardKey.controlLeft ||
key == LogicalKeyboardKey.controlRight) {
_ffi.ctrl = false;
} else if (key == LogicalKeyboardKey.shiftRight ||
key == LogicalKeyboardKey.shiftLeft) {
_ffi.shift = false;
} else if (key == LogicalKeyboardKey.metaLeft ||
key == LogicalKeyboardKey.metaRight) {
_ffi.command = false;
}
sendRawKey(e);
}
return KeyEventResult.handled;
},
child: child))));
}
Widget? getBottomAppBar() {
return BottomAppBar(
elevation: 10,
color: MyTheme.accent,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: <Widget>[
IconButton(
color: Colors.white,
icon: Icon(Icons.clear),
onPressed: () {
clientClose(_ffi.dialogManager);
},
)
] +
<Widget>[
IconButton(
color: Colors.white,
icon: Icon(Icons.tv),
onPressed: () {
setState(() => _showEdit = false);
showOptions(widget.id, _ffi.dialogManager);
},
)
] +
(isWebDesktop
? []
: _ffi.ffiModel.isPeerAndroid
? [
Widget? getBottomAppBar(FfiModel ffiModel) {
return MouseRegion(
cursor: SystemMouseCursors.basic,
child: BottomAppBar(
elevation: 10,
color: MyTheme.accent,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: <Widget>[
IconButton(
color: Colors.white,
icon: Icon(Icons.clear),
onPressed: () {
clientClose(_ffi.dialogManager);
},
)
] +
<Widget>[
IconButton(
color: Colors.white,
icon: Icon(Icons.tv),
onPressed: () {
setState(() => _showEdit = false);
showOptions(widget.id, _ffi.dialogManager);
},
)
] +
(isWebDesktop
? []
: _ffi.ffiModel.isPeerAndroid
? [
IconButton(
color: Colors.white,
icon: Icon(Icons.build),
onPressed: () {
if (mobileActionsOverlayEntry == null) {
showMobileActionsOverlay();
} else {
hideMobileActionsOverlay();
}
},
)
]
: [
IconButton(
color: Colors.white,
icon: Icon(Icons.keyboard),
onPressed: openKeyboard),
IconButton(
color: Colors.white,
icon: Icon(_ffi.ffiModel.touchMode
? Icons.touch_app
: Icons.mouse),
onPressed: changeTouchMode,
),
]) +
(isWeb
? []
: <Widget>[
IconButton(
color: Colors.white,
icon: Icon(Icons.build),
icon: Icon(Icons.message),
onPressed: () {
if (mobileActionsOverlayEntry == null) {
showMobileActionsOverlay();
} else {
hideMobileActionsOverlay();
}
_ffi.chatModel
.changeCurrentID(ChatModel.clientModeID);
_ffi.chatModel.toggleChatOverlay();
},
)
]
: [
IconButton(
color: Colors.white,
icon: Icon(Icons.keyboard),
onPressed: openKeyboard),
IconButton(
color: Colors.white,
icon: Icon(_ffi.ffiModel.touchMode
? Icons.touch_app
: Icons.mouse),
onPressed: changeTouchMode,
),
]) +
(isWeb
? []
: <Widget>[
IconButton(
color: Colors.white,
icon: Icon(Icons.message),
onPressed: () {
_ffi.chatModel
.changeCurrentID(ChatModel.clientModeID);
_ffi.chatModel.toggleChatOverlay();
},
)
]) +
[
IconButton(
color: Colors.white,
icon: Icon(Icons.more_vert),
onPressed: () {
setState(() => _showEdit = false);
showActions(widget.id);
},
),
]),
IconButton(
color: Colors.white,
icon: Icon(Icons.expand_more),
onPressed: () {
setState(() => _showBar = !_showBar);
}),
],
),
);
[
IconButton(
color: Colors.white,
icon: Icon(Icons.more_vert),
onPressed: () {
setState(() => _showEdit = false);
showActions(widget.id, ffiModel);
},
),
]),
IconButton(
color: Colors.white,
icon: Icon(Icons.expand_more),
onPressed: () {
setState(() => _showBar = !_showBar);
}),
],
),
));
}
/// touchMode only:
@ -499,6 +447,81 @@ class _RemotePageState extends State<RemotePage>
/// DoubleFiner -> right click
/// HoldDrag -> left drag
void _onPointHoverImage(PointerHoverEvent e) {
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (!_isPhysicalMouse) {
setState(() {
_isPhysicalMouse = true;
});
}
if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mousemove'),
tabBarHeight: super.widget.tabBarHeight);
}
}
void _onPointDownImage(PointerDownEvent e) {
if (e.kind != ui.PointerDeviceKind.mouse) {
if (_isPhysicalMouse) {
setState(() {
_isPhysicalMouse = false;
});
}
}
if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mousedown'),
tabBarHeight: super.widget.tabBarHeight);
}
}
void _onPointUpImage(PointerUpEvent e) {
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mouseup'),
tabBarHeight: super.widget.tabBarHeight);
}
}
void _onPointMoveImage(PointerMoveEvent e) {
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) {
_ffi.handleMouse(getEvent(e, 'mousemove'),
tabBarHeight: super.widget.tabBarHeight);
}
}
void _onPointerSignalImage(PointerSignalEvent e) {
if (e is PointerScrollEvent) {
var dx = e.scrollDelta.dx.toInt();
var dy = e.scrollDelta.dy.toInt();
if (dx > 0)
dx = -1;
else if (dx < 0) dx = 1;
if (dy > 0)
dy = -1;
else if (dy < 0) dy = 1;
bind.sessionSendMouse(
id: widget.id, msg: '{"type": "wheel", "x": "$dx", "y": "$dy"}');
}
}
Widget _buildImageListener(Widget child) {
return Listener(
onPointerHover: _onPointHoverImage,
onPointerDown: _onPointDownImage,
onPointerUp: _onPointUpImage,
onPointerMove: _onPointMoveImage,
onPointerSignal: _onPointerSignalImage,
child: MouseRegion(
onEnter: (evt) {
_cursorOverImage.value = true;
},
onExit: (evt) {
_cursorOverImage.value = false;
},
child: child));
}
Widget getBodyForDesktop(BuildContext context, bool keyboard) {
var paints = <Widget>[
MouseRegion(onEnter: (evt) {
@ -512,6 +535,8 @@ class _RemotePageState extends State<RemotePage>
});
return ImagePaint(
id: widget.id,
cursorOverImage: _cursorOverImage,
listenerBuilder: _buildImageListener,
);
}),
))
@ -550,7 +575,7 @@ class _RemotePageState extends State<RemotePage>
return out;
}
void showActions(String id) async {
void showActions(String id, FfiModel ffiModel) async {
final size = MediaQuery.of(context).size;
final x = 120.0;
final y = size.height - super.widget.tabBarHeight;
@ -595,12 +620,8 @@ class _RemotePageState extends State<RemotePage>
await bind.getSessionToggleOption(id: id, arg: 'privacy-mode') !=
true) {
more.add(PopupMenuItem<String>(
child: Consumer<FfiModel>(
builder: (_context, ffiModel, _child) => () {
return Text(translate(
(ffiModel.inputBlocked ? 'Unb' : 'B') +
'lock user input'));
}()),
child: Text(translate(
(ffiModel.inputBlocked ? 'Unb' : 'B') + 'lock user input')),
value: 'block-input'));
}
}
@ -630,6 +651,9 @@ class _RemotePageState extends State<RemotePage>
}
}();
} else if (value == 'enter_os_password') {
// FIXME:
// null means no session of id
// empty string means no password
var password = await bind.getSessionOption(id: id, arg: "os-password");
if (password != null) {
bind.sessionInputOsPassword(id: widget.id, value: password);
@ -822,18 +846,95 @@ class _RemotePageState extends State<RemotePage>
class ImagePaint extends StatelessWidget {
final String id;
final Rx<bool> cursorOverImage;
final Widget Function(Widget)? listenerBuilder;
final ScrollController _horizontal = ScrollController();
final ScrollController _vertical = ScrollController();
const ImagePaint({Key? key, required this.id}) : super(key: key);
ImagePaint(
{Key? key,
required this.id,
required this.cursorOverImage,
this.listenerBuilder = null})
: super(key: key);
@override
Widget build(BuildContext context) {
final m = Provider.of<ImageModel>(context);
final c = Provider.of<CanvasModel>(context);
var s = c.scale;
return CustomPaint(
painter:
new ImagePainter(image: m.image, x: c.x / s, y: c.y / s, scale: s),
);
var c = Provider.of<CanvasModel>(context);
final s = c.scale;
if (c.scrollStyle == ScrollStyle.scrollbar) {
final imageWidget = SizedBox(
width: c.getDisplayWidth() * s,
height: c.getDisplayHeight() * s,
child: CustomPaint(
painter: new ImagePainter(image: m.image, x: 0, y: 0, scale: s),
));
return Center(
child: NotificationListener<ScrollNotification>(
onNotification: (_notification) {
final percentX = _horizontal.position.extentBefore /
(_horizontal.position.extentBefore +
_horizontal.position.extentInside +
_horizontal.position.extentAfter);
final percentY = _vertical.position.extentBefore /
(_vertical.position.extentBefore +
_vertical.position.extentInside +
_vertical.position.extentAfter);
c.setScrollPercent(percentX, percentY);
return false;
},
child: Obx(() => MouseRegion(
cursor: cursorOverImage.value
? SystemMouseCursors.none
: SystemMouseCursors.basic,
child: _buildCrossScrollbar(_buildListener(imageWidget)))),
));
} else {
final imageWidget = SizedBox(
width: c.size.width,
height: c.size.height,
child: CustomPaint(
painter: new ImagePainter(
image: m.image, x: c.x / s, y: c.y / s, scale: s),
));
return _buildListener(imageWidget);
}
}
Widget _buildCrossScrollbar(Widget child) {
final physicsVertical =
cursorOverImage.value ? const NeverScrollableScrollPhysics() : null;
final physicsHorizontal =
cursorOverImage.value ? const NeverScrollableScrollPhysics() : null;
return Scrollbar(
controller: _vertical,
thumbVisibility: true,
trackVisibility: true,
child: Scrollbar(
controller: _horizontal,
thumbVisibility: true,
trackVisibility: true,
notificationPredicate: (notif) => notif.depth == 1,
child: SingleChildScrollView(
controller: _vertical,
physics: physicsVertical,
child: SingleChildScrollView(
controller: _horizontal,
scrollDirection: Axis.horizontal,
physics: physicsHorizontal,
child: child,
),
),
));
}
Widget _buildListener(Widget child) {
if (listenerBuilder != null) {
return listenerBuilder!(child);
} else {
return child;
}
}
}
@ -896,6 +997,8 @@ void showOptions(String id, OverlayDialogManager dialogManager) async {
if (quality == '') quality = 'balanced';
String viewStyle =
await bind.getSessionOption(id: id, arg: 'view-style') ?? '';
String scrollStyle =
await bind.getSessionOption(id: id, arg: 'scroll-style') ?? '';
var displays = <Widget>[];
final pi = ffi(id).ffiModel.pi;
final image = ffi(id).ffiModel.getConnectionImage();
@ -968,6 +1071,14 @@ void showOptions(String id, OverlayDialogManager dialogManager) async {
ffi(id).canvasModel.updateViewStyle();
});
};
var setScrollStyle = (String? value) {
if (value == null) return;
setState(() {
scrollStyle = value;
bind.sessionPeerOption(id: id, name: "scroll-style", value: value);
ffi(id).canvasModel.updateScrollStyle();
});
};
return CustomAlertDialog(
title: SizedBox.shrink(),
content: Column(
@ -978,6 +1089,10 @@ void showOptions(String id, OverlayDialogManager dialogManager) async {
getRadio('Shrink', 'shrink', viewStyle, setViewStyle),
getRadio('Stretch', 'stretch', viewStyle, setViewStyle),
Divider(color: MyTheme.border),
getRadio(
'ScrollAuto', 'scrollauto', scrollStyle, setScrollStyle),
getRadio('Scrollbar', 'scrollbar', scrollStyle, setScrollStyle),
Divider(color: MyTheme.border),
getRadio('Good image quality', 'best', quality, setQuality),
getRadio('Balanced', 'balanced', quality, setQuality),
getRadio('Optimize reaction time', 'low', quality, setQuality),

View File

@ -735,6 +735,9 @@ class _RemotePageState extends State<RemotePage> {
}
}();
} else if (value == 'enter_os_password') {
// FIXME:
// null means no session of id
// empty string means no password
var password = await bind.getSessionOption(id: id, arg: "os-password");
if (password != null) {
bind.sessionInputOsPassword(id: widget.id, value: password);

View File

@ -387,8 +387,9 @@ class ImageModel with ChangeNotifier {
void update(ui.Image? image, double tabBarHeight) {
if (_image == null && image != null) {
if (isWebDesktop) {
if (isWebDesktop || isDesktop) {
parent.target?.canvasModel.updateViewStyle();
parent.target?.canvasModel.updateScrollStyle();
} else {
final size = MediaQueryData.fromWindow(ui.window).size;
final canvasWidth = size.width;
@ -432,36 +433,52 @@ class ImageModel with ChangeNotifier {
}
}
enum ScrollStyle {
scrollbar,
scrollauto,
}
class CanvasModel with ChangeNotifier {
// scroll offset x percent
double _scrollX = 0.0;
// scroll offset y percent
double _scrollY = 0.0;
double _x = 0;
double _y = 0;
double _scale = 1.0;
double _tabBarHeight = 0.0;
String id = ""; // TODO multi canvas model
ScrollStyle _scrollStyle = ScrollStyle.scrollauto;
WeakReference<FFI> parent;
CanvasModel(this.parent);
double get x => _x;
double get y => _y;
double get scale => _scale;
ScrollStyle get scrollStyle => _scrollStyle;
setScrollPercent(double x, double y) {
_scrollX = x;
_scrollY = y;
}
double get scrollX => _scrollX;
double get scrollY => _scrollY;
set tabBarHeight(double h) => _tabBarHeight = h;
double get tabBarHeight => _tabBarHeight;
void updateViewStyle() async {
final s = await bind.getSessionOption(id: id, arg: 'view-style');
if (s == null) {
final style = await bind.getSessionOption(id: id, arg: 'view-style');
if (style == null) {
return;
}
final size = MediaQueryData.fromWindow(ui.window).size;
final canvasWidth = size.width;
final canvasHeight = size.height - _tabBarHeight;
final s1 = canvasWidth / (parent.target?.ffiModel.display.width ?? 720);
final s2 = canvasHeight / (parent.target?.ffiModel.display.height ?? 1280);
final s1 = size.width / (parent.target?.ffiModel.display.width ?? 720);
final s2 = size.height / (parent.target?.ffiModel.display.height ?? 1280);
// Closure to perform shrink operation.
final shrinkOp = () {
final s = s1 < s2 ? s1 : s2;
@ -471,7 +488,7 @@ class CanvasModel with ChangeNotifier {
};
// Closure to perform stretch operation.
final stretchOp = () {
final s = s1 > s2 ? s1 : s2;
final s = s1 < s2 ? s1 : s2;
if (s > 1) {
_scale = s;
}
@ -480,20 +497,34 @@ class CanvasModel with ChangeNotifier {
final defaultOp = () {
_scale = 1.0;
};
if (s == 'shrink') {
// // On desktop, shrink is the default behavior.
// if (isDesktop) {
// shrinkOp();
// } else {
defaultOp();
// }
if (style == 'shrink') {
shrinkOp();
} else if (s == 'stretch') {
} else if (style == 'stretch') {
stretchOp();
} else {
// On desktop, shrink is the default behavior.
if (isDesktop) {
shrinkOp();
} else {
defaultOp();
}
}
_x = (canvasWidth - getDisplayWidth() * _scale) / 2;
_y = (canvasHeight - getDisplayHeight() * _scale) / 2;
_x = (size.width - getDisplayWidth() * _scale) / 2;
_y = (size.height - getDisplayHeight() * _scale) / 2;
notifyListeners();
}
updateScrollStyle() async {
final style = await bind.getSessionOption(id: id, arg: 'scroll-style');
if (style == 'scrollbar') {
_scrollStyle = ScrollStyle.scrollbar;
_scrollX = 0.0;
_scrollY = 0.0;
} else {
_scrollStyle = ScrollStyle.scrollauto;
}
notifyListeners();
}
@ -512,28 +543,30 @@ class CanvasModel with ChangeNotifier {
return parent.target?.ffiModel.display.height ?? 720;
}
Size get size {
final size = MediaQueryData.fromWindow(ui.window).size;
return Size(size.width, size.height - _tabBarHeight);
}
void moveDesktopMouse(double x, double y) {
// On mobile platforms, move the canvas with the cursor.
if (!isDesktop) {
final size = MediaQueryData.fromWindow(ui.window).size;
final canvasWidth = size.width;
final canvasHeight = size.height - _tabBarHeight;
final dw = getDisplayWidth() * _scale;
final dh = getDisplayHeight() * _scale;
var dxOffset = 0;
var dyOffset = 0;
if (dw > canvasWidth) {
dxOffset = (x - dw * (x / canvasWidth) - _x).toInt();
}
if (dh > canvasHeight) {
dyOffset = (y - dh * (y / canvasHeight) - _y).toInt();
}
_x += dxOffset;
_y += dyOffset;
if (dxOffset != 0 || dyOffset != 0) {
notifyListeners();
}
//if (!isDesktop) {
final dw = getDisplayWidth() * _scale;
final dh = getDisplayHeight() * _scale;
var dxOffset = 0;
var dyOffset = 0;
if (dw > size.width) {
dxOffset = (x - dw * (x / size.width) - _x).toInt();
}
if (dh > size.height) {
dyOffset = (y - dh * (y / size.height) - _y).toInt();
}
_x += dxOffset;
_y += dyOffset;
if (dxOffset != 0 || dyOffset != 0) {
notifyListeners();
}
//}
parent.target?.cursorModel.moveLocal(x, y);
}
@ -1091,8 +1124,24 @@ class FFI {
canvasModel.moveDesktopMouse(x, y);
}
final d = ffiModel.display;
x -= canvasModel.x;
y -= canvasModel.y;
if (canvasModel.scrollStyle == ScrollStyle.scrollbar) {
final imageWidth = d.width * canvasModel.scale;
final imageHeight = d.height * canvasModel.scale;
x += imageWidth * canvasModel.scrollX;
y += imageHeight * canvasModel.scrollY;
// boxed size is a center widget
if (canvasModel.size.width > imageWidth) {
x -= ((canvasModel.size.width - imageWidth) / 2);
}
if (canvasModel.size.height > imageHeight) {
y -= ((canvasModel.size.height - imageHeight) / 2);
}
} else {
x -= canvasModel.x;
y -= canvasModel.y;
}
if (!isMove && (x < 0 || x > d.width || y < 0 || y > d.height)) {
return;
}

File diff suppressed because it is too large Load Diff

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "原始比例"),
("Shrink", "收缩"),
("Stretch", "伸展"),
("Scrollbar", "滚动条"),
("ScrollAuto", "自动滚动"),
("Good image quality", "好画质"),
("Balanced", "一般画质"),
("Optimize reaction time", "优化反应时间"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Původní"),
("Shrink", "Oříznout"),
("Stretch", "Roztáhnout"),
("Scrollbar", "Posuvník"),
("ScrollAuto", "Rolovať Auto"),
("Good image quality", "Dobrá kvalita obrazu"),
("Balanced", "Vyvážené"),
("Optimize reaction time", "Optimalizovat pro co nejnižší prodlevu odezvy"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Original"),
("Shrink", "Krymp"),
("Stretch", "Strak"),
("Scrollbar", "Rullebar"),
("ScrollAuto", "Rul Auto"),
("Good image quality", "God billedkvalitet"),
("Balanced", "Afbalanceret"),
("Optimize reaction time", "Optimeret responstid"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Original"),
("Shrink", "Verkleinern"),
("Stretch", "Strecken"),
("Scrollbar", "Scrollleiste"),
("ScrollAuto", "Automatisch scrollen"),
("Good image quality", "Schöner"),
("Balanced", "Ausgeglichen"),
("Optimize reaction time", "Schneller"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Originala rilatumo"),
("Shrink", "Ŝrumpi"),
("Stretch", "Streĉi"),
("Scrollbar", "Rulumbreto"),
("ScrollAuto", "Rulumu Aŭtomate"),
("Good image quality", "Bona bilda kvalito"),
("Balanced", "Normala bilda kvalito"),
("Optimize reaction time", "Optimigi reakcia tempo"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Original"),
("Shrink", "Encogerse"),
("Stretch", "Estirar"),
("Scrollbar", "Barra de desplazamiento"),
("ScrollAuto", "Desplazamiento automático"),
("Good image quality", "Buena calidad de imagen"),
("Balanced", "Equilibrado"),
("Optimize reaction time", "Optimizar el tiempo de reacción"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Ratio d'origine"),
("Shrink", "Rétrécir"),
("Stretch", "Étirer"),
("Scrollbar", "Barre de défilement"),
("ScrollAuto", "Défilement automatique"),
("Good image quality", "Bonne qualité d'image"),
("Balanced", "Qualité d'image normale"),
("Optimize reaction time", "Optimiser le temps de réaction"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Eredeti"),
("Shrink", "Zsugorított"),
("Stretch", "Nyújtott"),
("Scrollbar", "Görgetősáv"),
("ScrollAuto", "Görgessen Auto"),
("Good image quality", "Jó képminőség"),
("Balanced", "Balanszolt"),
("Optimize reaction time", "Válaszidő optimializálása"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Original"),
("Shrink", "Susutkan"),
("Stretch", "Regangkan"),
("Scrollbar", "Scroll bar"),
("ScrollAuto", "Gulir Otomatis"),
("Good image quality", "Kualitas Gambar Baik"),
("Balanced", "Seimbang"),
("Optimize reaction time", "Optimalkan waktu reaksi"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Originale"),
("Shrink", "Restringi"),
("Stretch", "Allarga"),
("Scrollbar", "Barra di scorrimento"),
("ScrollAuto", "Scorri automaticamente"),
("Good image quality", "Buona qualità immagine"),
("Balanced", "Bilanciato"),
("Optimize reaction time", "Ottimizza il tempo di reazione"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "オリジナル"),
("Shrink", "縮小"),
("Stretch", "伸縮"),
("Scrollbar", "スクロール・バー"),
("ScrollAuto", "自動スクロール"),
("Good image quality", "画質優先"),
("Balanced", "バランス"),
("Optimize reaction time", "速度優先"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Oryginał"),
("Shrink", "Zmniejsz"),
("Stretch", "Zwiększ"),
("Scrollbar", "Pasek przewijania"),
("ScrollAuto", "Przewijanie automatyczne"),
("Good image quality", "Dobra jakość obrazu"),
("Balanced", "Zrównoważony"),
("Optimize reaction time", "Zoptymalizuj czas reakcji"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Original"),
("Shrink", "Reduzir"),
("Stretch", "Aumentar"),
("Scrollbar", "Barra de rolagem"),
("ScrollAuto", "Rolagem automática"),
("Good image quality", "Qualidade visual boa"),
("Balanced", "Balanceada"),
("Optimize reaction time", "Otimizar tempo de reação"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Оригинал"),
("Shrink", "Уменьшить"),
("Stretch", "Растянуть"),
("Scrollbar", "Полоса прокрутки"),
("ScrollAuto", "Прокрутка Авто"),
("Good image quality", "Хорошее качество изображения"),
("Balanced", "Сбалансированный"),
("Optimize reaction time", "Оптимизировать время реакции"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Pôvodný"),
("Shrink", "Zmenšené"),
("Stretch", "Roztiahnuté"),
("Scrollbar", "Posuvník"),
("ScrollAuto", "Rolovať Auto"),
("Good image quality", "Dobrá kvalita obrazu"),
("Balanced", "Vyvážené"),
("Optimize reaction time", "Optimalizované pre čas odozvy"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", ""),
("Shrink", ""),
("Stretch", ""),
("Scrollbar", ""),
("ScrollAuto", ""),
("Good image quality", ""),
("Balanced", ""),
("Optimize reaction time", ""),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Orjinal"),
("Shrink", "Küçült"),
("Stretch", "Uzat"),
("Scrollbar", "Kaydırma çubuğu"),
("ScrollAuto", "Otomatik Kaydır"),
("Good image quality", "İyi görüntü kalitesi"),
("Balanced", "Dengelenmiş"),
("Optimize reaction time", "Tepki süresini optimize et"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "原始"),
("Shrink", "縮減"),
("Stretch", "延展"),
("Scrollbar", "滾動條"),
("ScrollAuto", "自動滾動"),
("Good image quality", "畫面品質良好"),
("Balanced", "平衡"),
("Optimize reaction time", "回應速度最佳化"),

View File

@ -103,6 +103,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Original", "Gốc"),
("Shrink", "Thu nhỏ"),
("Stretch", "Kéo dãn"),
("Scrollbar", "Thanh cuộn"),
("ScrollAuto", "Tự động cuộn"),
("Good image quality", "Chất lượng hình ảnh tốt"),
("Balanced", "Cân bằng"),
("Optimize reaction time", "Thời gian phản ứng tối ưu"),