cursor: wait for a native cursor at the raster asked for, not any raster

A shape let its pixels go once it had a native cursor at some raster.
After a zoom, or on a display of another scale, an animation's frames
had native cursors at the old raster only; each was fetched again at
the new one, and a restore that finished after the next switch let its
pixels go at once, so the frames never got a native cursor at the new
raster and the cursor stayed on the one shown before. A lookup that
finds no native cursor at the raster asked for now counts the shape as
waiting for one, so a late restore keeps its pixels until it has one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
This commit is contained in:
rustdeskandClaude Opus 5.5 committed 2026-09-24 14:16:46 +08:00
1 parent a39fe47863
commit 1e4388771f
2 files changed
+34 -2

No files matched your search

+6 -2
View File
@@ -3617,8 +3617,12 @@ class CursorModel with ChangeNotifier {
static int _nextKeyScope = 0;
final int _keyScope = _nextKeyScope++;
String nativeKey(CursorData cache, double scale) =>
'${_keyScope}_${cache.updateGetKey(scale)}';
String nativeKey(CursorData cache, double scale) {
final key = '${_keyScope}_${cache.updateGetKey(scale)}';
// A native cursor at another raster does not hold the pixels this one is made from.
if (!_cacheKeys.contains(key)) _nativeIds.remove(cache.id);
return key;
}
// Native cursors stay for the session, one per raster of each shape, and only [clear]
// deletes them, as before; unlike the painted images they have no LRU, for now. A delete
+28
View File
@@ -239,6 +239,34 @@ void main() {
expect(registered.where((key) => key.contains('_B_')), isNotEmpty);
});
test('a zoom still gets native cursors for shapes that keep decoding late',
() async {
final cursor = ffi.cursorModel;
// An animation whose frames have native cursors at 1.0 only.
for (final (id, seed) in [('A', 1), ('B', 2)]) {
await _feed(ffi, id, size: 32, seed: seed);
buildCursorOfCache(cursor, 1.0, cursor.cache);
await _settle();
}
// At 0.5 each frame is fetched again, and each fetch answers after the next switch.
ffi.cursor.holdFetches = true;
for (var i = 0; i < 6; i++) {
_select(ffi, i.isEven ? 'A' : 'B');
ffi.cursor.answerFetches();
await _settle();
buildCursorOfCache(cursor, 0.5, cursor.cache);
await _settle();
}
expect(
registered
.where((key) => key.contains('_A_') && key.endsWith('_16_16')),
isNotEmpty);
expect(
registered
.where((key) => key.contains('_B_') && key.endsWith('_16_16')),
isNotEmpty);
});
test('shapes waiting for a native cursor keep their pixels, within the limit',
() async {
const max = CursorModel.kRecentShapes;