diff --git a/lib/src/skin/skin_cursors.dart b/lib/src/skin/skin_cursors.dart index 0315ec9..b65541f 100644 --- a/lib/src/skin/skin_cursors.dart +++ b/lib/src/skin/skin_cursors.dart @@ -12,24 +12,27 @@ const winampChannel = MethodChannel("winamp"); /// name, so hover regions can switch to them. Best-effort: failures are /// ignored because the native side may not be ready yet. Future registerSkinCursors(ClassicSkin skin) async { - for (final entry in skin.cursors.entries) { - final png = await _cursorPng(entry.value.image); - try { - await winampChannel.invokeMethod( - "registerCursor", - { - "name": entry.key, - "png": png, - "hotspotX": entry.value.hotspotX, - "hotspotY": entry.value.hotspotY, - }, - ); - } on PlatformException { - // ignore: best-effort - } on MissingPluginException { - // ignore: best-effort - } - } + // IMPROVEMENT: Load and register all cursors in parallel instead of sequentially. + await Future.wait( + skin.cursors.entries.map((entry) async { + final png = await _cursorPng(entry.value.image); + try { + await winampChannel.invokeMethod( + "registerCursor", + { + "name": entry.key, + "png": png, + "hotspotX": entry.value.hotspotX, + "hotspotY": entry.value.hotspotY, + }, + ); + } on PlatformException { + // ignore: best-effort + } on MissingPluginException { + // ignore: best-effort + } + }), + ); } Future _cursorPng(ui.Image image) async { @@ -62,10 +65,9 @@ class SkinCursorsGate extends StatefulWidget { } class _SkinCursorsGateState extends State { - /// Serializes cursor swaps so a slow native call from a stale enter/exit - /// event (e.g. during a drag when the window moves under the cursor) can - /// never leave the wrong cursor stuck after the latest request. - int _cursorSeq = 0; + // Track the current cursor locally to prevent flooding the platform channel + String _activeCursorName = "arrow"; + bool _isHovering = false; @override void initState() { @@ -78,25 +80,42 @@ class _SkinCursorsGateState extends State { super.didUpdateWidget(oldWidget); if (oldWidget.skin != widget.skin) { unawaited(registerSkinCursors(widget.skin)); + + // If the skin changes while the mouse is already inside the widget, + // we need to re-evaluate what cursor should be showing. + if (_isHovering) { + _updateCursor(); + } } } + void _updateCursor() { + final hasCustom = widget.skin.cursors.containsKey("normal"); + _setCursor(hasCustom ? "normal" : "arrow"); + } + + void _setCursor(String name) { + if (_activeCursorName == name) return; + + _activeCursorName = name; + unawaited(setSkinCursor(name)); + } + @override Widget build(BuildContext context) { - final hasCustom = widget.skin.cursors.containsKey("normal"); return MouseRegion( - onEnter: (_) => _setCursor(hasCustom ? "normal" : "arrow"), - onExit: (_) => _setCursor("arrow"), + hitTestBehavior: HitTestBehavior.translucent, + // onHover is intentionally omitted. We only need to know when the + // mouse enters or exits the boundary to trigger the cursor change. + onEnter: (_) { + _isHovering = true; + _updateCursor(); + }, + onExit: (_) { + _isHovering = false; + _setCursor("arrow"); + }, child: widget.child, ); } - - Future _setCursor(String name) async { - final seq = ++_cursorSeq; - await setSkinCursor(name); - // A newer enter/exit won the race; leave that cursor in place. - if (seq != _cursorSeq) { - return; - } - } }