Merge pull request #1470 from fufesou/flutter_cursors_cache
Flutter cursors cache
This commit is contained in:
		
						commit
						48998ded2e
					
				| @ -568,13 +568,16 @@ class ImagePaint extends StatelessWidget { | ||||
|               cursor: (cursorOverImage.isTrue && keyboardEnabled.isTrue) | ||||
|                   ? (remoteCursorMoved.isTrue | ||||
|                       ? SystemMouseCursors.none | ||||
|                       : (cursor.pngData != null | ||||
|                       : (cursor.cacheLinux != null | ||||
|                           ? FlutterCustomMemoryImageCursor( | ||||
|                               pixbuf: cursor.pngData!, | ||||
|                               hotx: cursor.hotx, | ||||
|                               hoty: cursor.hoty, | ||||
|                               imageWidth: (cursor.image!.width * s).toInt(), | ||||
|                               imageHeight: (cursor.image!.height * s).toInt(), | ||||
|                               pixbuf: cursor.cacheLinux!.data, | ||||
|                               key: cursor.cacheLinux!.key, | ||||
|                               hotx: cursor.cacheLinux!.hotx, | ||||
|                               hoty: cursor.cacheLinux!.hoty, | ||||
|                               imageWidth: | ||||
|                                   (cursor.cacheLinux!.width * s).toInt(), | ||||
|                               imageHeight: | ||||
|                                   (cursor.cacheLinux!.height * s).toInt(), | ||||
|                             ) | ||||
|                           : 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 | ||||
| class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> { | ||||
|   const PopupMenuChildrenItem({ | ||||
|   PopupMenuChildrenItem({ | ||||
|     key, | ||||
|     this.height = kMinInteractiveDimension, | ||||
|     this.padding, | ||||
| @ -43,6 +43,16 @@ class PopupMenuChildrenItem<T> extends mod_menu.PopupMenuEntry<T> { | ||||
| 
 | ||||
| class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>> | ||||
|     extends State<W> { | ||||
|   RxBool enabled = true.obs; | ||||
| 
 | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     if (widget.enabled != null) { | ||||
|       enabled.value = widget.enabled!.value; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @protected | ||||
|   void handleTap(T value) { | ||||
|     widget.onTap?.call(); | ||||
| @ -56,27 +66,25 @@ class MyPopupMenuItemState<T, W extends PopupMenuChildrenItem<T>> | ||||
|     TextStyle style = widget.textStyle ?? | ||||
|         popupMenuTheme.textStyle ?? | ||||
|         theme.textTheme.subtitle1!; | ||||
|     return Obx(() { | ||||
|       return mod_menu.PopupMenuButton<T>( | ||||
|         enabled: widget.enabled != null ? widget.enabled!.value : true, | ||||
|         position: widget.position, | ||||
|         offset: widget.offset, | ||||
|         onSelected: handleTap, | ||||
|         itemBuilder: widget.itemBuilder, | ||||
|         padding: EdgeInsets.zero, | ||||
|         child: AnimatedDefaultTextStyle( | ||||
|           style: style, | ||||
|           duration: kThemeChangeDuration, | ||||
|           child: Container( | ||||
|             alignment: AlignmentDirectional.centerStart, | ||||
|             constraints: BoxConstraints(minHeight: widget.height), | ||||
|             padding: | ||||
|                 widget.padding ?? const EdgeInsets.symmetric(horizontal: 16), | ||||
|             child: widget.child, | ||||
|     return Obx(() => mod_menu.PopupMenuButton<T>( | ||||
|           enabled: enabled.value, | ||||
|           position: widget.position, | ||||
|           offset: widget.offset, | ||||
|           onSelected: handleTap, | ||||
|           itemBuilder: widget.itemBuilder, | ||||
|           padding: EdgeInsets.zero, | ||||
|           child: AnimatedDefaultTextStyle( | ||||
|             style: style, | ||||
|             duration: kThemeChangeDuration, | ||||
|             child: Container( | ||||
|               alignment: AlignmentDirectional.centerStart, | ||||
|               constraints: BoxConstraints(minHeight: widget.height), | ||||
|               padding: | ||||
|                   widget.padding ?? const EdgeInsets.symmetric(horizontal: 16), | ||||
|               child: widget.child, | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
|         )); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @ -342,7 +350,7 @@ typedef SwitchSetter = Future<void> Function(bool); | ||||
| 
 | ||||
| abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> { | ||||
|   final String text; | ||||
|   final Rx<TextStyle>? textStyle; | ||||
|   Rx<TextStyle>? textStyle; | ||||
| 
 | ||||
|   MenuEntrySwitchBase({ | ||||
|     required this.text, | ||||
| @ -357,6 +365,11 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> { | ||||
|   @override | ||||
|   List<mod_menu.PopupMenuEntry<T>> build( | ||||
|       BuildContext context, MenuConfig conf) { | ||||
|     textStyle ??= const TextStyle( | ||||
|             color: Colors.black, | ||||
|             fontSize: MenuConfig.fontSize, | ||||
|             fontWeight: FontWeight.normal) | ||||
|         .obs; | ||||
|     return [ | ||||
|       mod_menu.PopupMenuItem( | ||||
|         padding: EdgeInsets.zero, | ||||
| @ -366,23 +379,10 @@ abstract class MenuEntrySwitchBase<T> extends MenuEntryBase<T> { | ||||
|               alignment: AlignmentDirectional.centerStart, | ||||
|               height: conf.height, | ||||
|               child: Row(children: [ | ||||
|                 () { | ||||
|                   if (textStyle != null) { | ||||
|                     final style = textStyle!; | ||||
|                     return Obx(() => Text( | ||||
|                           text, | ||||
|                           style: style.value, | ||||
|                         )); | ||||
|                   } else { | ||||
|                     return Text( | ||||
|                 Obx(() => Text( | ||||
|                       text, | ||||
|                       style: const TextStyle( | ||||
|                           color: Colors.black, | ||||
|                           fontSize: MenuConfig.fontSize, | ||||
|                           fontWeight: FontWeight.normal), | ||||
|                     ); | ||||
|                   } | ||||
|                 }(), | ||||
|                       style: textStyle!.value, | ||||
|                     )), | ||||
|                 Expanded( | ||||
|                     child: Align( | ||||
|                   alignment: Alignment.centerRight, | ||||
| @ -485,6 +485,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> { | ||||
|   @override | ||||
|   List<mod_menu.PopupMenuEntry<T>> build( | ||||
|       BuildContext context, MenuConfig conf) { | ||||
|     super.enabled ??= true.obs; | ||||
|     return [ | ||||
|       PopupMenuChildrenItem( | ||||
|         enabled: super.enabled, | ||||
| @ -500,9 +501,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> { | ||||
|           Obx(() => Text( | ||||
|                 text, | ||||
|                 style: TextStyle( | ||||
|                     color: (super.enabled != null ? super.enabled!.value : true) | ||||
|                         ? Colors.black | ||||
|                         : Colors.grey, | ||||
|                     color: super.enabled!.value ? Colors.black : Colors.grey, | ||||
|                     fontSize: MenuConfig.fontSize, | ||||
|                     fontWeight: FontWeight.normal), | ||||
|               )), | ||||
| @ -511,9 +510,7 @@ class MenuEntrySubMenu<T> extends MenuEntryBase<T> { | ||||
|             alignment: Alignment.centerRight, | ||||
|             child: Obx(() => Icon( | ||||
|                   Icons.keyboard_arrow_right, | ||||
|                   color: (super.enabled != null ? super.enabled!.value : true) | ||||
|                       ? conf.commonColor | ||||
|                       : Colors.grey, | ||||
|                   color: super.enabled!.value ? conf.commonColor : Colors.grey, | ||||
|                 )), | ||||
|           )) | ||||
|         ]), | ||||
| @ -537,35 +534,31 @@ class MenuEntryButton<T> extends MenuEntryBase<T> { | ||||
|         ); | ||||
| 
 | ||||
|   Widget _buildChild(BuildContext context, MenuConfig conf) { | ||||
|     return Obx(() { | ||||
|       bool enabled = true; | ||||
|       if (super.enabled != null) { | ||||
|         enabled = super.enabled!.value; | ||||
|       } | ||||
|       const enabledStyle = TextStyle( | ||||
|           color: Colors.black, | ||||
|           fontSize: MenuConfig.fontSize, | ||||
|           fontWeight: FontWeight.normal); | ||||
|       const disabledStyle = TextStyle( | ||||
|           color: Colors.grey, | ||||
|           fontSize: MenuConfig.fontSize, | ||||
|           fontWeight: FontWeight.normal); | ||||
|       return TextButton( | ||||
|         onPressed: enabled | ||||
|             ? () { | ||||
|                 if (super.dismissOnClicked && Navigator.canPop(context)) { | ||||
|                   Navigator.pop(context); | ||||
|     const enabledStyle = TextStyle( | ||||
|         color: Colors.black, | ||||
|         fontSize: MenuConfig.fontSize, | ||||
|         fontWeight: FontWeight.normal); | ||||
|     const disabledStyle = TextStyle( | ||||
|         color: Colors.grey, | ||||
|         fontSize: MenuConfig.fontSize, | ||||
|         fontWeight: FontWeight.normal); | ||||
|     super.enabled ??= true.obs; | ||||
|     return Obx(() => TextButton( | ||||
|           onPressed: super.enabled!.value | ||||
|               ? () { | ||||
|                   if (super.dismissOnClicked && Navigator.canPop(context)) { | ||||
|                     Navigator.pop(context); | ||||
|                   } | ||||
|                   proc(); | ||||
|                 } | ||||
|                 proc(); | ||||
|               } | ||||
|             : null, | ||||
|         child: Container( | ||||
|           alignment: AlignmentDirectional.centerStart, | ||||
|           constraints: BoxConstraints(minHeight: conf.height), | ||||
|           child: childBuilder(enabled ? enabledStyle : disabledStyle), | ||||
|         ), | ||||
|       ); | ||||
|     }); | ||||
|               : null, | ||||
|           child: Container( | ||||
|             alignment: AlignmentDirectional.centerStart, | ||||
|             constraints: BoxConstraints(minHeight: conf.height), | ||||
|             child: childBuilder( | ||||
|                 super.enabled!.value ? enabledStyle : disabledStyle), | ||||
|           ), | ||||
|         )); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
| @ -573,7 +566,6 @@ class MenuEntryButton<T> extends MenuEntryBase<T> { | ||||
|       BuildContext context, MenuConfig conf) { | ||||
|     return [ | ||||
|       mod_menu.PopupMenuItem( | ||||
|         enabled: super.enabled != null ? super.enabled!.value : true, | ||||
|         padding: EdgeInsets.zero, | ||||
|         height: conf.height, | ||||
|         child: _buildChild(context, conf), | ||||
|  | ||||
| @ -611,7 +611,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> { | ||||
|                 MenuEntryRadioOption(text: translate('Map mode'), value: 'map'), | ||||
|               ], | ||||
|           curOptionGetter: () async { | ||||
|             return await bind.sessionGetKeyboardName(id: widget.id) ?? 'legacy'; | ||||
|             return await bind.sessionGetKeyboardName(id: widget.id); | ||||
|           }, | ||||
|           optionSetter: (String oldValue, String newValue) async { | ||||
|             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:shared_preferences/shared_preferences.dart'; | ||||
| import 'package:tuple/tuple.dart'; | ||||
| import 'package:flutter_custom_cursor/flutter_custom_cursor.dart'; | ||||
| 
 | ||||
| import '../common.dart'; | ||||
| import '../common/shared_state.dart'; | ||||
| @ -351,7 +352,7 @@ class ImageModel with ChangeNotifier { | ||||
|         // my throw exception, because the listener maybe already dispose | ||||
|         update(image, tabBarHeight); | ||||
|       } 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 { | ||||
|   ui.Image? _image; | ||||
|   final _images = <int, Tuple3<ui.Image, double, double>>{}; | ||||
|   Uint8List? _pngData; | ||||
|   final _pngs = <int, Uint8List?>{}; | ||||
|   CursorData? _cacheLinux; | ||||
|   final _cacheMapLinux = <int, CursorData>{}; | ||||
|   double _x = -10000; | ||||
|   double _y = -10000; | ||||
|   double _hotx = 0; | ||||
| @ -609,7 +635,7 @@ class CursorModel with ChangeNotifier { | ||||
|   WeakReference<FFI> parent; | ||||
| 
 | ||||
|   ui.Image? get image => _image; | ||||
|   Uint8List? get pngData => _pngData; | ||||
|   CursorData? get cacheLinux => _cacheLinux; | ||||
| 
 | ||||
|   double get x => _x - _displayOriginX; | ||||
| 
 | ||||
| @ -623,6 +649,9 @@ class CursorModel with ChangeNotifier { | ||||
| 
 | ||||
|   CursorModel(this.parent); | ||||
| 
 | ||||
|   List<String> get cachedKeysLinux => | ||||
|       _cacheMapLinux.values.map((v) => v.key).toList(); | ||||
| 
 | ||||
|   // remote physical display coordinate | ||||
|   Rect getVisibleRect() { | ||||
|     final size = MediaQueryData.fromWindow(ui.window).size; | ||||
| @ -763,13 +792,7 @@ class CursorModel with ChangeNotifier { | ||||
|         if (parent.target?.id != pid) return; | ||||
|         _image = image; | ||||
|         _images[id] = Tuple3(image, _hotx, _hoty); | ||||
|         final data = await image.toByteData(format: ImageByteFormat.png); | ||||
|         if (data != null) { | ||||
|           _pngData = data.buffer.asUint8List(); | ||||
|         } else { | ||||
|           _pngData = null; | ||||
|         } | ||||
|         _pngs[id] = _pngData; | ||||
|         _updateCacheLinux(image, id, width, height); | ||||
|         try { | ||||
|           // my throw exception, because the listener maybe already dispose | ||||
|           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) { | ||||
|     _pngData = _pngs[int.parse(evt['id'])]; | ||||
|     _cacheLinux = _cacheMapLinux[int.parse(evt['id'])]; | ||||
|     final tmp = _images[int.parse(evt['id'])]; | ||||
|     if (tmp != null) { | ||||
|       _image = tmp.item1; | ||||
| @ -828,6 +871,17 @@ class CursorModel with ChangeNotifier { | ||||
|     _x = -10000; | ||||
|     _image = null; | ||||
|     _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']; | ||||
|       } | ||||
|       notifyListeners(); | ||||
|     } catch (e) {} | ||||
|     } catch (e) { | ||||
|       // | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @ -1230,7 +1286,7 @@ class FFI { | ||||
|   Future<Map<String, String>> getHttpHeaders() async { | ||||
|     return { | ||||
|       'Authorization': | ||||
|           'Bearer ' + await bind.mainGetLocalOption(key: 'access_token') | ||||
|           'Bearer ${await bind.mainGetLocalOption(key: 'access_token')}' | ||||
|     }; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -71,7 +71,7 @@ dependencies: | ||||
|     flutter_custom_cursor: | ||||
|         git: | ||||
|             url: https://github.com/Kingtous/rustdesk_flutter_custom_cursor | ||||
|             ref: 7fe78c139c711bafbae52d924e9caf18bd193e28 | ||||
|             ref: 9021e21de36c84edf01d5034f38eda580463163b | ||||
|     get: ^4.6.5 | ||||
|     visibility_detector: ^0.3.3 | ||||
|     contextmenu: ^3.0.0 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user