mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-14 00:11:01 +03:00
fix(cursor): align Web hotspots with rounded cursor images
This commit is contained in:
@@ -2920,8 +2920,8 @@ class CursorData {
|
|||||||
img2.encodePng(
|
img2.encodePng(
|
||||||
img2.copyResize(
|
img2.copyResize(
|
||||||
image,
|
image,
|
||||||
width: (width * scale).toInt(),
|
width: isWeb ? (width * scale).round() : (width * scale).toInt(),
|
||||||
height: (height * scale).toInt(),
|
height: isWeb ? (height * scale).round() : (height * scale).toInt(),
|
||||||
interpolation: img2.Interpolation.average,
|
interpolation: img2.Interpolation.average,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -2932,6 +2932,11 @@ class CursorData {
|
|||||||
this.scale = scale;
|
this.scale = scale;
|
||||||
hotx = hotxOrigin * scale;
|
hotx = hotxOrigin * scale;
|
||||||
hoty = hotyOrigin * scale;
|
hoty = hotyOrigin * scale;
|
||||||
|
if (isWeb) {
|
||||||
|
// CSS hotspots must follow the actual rounded PNG dimensions.
|
||||||
|
hotx = hotxOrigin * (width * scale).round() / width;
|
||||||
|
hoty = hotyOrigin * (height * scale).round() / height;
|
||||||
|
}
|
||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ class CursorManager {
|
|||||||
'cursor',
|
'cursor',
|
||||||
jsonEncode({
|
jsonEncode({
|
||||||
'url': cursorData.url,
|
'url': cursorData.url,
|
||||||
'hotx': cursorData.hotX.toInt(),
|
'hotx': cursorData.hotX.round(),
|
||||||
'hoty': cursorData.hotY.toInt(),
|
'hoty': cursorData.hotY.round(),
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -116,8 +116,8 @@ MouseCursor buildCursorOfCache(
|
|||||||
CursorManager.instance.registerCursor(CursorData(
|
CursorManager.instance.registerCursor(CursorData(
|
||||||
key: key,
|
key: key,
|
||||||
url: 'data:image/rgba;base64,${base64Encode(data)}',
|
url: 'data:image/rgba;base64,${base64Encode(data)}',
|
||||||
width: (cache.width * cache.scale).toInt(),
|
width: (cache.width * cache.scale).round(),
|
||||||
height: (cache.height * cache.scale).toInt(),
|
height: (cache.height * cache.scale).round(),
|
||||||
hotX: cache.hotx,
|
hotX: cache.hotx,
|
||||||
hotY: cache.hoty));
|
hotY: cache.hoty));
|
||||||
cursor.addKey(key);
|
cursor.addKey(key);
|
||||||
|
|||||||
74
flutter/test/cursor_web_test.dart
Normal file
74
flutter/test/cursor_web_test.dart
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
@TestOn('browser')
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:js' as js;
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter_hbb/models/model.dart' as model;
|
||||||
|
import 'package:flutter_hbb/web/custom_cursor.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
|
||||||
|
class _CursorModel extends Fake implements model.CursorModel {
|
||||||
|
@override
|
||||||
|
final Set<String> cachedKeys = {};
|
||||||
|
@override
|
||||||
|
void addKey(String key) => cachedKeys.add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
test('Web cursor aligns CSS hotspots with rounded PNG dimensions', () async {
|
||||||
|
Map<String, dynamic>? registered;
|
||||||
|
final original = js.context['setByName'];
|
||||||
|
js.context['setByName'] = js.allowInterop((String name, String value) {
|
||||||
|
expect(name, 'cursor');
|
||||||
|
registered = jsonDecode(value) as Map<String, dynamic>;
|
||||||
|
});
|
||||||
|
addTearDown(() => js.context['setByName'] = original);
|
||||||
|
final recorder = ui.PictureRecorder();
|
||||||
|
ui.Canvas(recorder).drawColor(const ui.Color(0xffffffff), ui.BlendMode.src);
|
||||||
|
final picture = recorder.endRecording();
|
||||||
|
final nativeImage = await picture.toImage(48, 48);
|
||||||
|
picture.dispose();
|
||||||
|
addTearDown(nativeImage.dispose);
|
||||||
|
for (final (hotspot, scale, side, expected) in [
|
||||||
|
((7.0, 7.0), 633 / 1600, 19, (3, 3)),
|
||||||
|
((21.0, 23.0), 633 / 1600, 19, (8, 9)),
|
||||||
|
((22.0, 22.0), 633 / 1600, 19, (9, 9)),
|
||||||
|
((21.0, 23.0), 0.05, 12, (5, 6)),
|
||||||
|
((21.0, 23.0), 0.5, 24, (11, 12)),
|
||||||
|
((7.0, 7.0), 1.0, 48, (7, 7)),
|
||||||
|
]) {
|
||||||
|
final cache = _data(nativeImage, hotspot);
|
||||||
|
final session =
|
||||||
|
buildCursorOfCache(_CursorModel(), scale, cache).createSession(1);
|
||||||
|
await session.activate();
|
||||||
|
final uri = Uri.parse(registered!['url'] as String);
|
||||||
|
final bitmap = img.decodePng(uri.data!.contentAsBytes())!;
|
||||||
|
expect((bitmap.width, bitmap.height), (side, side));
|
||||||
|
expect((registered!['hotx'], registered!['hoty']), expected);
|
||||||
|
session.dispose();
|
||||||
|
await deleteCustomCursor(cache.updateGetKey(scale));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
model.CursorData _data(ui.Image nativeImage, (double, double) hotspot) {
|
||||||
|
final image = img.Image(width: 48, height: 48, numChannels: 4);
|
||||||
|
img.fill(image, color: img.ColorRgba8(255, 255, 255, 255));
|
||||||
|
return model.CursorData(
|
||||||
|
peerId: 'web-cursor-test',
|
||||||
|
id: '$hotspot',
|
||||||
|
image: image,
|
||||||
|
nativeImage: nativeImage,
|
||||||
|
scale: 1,
|
||||||
|
data: Uint8List.fromList(img.encodePng(image)),
|
||||||
|
hotxOrigin: hotspot.$1,
|
||||||
|
hotyOrigin: hotspot.$2,
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user