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,159 +226,68 @@ class _RemotePageState extends State<RemotePage> {
clientClose(gFFI.dialogManager); clientClose(gFFI.dialogManager);
return false; return false;
}, },
child: getRawPointerAndKeyBody( child: getRawPointerAndKeyBody(Scaffold(
keyboard, // resizeToAvoidBottomInset: true,
Scaffold( floatingActionButton: !showActionButton
// resizeToAvoidBottomInset: true, ? null
floatingActionButton: !showActionButton : FloatingActionButton(
? null mini: !hideKeyboard,
: FloatingActionButton( child: Icon(
mini: !hideKeyboard, hideKeyboard ? Icons.expand_more : Icons.expand_less),
child: Icon( backgroundColor: MyTheme.accent,
hideKeyboard ? Icons.expand_more : Icons.expand_less), onPressed: () {
backgroundColor: MyTheme.accent, setState(() {
onPressed: () { if (hideKeyboard) {
setState(() { _showEdit = false;
if (hideKeyboard) { gFFI.invokeMethod("enable_soft_keyboard", false);
_showEdit = false; _mobileFocusNode.unfocus();
gFFI.invokeMethod("enable_soft_keyboard", false); _physicalFocusNode.requestFocus();
_mobileFocusNode.unfocus(); } else {
_physicalFocusNode.requestFocus(); _showBar = !_showBar;
} else { }
_showBar = !_showBar; });
} }),
}); bottomNavigationBar: _showBar && pi.displays.isNotEmpty
}), ? getBottomAppBar(keyboard)
bottomNavigationBar: _showBar && pi.displays.length > 0 : null,
? getBottomAppBar(keyboard) body: Overlay(
: null, initialEntries: [
body: Overlay( OverlayEntry(builder: (context) {
initialEntries: [ return Container(
OverlayEntry(builder: (context) { color: Colors.black,
return Container( child: isWebDesktop
color: Colors.black, ? getBodyForDesktopWithListener(keyboard)
child: isWebDesktop : SafeArea(child:
? getBodyForDesktopWithListener(keyboard) OrientationBuilder(builder: (ctx, orientation) {
: SafeArea(child: if (_currentOrientation != orientation) {
OrientationBuilder(builder: (ctx, orientation) { Timer(const Duration(milliseconds: 200), () {
if (_currentOrientation != orientation) { gFFI.dialogManager
Timer(const Duration(milliseconds: 200), () { .resetMobileActionsOverlay(ffi: gFFI);
gFFI.dialogManager _currentOrientation = orientation;
.resetMobileActionsOverlay(ffi: gFFI); gFFI.canvasModel.updateViewStyle();
_currentOrientation = orientation; });
gFFI.canvasModel.updateViewStyle(); }
}); return Obx(() => Container(
} color: MyTheme.canvasColor,
return Container( child: inputModel.isPhysicalMouse.value
color: MyTheme.canvasColor, ? getBodyForMobile()
child: _isPhysicalMouse : getBodyForMobileWithGesture()));
? getBodyForMobile() })));
: getBodyForMobileWithGesture()); })
}))); ],
}) ))),
],
))),
); );
} }
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; cursor: keyboard ? SystemMouseCursors.none : MouseCursor.defer,
if (!_isPhysicalMouse) { inputModel: inputModel,
setState(() { child: RawKeyFocusScope(
_isPhysicalMouse = true; focusNode: _physicalFocusNode,
}); inputModel: inputModel,
} child: child));
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,
child: FocusScope(
autofocus: true,
child: Focus(
autofocus: true,
canRequestFocus: true,
focusNode: _physicalFocusNode,
onKey: (data, e) {
final key = e.logicalKey;
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'));
} }
} }