refactor RawPointerMouseRegion & RawKeyFocusScope
This commit is contained in:
parent
3dc9ecce29
commit
77fcf2d4fa
60
flutter/lib/common/widgets/remote_input.dart
Normal file
60
flutter/lib/common/widgets/remote_input.dart
Normal 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));
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:wakelock/wakelock.dart';
|
||||
import 'package:flutter_custom_cursor/flutter_custom_cursor.dart';
|
||||
|
||||
import '../../common/widgets/remote_input.dart';
|
||||
import '../widgets/remote_menubar.dart';
|
||||
import '../../common.dart';
|
||||
import '../../mobile/widgets/dialog.dart';
|
||||
@ -135,7 +136,13 @@ class _RemotePageState extends State<RemotePage>
|
||||
_ffi.dialogManager.setOverlayState(Overlay.of(context));
|
||||
return Container(
|
||||
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)));
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!_imageFocused) {
|
||||
_rawKeyFocusNode.requestFocus();
|
||||
@ -200,17 +193,6 @@ class _RemotePageState extends State<RemotePage>
|
||||
_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) {
|
||||
var paints = <Widget>[
|
||||
MouseRegion(onEnter: (evt) {
|
||||
@ -226,7 +208,12 @@ class _RemotePageState extends State<RemotePage>
|
||||
cursorOverImage: _cursorOverImage,
|
||||
keyboardEnabled: _keyboardEnabled,
|
||||
remoteCursorMoved: _remoteCursorMoved,
|
||||
listenerBuilder: _buildImageListener,
|
||||
listenerBuilder: (child) => RawPointerMouseRegion(
|
||||
onEnter: enterView,
|
||||
onExit: leaveView,
|
||||
inputModel: _ffi.inputModel,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}))
|
||||
];
|
||||
|
@ -7,11 +7,12 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hbb/mobile/widgets/gesture_help.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:wakelock/wakelock.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../consts.dart';
|
||||
import '../../common/widgets/remote_input.dart';
|
||||
import '../../models/input_model.dart';
|
||||
import '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
@ -26,7 +27,7 @@ class RemotePage extends StatefulWidget {
|
||||
final String id;
|
||||
|
||||
@override
|
||||
_RemotePageState createState() => _RemotePageState();
|
||||
State<RemotePage> createState() => _RemotePageState();
|
||||
}
|
||||
|
||||
class _RemotePageState extends State<RemotePage> {
|
||||
@ -44,9 +45,8 @@ class _RemotePageState extends State<RemotePage> {
|
||||
final FocusNode _mobileFocusNode = FocusNode();
|
||||
final FocusNode _physicalFocusNode = FocusNode();
|
||||
var _showEdit = false; // use soft keyboard
|
||||
var _isPhysicalMouse = false;
|
||||
|
||||
get inputModel => gFFI.inputModel;
|
||||
InputModel get inputModel => gFFI.inputModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -148,8 +148,8 @@ class _RemotePageState extends State<RemotePage> {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (oldValue.length > 0 &&
|
||||
newValue.length > 0 &&
|
||||
if (oldValue.isNotEmpty &&
|
||||
newValue.isNotEmpty &&
|
||||
oldValue[0] == '\1' &&
|
||||
newValue[0] != '\1') {
|
||||
// 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
|
||||
Widget build(BuildContext context) {
|
||||
final pi = Provider.of<FfiModel>(context).pi;
|
||||
@ -234,159 +226,68 @@ class _RemotePageState extends State<RemotePage> {
|
||||
clientClose(gFFI.dialogManager);
|
||||
return false;
|
||||
},
|
||||
child: getRawPointerAndKeyBody(
|
||||
keyboard,
|
||||
Scaffold(
|
||||
// resizeToAvoidBottomInset: true,
|
||||
floatingActionButton: !showActionButton
|
||||
? null
|
||||
: FloatingActionButton(
|
||||
mini: !hideKeyboard,
|
||||
child: Icon(
|
||||
hideKeyboard ? Icons.expand_more : Icons.expand_less),
|
||||
backgroundColor: MyTheme.accent,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
if (hideKeyboard) {
|
||||
_showEdit = false;
|
||||
gFFI.invokeMethod("enable_soft_keyboard", false);
|
||||
_mobileFocusNode.unfocus();
|
||||
_physicalFocusNode.requestFocus();
|
||||
} else {
|
||||
_showBar = !_showBar;
|
||||
}
|
||||
});
|
||||
}),
|
||||
bottomNavigationBar: _showBar && pi.displays.length > 0
|
||||
? getBottomAppBar(keyboard)
|
||||
: null,
|
||||
body: Overlay(
|
||||
initialEntries: [
|
||||
OverlayEntry(builder: (context) {
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: isWebDesktop
|
||||
? getBodyForDesktopWithListener(keyboard)
|
||||
: SafeArea(child:
|
||||
OrientationBuilder(builder: (ctx, orientation) {
|
||||
if (_currentOrientation != orientation) {
|
||||
Timer(const Duration(milliseconds: 200), () {
|
||||
gFFI.dialogManager
|
||||
.resetMobileActionsOverlay(ffi: gFFI);
|
||||
_currentOrientation = orientation;
|
||||
gFFI.canvasModel.updateViewStyle();
|
||||
});
|
||||
}
|
||||
return Container(
|
||||
color: MyTheme.canvasColor,
|
||||
child: _isPhysicalMouse
|
||||
? getBodyForMobile()
|
||||
: getBodyForMobileWithGesture());
|
||||
})));
|
||||
})
|
||||
],
|
||||
))),
|
||||
child: getRawPointerAndKeyBody(Scaffold(
|
||||
// resizeToAvoidBottomInset: true,
|
||||
floatingActionButton: !showActionButton
|
||||
? null
|
||||
: FloatingActionButton(
|
||||
mini: !hideKeyboard,
|
||||
child: Icon(
|
||||
hideKeyboard ? Icons.expand_more : Icons.expand_less),
|
||||
backgroundColor: MyTheme.accent,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
if (hideKeyboard) {
|
||||
_showEdit = false;
|
||||
gFFI.invokeMethod("enable_soft_keyboard", false);
|
||||
_mobileFocusNode.unfocus();
|
||||
_physicalFocusNode.requestFocus();
|
||||
} else {
|
||||
_showBar = !_showBar;
|
||||
}
|
||||
});
|
||||
}),
|
||||
bottomNavigationBar: _showBar && pi.displays.isNotEmpty
|
||||
? getBottomAppBar(keyboard)
|
||||
: null,
|
||||
body: Overlay(
|
||||
initialEntries: [
|
||||
OverlayEntry(builder: (context) {
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: isWebDesktop
|
||||
? getBodyForDesktopWithListener(keyboard)
|
||||
: SafeArea(child:
|
||||
OrientationBuilder(builder: (ctx, orientation) {
|
||||
if (_currentOrientation != orientation) {
|
||||
Timer(const Duration(milliseconds: 200), () {
|
||||
gFFI.dialogManager
|
||||
.resetMobileActionsOverlay(ffi: gFFI);
|
||||
_currentOrientation = orientation;
|
||||
gFFI.canvasModel.updateViewStyle();
|
||||
});
|
||||
}
|
||||
return Obx(() => Container(
|
||||
color: MyTheme.canvasColor,
|
||||
child: inputModel.isPhysicalMouse.value
|
||||
? getBodyForMobile()
|
||||
: getBodyForMobileWithGesture()));
|
||||
})));
|
||||
})
|
||||
],
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getRawPointerAndKeyBody(bool keyboard, Widget child) {
|
||||
return Listener(
|
||||
onPointerHover: (e) {
|
||||
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,
|
||||
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 getRawPointerAndKeyBody(Widget child) {
|
||||
final keyboard = gFFI.ffiModel.permissions['keyboard'] != false;
|
||||
return RawPointerMouseRegion(
|
||||
cursor: keyboard ? SystemMouseCursors.none : MouseCursor.defer,
|
||||
inputModel: inputModel,
|
||||
child: RawKeyFocusScope(
|
||||
focusNode: _physicalFocusNode,
|
||||
inputModel: inputModel,
|
||||
child: child));
|
||||
}
|
||||
|
||||
Widget getBottomAppBar(bool keyboard) {
|
||||
@ -685,7 +586,7 @@ class _RemotePageState extends State<RemotePage> {
|
||||
if (perms['keyboard'] != false) {
|
||||
if (pi.platform == 'Linux' || pi.sasEnabled) {
|
||||
more.add(PopupMenuItem<String>(
|
||||
child: Text(translate('Insert') + ' Ctrl + Alt + Del'),
|
||||
child: Text('${translate('Insert')} Ctrl + Alt + Del'),
|
||||
value: 'cad'));
|
||||
}
|
||||
more.add(PopupMenuItem<String>(
|
||||
@ -694,8 +595,8 @@ class _RemotePageState extends State<RemotePage> {
|
||||
await bind.sessionGetToggleOption(id: id, arg: 'privacy-mode') !=
|
||||
true) {
|
||||
more.add(PopupMenuItem<String>(
|
||||
child: Text(translate((gFFI.ffiModel.inputBlocked ? 'Unb' : 'B') +
|
||||
'lock user input')),
|
||||
child: Text(translate(
|
||||
'${gFFI.ffiModel.inputBlocked ? 'Unb' : 'B'}lock user input')),
|
||||
value: 'block-input'));
|
||||
}
|
||||
}
|
||||
@ -720,7 +621,7 @@ class _RemotePageState extends State<RemotePage> {
|
||||
} else if (value == 'block-input') {
|
||||
bind.sessionToggleOption(
|
||||
id: widget.id,
|
||||
value: (gFFI.ffiModel.inputBlocked ? 'un' : '') + 'block-input');
|
||||
value: '${gFFI.ffiModel.inputBlocked ? 'un' : ''}block-input');
|
||||
gFFI.ffiModel.inputBlocked = !gFFI.ffiModel.inputBlocked;
|
||||
} else if (value == 'refresh') {
|
||||
bind.sessionRefresh(id: widget.id);
|
||||
@ -845,9 +746,9 @@ class _RemotePageState extends State<RemotePage> {
|
||||
SizedBox(width: 9999),
|
||||
];
|
||||
for (var i = 1; i <= 12; ++i) {
|
||||
final name = 'F' + i.toString();
|
||||
final name = 'F$i';
|
||||
fn.add(wrap(name, () {
|
||||
inputModel.inputKey('VK_' + name);
|
||||
inputModel.inputKey('VK_$name');
|
||||
}));
|
||||
}
|
||||
final more = <Widget>[
|
||||
@ -920,7 +821,7 @@ class ImagePaint extends StatelessWidget {
|
||||
final adjust = gFFI.cursorModel.adjustForKeyboard();
|
||||
var s = c.scale;
|
||||
return CustomPaint(
|
||||
painter: new ImagePainter(
|
||||
painter: ImagePainter(
|
||||
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();
|
||||
var s = c.scale;
|
||||
return CustomPaint(
|
||||
painter: new ImagePainter(
|
||||
painter: ImagePainter(
|
||||
image: m.image,
|
||||
x: m.x * s - m.hotx + c.x,
|
||||
y: m.y * s - m.hoty + c.y - adjust,
|
||||
@ -960,7 +861,7 @@ class ImagePainter extends CustomPainter {
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (image == null) return;
|
||||
canvas.scale(scale, scale);
|
||||
canvas.drawImage(image!, new Offset(x, y), new Paint());
|
||||
canvas.drawImage(image!, Offset(x, y), Paint());
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -5,7 +5,6 @@ import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_core/src/get_main.dart';
|
||||
|
||||
import '../../models/model.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
@ -40,7 +39,7 @@ class InputModel {
|
||||
var command = false;
|
||||
|
||||
// mouse
|
||||
var _isPhysicalMouse = false;
|
||||
final isPhysicalMouse = false.obs;
|
||||
int _lastMouseDownButtons = 0;
|
||||
|
||||
get id => parent.target?.id ?? "";
|
||||
@ -245,10 +244,10 @@ class InputModel {
|
||||
|
||||
void onPointHoverImage(PointerHoverEvent e) {
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
if (!_isPhysicalMouse) {
|
||||
_isPhysicalMouse = true;
|
||||
if (!isPhysicalMouse.value) {
|
||||
isPhysicalMouse.value = true;
|
||||
}
|
||||
if (_isPhysicalMouse) {
|
||||
if (isPhysicalMouse.value) {
|
||||
handleMouse(getEvent(e, 'mousemove'));
|
||||
}
|
||||
}
|
||||
@ -256,25 +255,25 @@ class InputModel {
|
||||
void onPointDownImage(PointerDownEvent e) {
|
||||
debugPrint("onPointDownImage");
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) {
|
||||
if (_isPhysicalMouse) {
|
||||
_isPhysicalMouse = false;
|
||||
if (isPhysicalMouse.value) {
|
||||
isPhysicalMouse.value = false;
|
||||
}
|
||||
}
|
||||
if (_isPhysicalMouse) {
|
||||
if (isPhysicalMouse.value) {
|
||||
handleMouse(getEvent(e, 'mousedown'));
|
||||
}
|
||||
}
|
||||
|
||||
void onPointUpImage(PointerUpEvent e) {
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
if (_isPhysicalMouse) {
|
||||
if (isPhysicalMouse.value) {
|
||||
handleMouse(getEvent(e, 'mouseup'));
|
||||
}
|
||||
}
|
||||
|
||||
void onPointMoveImage(PointerMoveEvent e) {
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
if (_isPhysicalMouse) {
|
||||
if (isPhysicalMouse.value) {
|
||||
handleMouse(getEvent(e, 'mousemove'));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user