refactor RawPointerMouseRegion & RawKeyFocusScope

This commit is contained in:
csf 2022-09-27 22:16:27 +08:00
parent 3dc9ecce29
commit 77fcf2d4fa
4 changed files with 157 additions and 210 deletions

View File

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../models/input_model.dart';
class RawKeyFocusScope extends StatelessWidget {
final FocusNode? focusNode;
final ValueChanged<bool>? onFocusChange;
final InputModel inputModel;
final Widget child;
RawKeyFocusScope(
{this.focusNode,
this.onFocusChange,
required this.inputModel,
required this.child});
@override
Widget build(BuildContext context) {
return FocusScope(
autofocus: true,
child: Focus(
autofocus: true,
canRequestFocus: true,
focusNode: focusNode,
onFocusChange: onFocusChange,
onKey: inputModel.handleRawKeyEvent,
child: child));
}
}
class RawPointerMouseRegion extends StatelessWidget {
final InputModel inputModel;
final Widget child;
final MouseCursor? cursor;
final PointerEnterEventListener? onEnter;
final PointerExitEventListener? onExit;
RawPointerMouseRegion(
{this.onEnter,
this.onExit,
this.cursor,
required this.inputModel,
required this.child});
@override
Widget build(BuildContext context) {
return Listener(
onPointerHover: inputModel.onPointHoverImage,
onPointerDown: inputModel.onPointDownImage,
onPointerUp: inputModel.onPointUpImage,
onPointerMove: inputModel.onPointMoveImage,
onPointerSignal: inputModel.onPointerSignalImage,
child: MouseRegion(
cursor: cursor ?? MouseCursor.defer,
onEnter: onEnter,
onExit: onExit,
child: child));
}
}

View File

@ -10,6 +10,7 @@ import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
import 'package:flutter_custom_cursor/flutter_custom_cursor.dart'; import 'package:flutter_custom_cursor/flutter_custom_cursor.dart';
import '../../common/widgets/remote_input.dart';
import '../widgets/remote_menubar.dart'; import '../widgets/remote_menubar.dart';
import '../../common.dart'; import '../../common.dart';
import '../../mobile/widgets/dialog.dart'; import '../../mobile/widgets/dialog.dart';
@ -135,7 +136,13 @@ class _RemotePageState extends State<RemotePage>
_ffi.dialogManager.setOverlayState(Overlay.of(context)); _ffi.dialogManager.setOverlayState(Overlay.of(context));
return Container( return Container(
color: Colors.black, color: Colors.black,
child: getRawPointerAndKeyBody(getBodyForDesktop(context))); child: RawKeyFocusScope(
focusNode: _rawKeyFocusNode,
onFocusChange: (bool v) {
_imageFocused = v;
},
inputModel: _ffi.inputModel,
child: getBodyForDesktop(context)));
}) })
], ],
)); ));
@ -159,20 +166,6 @@ class _RemotePageState extends State<RemotePage>
], child: buildBody(context))); ], child: buildBody(context)));
} }
Widget getRawPointerAndKeyBody(Widget child) {
return FocusScope(
autofocus: true,
child: Focus(
autofocus: true,
canRequestFocus: true,
focusNode: _rawKeyFocusNode,
onFocusChange: (bool v) {
_imageFocused = v;
},
onKey: _ffi.inputModel.handleRawKeyEvent,
child: child));
}
void enterView(PointerEnterEvent evt) { void enterView(PointerEnterEvent evt) {
if (!_imageFocused) { if (!_imageFocused) {
_rawKeyFocusNode.requestFocus(); _rawKeyFocusNode.requestFocus();
@ -200,17 +193,6 @@ class _RemotePageState extends State<RemotePage>
_ffi.inputModel.enterOrLeave(false); _ffi.inputModel.enterOrLeave(false);
} }
Widget _buildImageListener(Widget child) {
return Listener(
onPointerHover: _ffi.inputModel.onPointHoverImage,
onPointerDown: _ffi.inputModel.onPointDownImage,
onPointerUp: _ffi.inputModel.onPointUpImage,
onPointerMove: _ffi.inputModel.onPointMoveImage,
onPointerSignal: _ffi.inputModel.onPointerSignalImage,
child:
MouseRegion(onEnter: enterView, onExit: leaveView, child: child));
}
Widget getBodyForDesktop(BuildContext context) { Widget getBodyForDesktop(BuildContext context) {
var paints = <Widget>[ var paints = <Widget>[
MouseRegion(onEnter: (evt) { MouseRegion(onEnter: (evt) {
@ -226,7 +208,12 @@ class _RemotePageState extends State<RemotePage>
cursorOverImage: _cursorOverImage, cursorOverImage: _cursorOverImage,
keyboardEnabled: _keyboardEnabled, keyboardEnabled: _keyboardEnabled,
remoteCursorMoved: _remoteCursorMoved, remoteCursorMoved: _remoteCursorMoved,
listenerBuilder: _buildImageListener, listenerBuilder: (child) => RawPointerMouseRegion(
onEnter: enterView,
onExit: leaveView,
inputModel: _ffi.inputModel,
child: child,
),
); );
})) }))
]; ];

View File

@ -7,11 +7,12 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_hbb/mobile/widgets/gesture_help.dart'; import 'package:flutter_hbb/mobile/widgets/gesture_help.dart';
import 'package:flutter_hbb/models/chat_model.dart'; import 'package:flutter_hbb/models/chat_model.dart';
import 'package:get/get_state_manager/src/rx_flutter/rx_obx_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
import '../../common.dart'; import '../../common.dart';
import '../../consts.dart'; import '../../common/widgets/remote_input.dart';
import '../../models/input_model.dart'; import '../../models/input_model.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
@ -26,7 +27,7 @@ class RemotePage extends StatefulWidget {
final String id; final String id;
@override @override
_RemotePageState createState() => _RemotePageState(); State<RemotePage> createState() => _RemotePageState();
} }
class _RemotePageState extends State<RemotePage> { class _RemotePageState extends State<RemotePage> {
@ -44,9 +45,8 @@ class _RemotePageState extends State<RemotePage> {
final FocusNode _mobileFocusNode = FocusNode(); final FocusNode _mobileFocusNode = FocusNode();
final FocusNode _physicalFocusNode = FocusNode(); final FocusNode _physicalFocusNode = FocusNode();
var _showEdit = false; // use soft keyboard var _showEdit = false; // use soft keyboard
var _isPhysicalMouse = false;
get inputModel => gFFI.inputModel; InputModel get inputModel => gFFI.inputModel;
@override @override
void initState() { void initState() {
@ -148,8 +148,8 @@ class _RemotePageState extends State<RemotePage> {
} }
return; return;
} }
if (oldValue.length > 0 && if (oldValue.isNotEmpty &&
newValue.length > 0 && newValue.isNotEmpty &&
oldValue[0] == '\1' && oldValue[0] == '\1' &&
newValue[0] != '\1') { newValue[0] != '\1') {
// clipboard // clipboard
@ -214,14 +214,6 @@ class _RemotePageState extends State<RemotePage> {
}); });
} }
void sendRawKey(RawKeyEvent e, {bool? down, bool? press}) {
// for maximum compatibility
final label = logicalKeyMap[e.logicalKey.keyId] ??
physicalKeyMap[e.physicalKey.usbHidUsage] ??
e.logicalKey.keyLabel;
inputModel.inputKey(label, down: down, press: press ?? false);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final pi = Provider.of<FfiModel>(context).pi; final pi = Provider.of<FfiModel>(context).pi;
@ -234,9 +226,7 @@ class _RemotePageState extends State<RemotePage> {
clientClose(gFFI.dialogManager); clientClose(gFFI.dialogManager);
return false; return false;
}, },
child: getRawPointerAndKeyBody( child: getRawPointerAndKeyBody(Scaffold(
keyboard,
Scaffold(
// resizeToAvoidBottomInset: true, // resizeToAvoidBottomInset: true,
floatingActionButton: !showActionButton floatingActionButton: !showActionButton
? null ? null
@ -257,7 +247,7 @@ class _RemotePageState extends State<RemotePage> {
} }
}); });
}), }),
bottomNavigationBar: _showBar && pi.displays.length > 0 bottomNavigationBar: _showBar && pi.displays.isNotEmpty
? getBottomAppBar(keyboard) ? getBottomAppBar(keyboard)
: null, : null,
body: Overlay( body: Overlay(
@ -277,11 +267,11 @@ class _RemotePageState extends State<RemotePage> {
gFFI.canvasModel.updateViewStyle(); gFFI.canvasModel.updateViewStyle();
}); });
} }
return Container( return Obx(() => Container(
color: MyTheme.canvasColor, color: MyTheme.canvasColor,
child: _isPhysicalMouse child: inputModel.isPhysicalMouse.value
? getBodyForMobile() ? getBodyForMobile()
: getBodyForMobileWithGesture()); : getBodyForMobileWithGesture()));
}))); })));
}) })
], ],
@ -289,104 +279,15 @@ class _RemotePageState extends State<RemotePage> {
); );
} }
Widget getRawPointerAndKeyBody(bool keyboard, Widget child) { Widget getRawPointerAndKeyBody(Widget child) {
return Listener( final keyboard = gFFI.ffiModel.permissions['keyboard'] != false;
onPointerHover: (e) { return RawPointerMouseRegion(
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (!_isPhysicalMouse) {
setState(() {
_isPhysicalMouse = true;
});
}
if (_isPhysicalMouse) {
inputModel.handleMouse(getEvent(e, 'mousemove'));
}
},
onPointerDown: (e) {
if (e.kind != ui.PointerDeviceKind.mouse) {
if (_isPhysicalMouse) {
setState(() {
_isPhysicalMouse = false;
});
}
}
if (_isPhysicalMouse) {
inputModel.handleMouse(getEvent(e, 'mousedown'));
}
},
onPointerUp: (e) {
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) {
inputModel.handleMouse(getEvent(e, 'mouseup'));
}
},
onPointerMove: (e) {
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) {
inputModel.handleMouse(getEvent(e, 'mousemove'));
}
},
onPointerSignal: (e) {
if (e is PointerScrollEvent) {
var dy = 0;
if (e.scrollDelta.dy > 0) {
dy = -1;
} else if (e.scrollDelta.dy < 0) {
dy = 1;
}
inputModel.scroll(dy);
}
},
child: MouseRegion(
cursor: keyboard ? SystemMouseCursors.none : MouseCursor.defer, cursor: keyboard ? SystemMouseCursors.none : MouseCursor.defer,
child: FocusScope( inputModel: inputModel,
autofocus: true, child: RawKeyFocusScope(
child: Focus(
autofocus: true,
canRequestFocus: true,
focusNode: _physicalFocusNode, focusNode: _physicalFocusNode,
onKey: (data, e) { inputModel: inputModel,
final key = e.logicalKey; child: child));
if (e is RawKeyDownEvent) {
if (e.repeat &&
!e.isAltPressed &&
!e.isControlPressed &&
!e.isShiftPressed &&
!e.isMetaPressed) {
sendRawKey(e, press: true);
} else {
sendRawKey(e, down: true);
if (e.isAltPressed && !inputModel.alt) {
inputModel.alt = true;
} else if (e.isControlPressed && !inputModel.ctrl) {
inputModel.ctrl = true;
} else if (e.isShiftPressed && !inputModel.shift) {
inputModel.shift = true;
} else if (e.isMetaPressed && !inputModel.command) {
inputModel.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) {
inputModel.alt = false;
} else if (key == LogicalKeyboardKey.controlLeft ||
key == LogicalKeyboardKey.controlRight) {
inputModel.ctrl = false;
} else if (key == LogicalKeyboardKey.shiftRight ||
key == LogicalKeyboardKey.shiftLeft) {
inputModel.shift = false;
} else if (key == LogicalKeyboardKey.metaLeft ||
key == LogicalKeyboardKey.metaRight) {
inputModel.command = false;
}
sendRawKey(e);
}
return KeyEventResult.handled;
},
child: child))));
} }
Widget getBottomAppBar(bool keyboard) { Widget getBottomAppBar(bool keyboard) {
@ -685,7 +586,7 @@ class _RemotePageState extends State<RemotePage> {
if (perms['keyboard'] != false) { if (perms['keyboard'] != false) {
if (pi.platform == 'Linux' || pi.sasEnabled) { if (pi.platform == 'Linux' || pi.sasEnabled) {
more.add(PopupMenuItem<String>( more.add(PopupMenuItem<String>(
child: Text(translate('Insert') + ' Ctrl + Alt + Del'), child: Text('${translate('Insert')} Ctrl + Alt + Del'),
value: 'cad')); value: 'cad'));
} }
more.add(PopupMenuItem<String>( more.add(PopupMenuItem<String>(
@ -694,8 +595,8 @@ class _RemotePageState extends State<RemotePage> {
await bind.sessionGetToggleOption(id: id, arg: 'privacy-mode') != await bind.sessionGetToggleOption(id: id, arg: 'privacy-mode') !=
true) { true) {
more.add(PopupMenuItem<String>( more.add(PopupMenuItem<String>(
child: Text(translate((gFFI.ffiModel.inputBlocked ? 'Unb' : 'B') + child: Text(translate(
'lock user input')), '${gFFI.ffiModel.inputBlocked ? 'Unb' : 'B'}lock user input')),
value: 'block-input')); value: 'block-input'));
} }
} }
@ -720,7 +621,7 @@ class _RemotePageState extends State<RemotePage> {
} else if (value == 'block-input') { } else if (value == 'block-input') {
bind.sessionToggleOption( bind.sessionToggleOption(
id: widget.id, id: widget.id,
value: (gFFI.ffiModel.inputBlocked ? 'un' : '') + 'block-input'); value: '${gFFI.ffiModel.inputBlocked ? 'un' : ''}block-input');
gFFI.ffiModel.inputBlocked = !gFFI.ffiModel.inputBlocked; gFFI.ffiModel.inputBlocked = !gFFI.ffiModel.inputBlocked;
} else if (value == 'refresh') { } else if (value == 'refresh') {
bind.sessionRefresh(id: widget.id); bind.sessionRefresh(id: widget.id);
@ -845,9 +746,9 @@ class _RemotePageState extends State<RemotePage> {
SizedBox(width: 9999), SizedBox(width: 9999),
]; ];
for (var i = 1; i <= 12; ++i) { for (var i = 1; i <= 12; ++i) {
final name = 'F' + i.toString(); final name = 'F$i';
fn.add(wrap(name, () { fn.add(wrap(name, () {
inputModel.inputKey('VK_' + name); inputModel.inputKey('VK_$name');
})); }));
} }
final more = <Widget>[ final more = <Widget>[
@ -920,7 +821,7 @@ class ImagePaint extends StatelessWidget {
final adjust = gFFI.cursorModel.adjustForKeyboard(); final adjust = gFFI.cursorModel.adjustForKeyboard();
var s = c.scale; var s = c.scale;
return CustomPaint( return CustomPaint(
painter: new ImagePainter( painter: ImagePainter(
image: m.image, x: c.x / s, y: (c.y - adjust) / s, scale: s), image: m.image, x: c.x / s, y: (c.y - adjust) / s, scale: s),
); );
} }
@ -934,7 +835,7 @@ class CursorPaint extends StatelessWidget {
final adjust = gFFI.cursorModel.adjustForKeyboard(); final adjust = gFFI.cursorModel.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 - adjust, y: m.y * s - m.hoty + c.y - adjust,
@ -960,7 +861,7 @@ class ImagePainter extends CustomPainter {
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
if (image == null) return; if (image == null) return;
canvas.scale(scale, scale); canvas.scale(scale, scale);
canvas.drawImage(image!, new Offset(x, y), new Paint()); canvas.drawImage(image!, Offset(x, y), Paint());
} }
@override @override

View File

@ -5,7 +5,6 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import '../../models/model.dart'; import '../../models/model.dart';
import '../../models/platform_model.dart'; import '../../models/platform_model.dart';
@ -40,7 +39,7 @@ class InputModel {
var command = false; var command = false;
// mouse // mouse
var _isPhysicalMouse = false; final isPhysicalMouse = false.obs;
int _lastMouseDownButtons = 0; int _lastMouseDownButtons = 0;
get id => parent.target?.id ?? ""; get id => parent.target?.id ?? "";
@ -245,10 +244,10 @@ class InputModel {
void onPointHoverImage(PointerHoverEvent e) { void onPointHoverImage(PointerHoverEvent e) {
if (e.kind != ui.PointerDeviceKind.mouse) return; if (e.kind != ui.PointerDeviceKind.mouse) return;
if (!_isPhysicalMouse) { if (!isPhysicalMouse.value) {
_isPhysicalMouse = true; isPhysicalMouse.value = true;
} }
if (_isPhysicalMouse) { if (isPhysicalMouse.value) {
handleMouse(getEvent(e, 'mousemove')); handleMouse(getEvent(e, 'mousemove'));
} }
} }
@ -256,25 +255,25 @@ class InputModel {
void onPointDownImage(PointerDownEvent e) { void onPointDownImage(PointerDownEvent e) {
debugPrint("onPointDownImage"); debugPrint("onPointDownImage");
if (e.kind != ui.PointerDeviceKind.mouse) { if (e.kind != ui.PointerDeviceKind.mouse) {
if (_isPhysicalMouse) { if (isPhysicalMouse.value) {
_isPhysicalMouse = false; isPhysicalMouse.value = false;
} }
} }
if (_isPhysicalMouse) { if (isPhysicalMouse.value) {
handleMouse(getEvent(e, 'mousedown')); handleMouse(getEvent(e, 'mousedown'));
} }
} }
void onPointUpImage(PointerUpEvent e) { void onPointUpImage(PointerUpEvent e) {
if (e.kind != ui.PointerDeviceKind.mouse) return; if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) { if (isPhysicalMouse.value) {
handleMouse(getEvent(e, 'mouseup')); handleMouse(getEvent(e, 'mouseup'));
} }
} }
void onPointMoveImage(PointerMoveEvent e) { void onPointMoveImage(PointerMoveEvent e) {
if (e.kind != ui.PointerDeviceKind.mouse) return; if (e.kind != ui.PointerDeviceKind.mouse) return;
if (_isPhysicalMouse) { if (isPhysicalMouse.value) {
handleMouse(getEvent(e, 'mousemove')); handleMouse(getEvent(e, 'mousemove'));
} }
} }