diff --git a/flutter/lib/desktop/pages/remote_page.dart b/flutter/lib/desktop/pages/remote_page.dart index 21cbe45b9..8d0de3b41 100644 --- a/flutter/lib/desktop/pages/remote_page.dart +++ b/flutter/lib/desktop/pages/remote_page.dart @@ -340,10 +340,8 @@ class ImagePaint extends StatelessWidget { return FlutterCustomMemoryImageCursor( pixbuf: cache.data, key: key, - // hotx: cache.hotx, - // hoty: cache.hoty, - hotx: 0, - hoty: 0, + hotx: cache.hotx, + hoty: cache.hoty, imageWidth: (cache.width * cache.scale).toInt(), imageHeight: (cache.height * cache.scale).toInt(), ); @@ -488,11 +486,19 @@ class CursorPaint extends StatelessWidget { final m = Provider.of<CursorModel>(context); final c = Provider.of<CanvasModel>(context); // final adjust = m.adjustForKeyboard(); + double hotx = m.hotx; + double hoty = m.hoty; + if (m.image == null) { + if (m.defaultCache != null) { + hotx = m.defaultCache!.hotx; + hoty = m.defaultCache!.hoty; + } + } return CustomPaint( painter: ImagePainter( - image: m.image, - x: m.x - m.hotx + c.x / c.scale, - y: m.y - m.hoty + c.y / c.scale, + image: m.image ?? m.defaultImage, + x: m.x - hotx + c.x / c.scale, + y: m.y - hoty + c.y / c.scale, scale: c.scale), ); } diff --git a/flutter/lib/mobile/pages/remote_page.dart b/flutter/lib/mobile/pages/remote_page.dart index 0662fce2b..5dd01264b 100644 --- a/flutter/lib/mobile/pages/remote_page.dart +++ b/flutter/lib/mobile/pages/remote_page.dart @@ -862,11 +862,19 @@ class CursorPaint extends StatelessWidget { final c = Provider.of<CanvasModel>(context); final adjust = gFFI.cursorModel.adjustForKeyboard(); var s = c.scale; + double hotx = m.hotx; + double hoty = m.hoty; + if (m.image == null) { + if (m.defaultCache != null) { + hotx = m.defaultCache!.hotx; + hoty = m.defaultCache!.hoty; + } + } return CustomPaint( painter: ImagePainter( - image: m.image, - x: m.x * s - m.hotx + c.x, - y: m.y * s - m.hoty + c.y - adjust, + image: m.image ?? m.defaultImage, + x: m.x * s - hotx + c.x, + y: m.y * s - hoty + c.y - adjust, scale: 1), ); } diff --git a/flutter/lib/models/model.dart b/flutter/lib/models/model.dart index 53a040eed..61185b016 100644 --- a/flutter/lib/models/model.dart +++ b/flutter/lib/models/model.dart @@ -721,8 +721,11 @@ class CursorData { height: (height * scale).toInt(), ) .getBytes(format: img2.Format.bgra); - hotx = (width * scale) / 2; - hoty = (height * scale) / 2; + if (hotx > 0 && hoty > 0) { + // default cursor data + hotx = (width * scale) / 2; + hoty = (height * scale) / 2; + } } } this.scale = scale; @@ -737,6 +740,7 @@ class CursorData { class CursorModel with ChangeNotifier { ui.Image? _image; + ui.Image? _defaultImage; final _images = <int, Tuple3<ui.Image, double, double>>{}; CursorData? _cache; final _defaultCacheId = -1; @@ -756,6 +760,7 @@ class CursorModel with ChangeNotifier { WeakReference<FFI> parent; ui.Image? get image => _image; + ui.Image? get defaultImage => _defaultImage; CursorData? get cache => _cache; CursorData? get defaultCache => _getDefaultCache(); @@ -771,30 +776,45 @@ class CursorModel with ChangeNotifier { DateTime.now().difference(_last_peer_mouse).inMilliseconds < kMouseControlTimeoutMSec; - CursorModel(this.parent); + CursorModel(this.parent) { + _getDefaultImage(); + _getDefaultCache(); + } Set<String> get cachedKeys => _cacheKeys; addKey(String key) => _cacheKeys.add(key); + Future<ui.Image?> _getDefaultImage() async { + if (_defaultImage == null) { + final defaultImg = defaultCursorImage!; + // This function is called only one time, no need to care about the performance. + Uint8List data = defaultImg.getBytes(format: img2.Format.rgba); + _defaultImage = await img.decodeImageFromPixels( + data, defaultImg.width, defaultImg.height, ui.PixelFormat.rgba8888); + } + return _defaultImage; + } + CursorData? _getDefaultCache() { if (_defaultCache == null) { + Uint8List data; if (Platform.isWindows) { - Uint8List data = defaultCursorImage!.getBytes(format: img2.Format.bgra); - _hotx = defaultCursorImage!.width / 2; - _hoty = defaultCursorImage!.height / 2; - - _defaultCache = CursorData( - peerId: id, - id: _defaultCacheId, - image: defaultCursorImage?.clone(), - scale: 1.0, - data: data, - hotx: _hotx, - hoty: _hoty, - width: defaultCursorImage!.width, - height: defaultCursorImage!.height, - ); + data = defaultCursorImage!.getBytes(format: img2.Format.bgra); + } else { + data = Uint8List.fromList(img2.encodePng(defaultCursorImage!)); } + double scale = 1.0; + _defaultCache = CursorData( + peerId: id, + id: _defaultCacheId, + image: defaultCursorImage?.clone(), + scale: scale, + data: data, + hotx: (defaultCursorImage!.width * scale) / 2, + hoty: (defaultCursorImage!.height * scale) / 2, + width: defaultCursorImage!.width, + height: defaultCursorImage!.height, + ); } return _defaultCache; } @@ -926,13 +946,15 @@ class CursorModel with ChangeNotifier { var height = int.parse(evt['height']); List<dynamic> colors = json.decode(evt['colors']); final rgba = Uint8List.fromList(colors.map((s) => s as int).toList()); - var pid = parent.target?.id; final image = await img.decodeImageFromPixels( rgba, width, height, ui.PixelFormat.rgba8888); - if (parent.target?.id != pid) return; _image = image; - _images[id] = Tuple3(image, _hotx, _hoty); - await _updateCache(image, id, width, height); + if (await _updateCache(image, id, width, height)) { + _images[id] = Tuple3(image, _hotx, _hoty); + } else { + _hotx = 0; + _hoty = 0; + } try { // my throw exception, because the listener maybe already dispose notifyListeners(); @@ -941,44 +963,33 @@ class CursorModel with ChangeNotifier { } } - _updateCache(ui.Image image, int id, int w, int h) async { - Uint8List? data; - img2.Image? image2; + Future<bool> _updateCache(ui.Image image, int id, int w, int h) async { + ui.ImageByteFormat imgFormat = ui.ImageByteFormat.png; if (Platform.isWindows) { - ByteData? data2 = - await image.toByteData(format: ui.ImageByteFormat.rawRgba); - if (data2 != null) { - data = data2.buffer.asUint8List(); - image2 = img2.Image.fromBytes(w, h, data); - } else { - data = defaultCursorImage?.getBytes(format: img2.Format.bgra); - image2 = defaultCursorImage?.clone(); - _hotx = defaultCursorImage!.width / 2; - _hoty = defaultCursorImage!.height / 2; - } - } else { - ByteData? data2 = await image.toByteData(format: ui.ImageByteFormat.png); - if (data2 != null) { - data = data2.buffer.asUint8List(); - } else { - data = Uint8List.fromList(img2.encodePng(defaultCursorImage!)); - _hotx = defaultCursorImage!.width / 2; - _hoty = defaultCursorImage!.height / 2; - } + imgFormat = ui.ImageByteFormat.rawRgba; } + ByteData? imgBytes = await image.toByteData(format: imgFormat); + if (imgBytes == null) { + return false; + } + + Uint8List? data = imgBytes.buffer.asUint8List(); _cache = CursorData( peerId: this.id, id: id, - image: image2, + image: Platform.isWindows ? img2.Image.fromBytes(w, h, data) : null, scale: 1.0, data: data, - hotx: _hotx, - hoty: _hoty, + hotx: 0, + hoty: 0, + // hotx: _hotx, + // hoty: _hoty, width: w, height: h, ); _cacheMap[id] = _cache!; + return true; } updateCursorId(Map<String, dynamic> evt) async {