Merge pull request from fufesou/flutter_cursors_cache

Flutter cursors cache
This commit is contained in:
RustDesk 2022-09-08 09:52:43 +08:00 committed by GitHub
commit 48998ded2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 95 deletions

@ -568,13 +568,16 @@ class ImagePaint extends StatelessWidget {
cursor: (cursorOverImage.isTrue && keyboardEnabled.isTrue) cursor: (cursorOverImage.isTrue && keyboardEnabled.isTrue)
? (remoteCursorMoved.isTrue ? (remoteCursorMoved.isTrue
? SystemMouseCursors.none ? SystemMouseCursors.none
: (cursor.pngData != null : (cursor.cacheLinux != null
? FlutterCustomMemoryImageCursor( ? FlutterCustomMemoryImageCursor(
pixbuf: cursor.pngData!, pixbuf: cursor.cacheLinux!.data,
hotx: cursor.hotx, key: cursor.cacheLinux!.key,
hoty: cursor.hoty, hotx: cursor.cacheLinux!.hotx,
imageWidth: (cursor.image!.width * s).toInt(), hoty: cursor.cacheLinux!.hoty,
imageHeight: (cursor.image!.height * s).toInt(), imageWidth:
(cursor.cacheLinux!.width * s).toInt(),
imageHeight:
(cursor.cacheLinux!.height * s).toInt(),
) )
: MouseCursor.defer)) : MouseCursor.defer))
: MouseCursor.defer, : MouseCursor.defer,

@ -8,7 +8,7 @@ import './material_mod_popup_menu.dart' as mod_menu;
// https://stackoverflow.com/questions/68318314/flutter-popup-menu-inside-popup-menu // https://stackoverflow.com/questions/68318314/flutter-popup-menu-inside-popup-menu
class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> { class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> {
const PopupMenuChildrenItem({ PopupMenuChildrenItem({
key, key,
this.height = kMinInteractiveDimension, this.height = kMinInteractiveDimension,
this.padding, this.padding,
@ -43,6 +43,16 @@ class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> {
class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>> class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
extends State<W> { extends State<W> {
RxBool enabled = true.obs;
@override
void initState() {
super.initState();
if (widget.enabled != null) {
enabled.value = widget.enabled!.value;
}
}
@protected @protected
void handleTap(T value) { void handleTap(T value) {
widget.onTap?.call(); widget.onTap?.call();
@ -56,9 +66,8 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
TextStyle style = widget.textStyle ?? TextStyle style = widget.textStyle ??
popupMenuTheme.textStyle ?? popupMenuTheme.textStyle ??
theme.textTheme.subtitle1!; theme.textTheme.subtitle1!;
return Obx(() { return Obx(() => mod_menu.PopupMenuButton<T>(
return mod_menu.PopupMenuButton<T>( enabled: enabled.value,
enabled: widget.enabled != null ? widget.enabled!.value : true,
position: widget.position, position: widget.position,
offset: widget.offset, offset: widget.offset,
onSelected: handleTap, onSelected: handleTap,
@ -75,8 +84,7 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>>
child: widget.child, child: widget.child,
), ),
), ),
); ));
});
} }
} }
@ -342,7 +350,7 @@ 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;
final Rx<TextStyle>? textStyle; Rx<TextStyle>? textStyle;
MenuEntrySwitchBase({ MenuEntrySwitchBase({
required this.text, required this.text,
@ -357,6 +365,11 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
@override @override
List<mod_menu.PopupMenuEntry<T>> build( List<mod_menu.PopupMenuEntry<T>> build(
BuildContext context, MenuConfig conf) { BuildContext context, MenuConfig conf) {
textStyle ??= const TextStyle(
color: Colors.black,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal)
.obs;
return [ return [
mod_menu.PopupMenuItem( mod_menu.PopupMenuItem(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -366,23 +379,10 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> {
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
height: conf.height, height: conf.height,
child: Row(children: [ child: Row(children: [
() { Obx(() => Text(
if (textStyle != null) {
final style = textStyle!;
return Obx(() => Text(
text, text,
style: style.value, style: textStyle!.value,
)); )),
} else {
return Text(
text,
style: const TextStyle(
color: Colors.black,
fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal),
);
}
}(),
Expanded( Expanded(
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
@ -485,6 +485,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
@override @override
List<mod_menu.PopupMenuEntry<T>> build( List<mod_menu.PopupMenuEntry<T>> build(
BuildContext context, MenuConfig conf) { BuildContext context, MenuConfig conf) {
super.enabled ??= true.obs;
return [ return [
PopupMenuChildrenItem( PopupMenuChildrenItem(
enabled: super.enabled, enabled: super.enabled,
@ -500,9 +501,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
Obx(() => Text( Obx(() => Text(
text, text,
style: TextStyle( style: TextStyle(
color: (super.enabled != null ? super.enabled!.value : true) color: super.enabled!.value ? Colors.black : Colors.grey,
? Colors.black
: Colors.grey,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal), fontWeight: FontWeight.normal),
)), )),
@ -511,9 +510,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Obx(() => Icon( child: Obx(() => Icon(
Icons.keyboard_arrow_right, Icons.keyboard_arrow_right,
color: (super.enabled != null ? super.enabled!.value : true) color: super.enabled!.value ? conf.commonColor : Colors.grey,
? conf.commonColor
: Colors.grey,
)), )),
)) ))
]), ]),
@ -537,11 +534,6 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
); );
Widget _buildChild(BuildContext context, MenuConfig conf) { Widget _buildChild(BuildContext context, MenuConfig conf) {
return Obx(() {
bool enabled = true;
if (super.enabled != null) {
enabled = super.enabled!.value;
}
const enabledStyle = TextStyle( const enabledStyle = TextStyle(
color: Colors.black, color: Colors.black,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
@ -550,8 +542,9 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
color: Colors.grey, color: Colors.grey,
fontSize: MenuConfig.fontSize, fontSize: MenuConfig.fontSize,
fontWeight: FontWeight.normal); fontWeight: FontWeight.normal);
return TextButton( super.enabled ??= true.obs;
onPressed: enabled return Obx(() => TextButton(
onPressed: super.enabled!.value
? () { ? () {
if (super.dismissOnClicked && Navigator.canPop(context)) { if (super.dismissOnClicked && Navigator.canPop(context)) {
Navigator.pop(context); Navigator.pop(context);
@ -562,10 +555,10 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
child: Container( child: Container(
alignment: AlignmentDirectional.centerStart, alignment: AlignmentDirectional.centerStart,
constraints: BoxConstraints(minHeight: conf.height), constraints: BoxConstraints(minHeight: conf.height),
child: childBuilder(enabled ? enabledStyle : disabledStyle), child: childBuilder(
super.enabled!.value ? enabledStyle : disabledStyle),
), ),
); ));
});
} }
@override @override
@ -573,7 +566,6 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
BuildContext context, MenuConfig conf) { BuildContext context, MenuConfig conf) {
return [ return [
mod_menu.PopupMenuItem( mod_menu.PopupMenuItem(
enabled: super.enabled != null ? super.enabled!.value : true,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
height: conf.height, height: conf.height,
child: _buildChild(context, conf), child: _buildChild(context, conf),

@ -611,7 +611,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'), MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
], ],
curOptionGetter: () async { curOptionGetter: () async {
return await bind.sessionGetKeyboardName(id: widget.id) ?? 'legacy'; return await bind.sessionGetKeyboardName(id: widget.id);
}, },
optionSetter: (String oldValue, String newValue) async { optionSetter: (String oldValue, String newValue) async {
await bind.sessionSetKeyboardMode( await bind.sessionSetKeyboardMode(

@ -17,6 +17,7 @@ import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/models/user_model.dart'; import 'package:flutter_hbb/models/user_model.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:flutter_custom_cursor/flutter_custom_cursor.dart';
import '../common.dart'; import '../common.dart';
import '../common/shared_state.dart'; import '../common/shared_state.dart';
@ -351,7 +352,7 @@ class ImageModel with ChangeNotifier {
// my throw exception, because the listener maybe already dispose // my throw exception, because the listener maybe already dispose
update(image, tabBarHeight); update(image, tabBarHeight);
} catch (e) { } catch (e) {
print('update image: $e'); debugPrint('update image: $e');
} }
}); });
} }
@ -594,11 +595,36 @@ class CanvasModel with ChangeNotifier {
} }
} }
// data for cursor
class CursorData {
final String peerId;
final int id;
final Uint8List? data;
final double hotx;
final double hoty;
final int width;
final int height;
late String key;
CursorData({
required this.peerId,
required this.id,
required this.data,
required this.hotx,
required this.hoty,
required this.width,
required this.height,
}) {
key =
'${peerId}_${id}_${(hotx * 10e6).round().toInt()}_${(hoty * 10e6).round().toInt()}_${width}_$height';
}
}
class CursorModel with ChangeNotifier { class CursorModel with ChangeNotifier {
ui.Image? _image; ui.Image? _image;
final _images = <int, Tuple3<ui.Image, double, double>>{}; final _images = <int, Tuple3<ui.Image, double, double>>{};
Uint8List? _pngData; CursorData? _cacheLinux;
final _pngs = <int, Uint8List?>{}; final _cacheMapLinux = <int, CursorData>{};
double _x = -10000; double _x = -10000;
double _y = -10000; double _y = -10000;
double _hotx = 0; double _hotx = 0;
@ -609,7 +635,7 @@ class CursorModel with ChangeNotifier {
WeakReference<FFI> parent; WeakReference<FFI> parent;
ui.Image? get image => _image; ui.Image? get image => _image;
Uint8List? get pngData => _pngData; CursorData? get cacheLinux => _cacheLinux;
double get x => _x - _displayOriginX; double get x => _x - _displayOriginX;
@ -623,6 +649,9 @@ class CursorModel with ChangeNotifier {
CursorModel(this.parent); CursorModel(this.parent);
List<String> get cachedKeysLinux =>
_cacheMapLinux.values.map((v) => v.key).toList();
// remote physical display coordinate // remote physical display coordinate
Rect getVisibleRect() { Rect getVisibleRect() {
final size = MediaQueryData.fromWindow(ui.window).size; final size = MediaQueryData.fromWindow(ui.window).size;
@ -763,13 +792,7 @@ class CursorModel with ChangeNotifier {
if (parent.target?.id != pid) return; if (parent.target?.id != pid) return;
_image = image; _image = image;
_images[id] = Tuple3(image, _hotx, _hoty); _images[id] = Tuple3(image, _hotx, _hoty);
final data = await image.toByteData(format: ImageByteFormat.png); _updateCacheLinux(image, id, width, height);
if (data != null) {
_pngData = data.buffer.asUint8List();
} else {
_pngData = null;
}
_pngs[id] = _pngData;
try { try {
// my throw exception, because the listener maybe already dispose // my throw exception, because the listener maybe already dispose
notifyListeners(); notifyListeners();
@ -780,8 +803,28 @@ class CursorModel with ChangeNotifier {
}); });
} }
void _updateCacheLinux(ui.Image image, int id, int w, int h) async {
final data = await image.toByteData(format: ImageByteFormat.png);
late Uint8List? dataLinux;
if (data != null) {
dataLinux = data.buffer.asUint8List();
} else {
dataLinux = null;
}
_cacheLinux = CursorData(
peerId: this.id,
data: dataLinux,
id: id,
hotx: _hotx,
hoty: _hoty,
width: w,
height: h,
);
_cacheMapLinux[id] = _cacheLinux!;
}
void updateCursorId(Map<String, dynamic> evt) { void updateCursorId(Map<String, dynamic> evt) {
_pngData = _pngs[int.parse(evt['id'])]; _cacheLinux = _cacheMapLinux[int.parse(evt['id'])];
final tmp = _images[int.parse(evt['id'])]; final tmp = _images[int.parse(evt['id'])];
if (tmp != null) { if (tmp != null) {
_image = tmp.item1; _image = tmp.item1;
@ -828,6 +871,17 @@ class CursorModel with ChangeNotifier {
_x = -10000; _x = -10000;
_image = null; _image = null;
_images.clear(); _images.clear();
_clearCacheLinux();
_cacheLinux = null;
_cacheMapLinux.clear();
}
void _clearCacheLinux() {
final cachedKeys = [...cachedKeysLinux];
for (var key in cachedKeys) {
customCursorController.freeCache(key);
}
} }
} }
@ -871,7 +925,9 @@ class QualityMonitorModel with ChangeNotifier {
_data.codecFormat = evt['codec_format']; _data.codecFormat = evt['codec_format'];
} }
notifyListeners(); notifyListeners();
} catch (e) {} } catch (e) {
//
}
} }
} }
@ -1230,7 +1286,7 @@ class FFI {
Future<Map<String, String>> getHttpHeaders() async { Future<Map<String, String>> getHttpHeaders() async {
return { return {
'Authorization': 'Authorization':
'Bearer ' + await bind.mainGetLocalOption(key: 'access_token') 'Bearer ${await bind.mainGetLocalOption(key: 'access_token')}'
}; };
} }
} }

@ -71,7 +71,7 @@ dependencies:
flutter_custom_cursor: flutter_custom_cursor:
git: git:
url: https://github.com/Kingtous/rustdesk_flutter_custom_cursor url: https://github.com/Kingtous/rustdesk_flutter_custom_cursor
ref: 7fe78c139c711bafbae52d924e9caf18bd193e28 ref: 9021e21de36c84edf01d5034f38eda580463163b
get: ^4.6.5 get: ^4.6.5
visibility_detector: ^0.3.3 visibility_detector: ^0.3.3
contextmenu: ^3.0.0 contextmenu: ^3.0.0