From 5ab381bcfadd365dab40562383cda5e3eca963ea Mon Sep 17 00:00:00 2001 From: Ortes Date: Wed, 8 Jul 2026 20:09:46 -0400 Subject: [PATCH 1/4] feat: auto-hide the mouse cursor while idle in fullscreen Toggle the root MouseRegion's cursor to SystemMouseCursors.none once the controls auto-hide in fullscreen, and restore it on mouse movement (the existing onHover handler already reveals the controls). Works on web and desktop, no-op on pointerless devices. Gated by the new ChewieController.hideCursorInFullScreen option (default true). --- lib/src/chewie_player.dart | 10 ++++++++++ lib/src/cupertino/cupertino_controls.dart | 9 +++++++++ lib/src/material/material_controls.dart | 9 +++++++++ lib/src/material/material_desktop_controls.dart | 9 +++++++++ 4 files changed, 37 insertions(+) diff --git a/lib/src/chewie_player.dart b/lib/src/chewie_player.dart index 7ffa295b2..4dc8ffcfb 100644 --- a/lib/src/chewie_player.dart +++ b/lib/src/chewie_player.dart @@ -335,6 +335,7 @@ class ChewieController extends ChangeNotifier { this.hideControlsTimer = defaultHideControlsTimer, this.controlsSafeAreaMinimum = EdgeInsets.zero, this.pauseOnBackgroundTap = false, + this.hideCursorInFullScreen = true, }) : assert( playbackSpeeds.every((speed) => speed > 0), 'The playbackSpeeds values must all be greater than 0', @@ -394,6 +395,7 @@ class ChewieController extends ChangeNotifier { )? routePageBuilder, bool? pauseOnBackgroundTap, + bool? hideCursorInFullScreen, }) { return ChewieController( draggableProgressBar: draggableProgressBar ?? this.draggableProgressBar, @@ -458,6 +460,8 @@ class ChewieController extends ChangeNotifier { progressIndicatorDelay: progressIndicatorDelay ?? this.progressIndicatorDelay, pauseOnBackgroundTap: pauseOnBackgroundTap ?? this.pauseOnBackgroundTap, + hideCursorInFullScreen: + hideCursorInFullScreen ?? this.hideCursorInFullScreen, ); } @@ -630,6 +634,12 @@ class ChewieController extends ChangeNotifier { /// Defines if the player should pause when the background is tapped final bool pauseOnBackgroundTap; + /// Whether the mouse cursor auto-hides together with the controls while in + /// fullscreen (and reappears on mouse movement), like most video players. + /// Has no effect outside fullscreen or on devices without a pointer. + /// Defaults to `true`. + final bool hideCursorInFullScreen; + static ChewieController of(BuildContext context) { final chewieControllerProvider = context .dependOnInheritedWidgetOfExactType()!; diff --git a/lib/src/cupertino/cupertino_controls.dart b/lib/src/cupertino/cupertino_controls.dart index 201f236f0..1ac59465f 100644 --- a/lib/src/cupertino/cupertino_controls.dart +++ b/lib/src/cupertino/cupertino_controls.dart @@ -56,6 +56,14 @@ class _CupertinoControlsState extends State ChewieController get chewieController => _chewieController!; ChewieController? _chewieController; + // Hides the mouse cursor along with the controls while idle in fullscreen. + MouseCursor get _idleCursor => + chewieController.hideCursorInFullScreen && + chewieController.isFullScreen && + notifier.hideStuff + ? SystemMouseCursors.none + : MouseCursor.defer; + @override void initState() { super.initState(); @@ -86,6 +94,7 @@ class _CupertinoControlsState extends State final buttonPadding = orientation == Orientation.portrait ? 16.0 : 24.0; return MouseRegion( + cursor: _idleCursor, onHover: (_) => _cancelAndRestartTimer(), child: GestureDetector( onTap: () => _cancelAndRestartTimer(), diff --git a/lib/src/material/material_controls.dart b/lib/src/material/material_controls.dart index 3d43a1ab8..b6dc2ae01 100644 --- a/lib/src/material/material_controls.dart +++ b/lib/src/material/material_controls.dart @@ -50,6 +50,14 @@ class _MaterialControlsState extends State // We know that _chewieController is set in didChangeDependencies ChewieController get chewieController => _chewieController!; + // Hides the mouse cursor along with the controls while idle in fullscreen. + MouseCursor get _idleCursor => + chewieController.hideCursorInFullScreen && + chewieController.isFullScreen && + notifier.hideStuff + ? SystemMouseCursors.none + : MouseCursor.defer; + @override void initState() { super.initState(); @@ -67,6 +75,7 @@ class _MaterialControlsState extends State } return MouseRegion( + cursor: _idleCursor, onHover: (_) { _cancelAndRestartTimer(); }, diff --git a/lib/src/material/material_desktop_controls.dart b/lib/src/material/material_desktop_controls.dart index acdd4a8b0..0167a2650 100644 --- a/lib/src/material/material_desktop_controls.dart +++ b/lib/src/material/material_desktop_controls.dart @@ -52,6 +52,14 @@ class _MaterialDesktopControlsState extends State // We know that _chewieController is set in didChangeDependencies ChewieController get chewieController => _chewieController!; + // Hides the mouse cursor along with the controls while idle in fullscreen. + MouseCursor get _idleCursor => + chewieController.hideCursorInFullScreen && + chewieController.isFullScreen && + notifier.hideStuff + ? SystemMouseCursors.none + : MouseCursor.defer; + @override void initState() { super.initState(); @@ -91,6 +99,7 @@ class _MaterialDesktopControlsState extends State focusNode: _focusNode, onKeyEvent: _handleKeyPress, child: MouseRegion( + cursor: _idleCursor, onHover: (_) { _focusNode.requestFocus(); _cancelAndRestartTimer(); From 46e0964e031a57d128d3236faa7e46c3b0076b6b Mon Sep 17 00:00:00 2001 From: Ortes Date: Wed, 8 Jul 2026 20:09:46 -0400 Subject: [PATCH 2/4] docs: changelog entry for hideCursorInFullScreen --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6a5997aa..2731be237 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [Unreleased] +* ⬆️ Auto-hide the mouse cursor along with the controls while idle in fullscreen, and show it again on mouse movement (works on web and desktop). Configurable via `ChewieController.hideCursorInFullScreen` (default `true`). Thanks [Ortes](https://github.com/Ortes). + ## [1.14.1] * 🛠️ [#945](https://github.com/fluttercommunity/chewie/pull/945): Flutter 3.38 downgrade. Thanks [diegotori](https://github.com/diegotori). * Library now supports Flutter and Dart versions `3.38.0` and `3.10` or higher respectively, restoring previous compatibility. From c900334f21e6a5743d2284b02d910adabe34018d Mon Sep 17 00:00:00 2001 From: Ortes Date: Wed, 8 Jul 2026 20:09:46 -0400 Subject: [PATCH 3/4] test: cover hideCursorInFullScreen default, copyWith and idle gate --- test/hide_cursor_test.dart | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 test/hide_cursor_test.dart diff --git a/test/hide_cursor_test.dart b/test/hide_cursor_test.dart new file mode 100644 index 000000000..3edca0f08 --- /dev/null +++ b/test/hide_cursor_test.dart @@ -0,0 +1,57 @@ +import 'package:chewie/chewie.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:video_player/video_player.dart'; + +const _src = + 'https://assets.mixkit.co/videos/preview/mixkit-spinning-around-the-earth-29351-large.mp4'; + +ChewieController _controller() { + return ChewieController( + videoPlayerController: VideoPlayerController.networkUrl(Uri.parse(_src)), + autoPlay: false, + looping: false, + customControls: const MaterialDesktopControls(), + ); +} + +Finder _hiddenCursor() => find.byWidgetPredicate( + (w) => w is MouseRegion && w.cursor == SystemMouseCursors.none, +); + +void main() { + testWidgets( + 'cursor is never hidden while not in fullscreen, even when idle', + (tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold(body: Chewie(controller: _controller())), + ), + ); + await tester.pump(); + + // Drive a mouse hover so the controls arm their auto-hide timer, then let + // the player go idle (controls hidden). + final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(location: tester.getCenter(find.byType(Chewie))); + addTearDown(gesture.removePointer); + await gesture.moveTo(tester.getCenter(find.byType(Chewie))); + await tester.pump(const Duration(seconds: 4)); + + // Idle outside fullscreen must not hide the cursor. + expect(_hiddenCursor(), findsNothing); + }, + ); + + test('hideCursorInFullScreen defaults to true and survives copyWith', () { + final controller = ChewieController( + videoPlayerController: VideoPlayerController.networkUrl(Uri.parse(_src)), + ); + expect(controller.hideCursorInFullScreen, isTrue); + expect( + controller.copyWith(hideCursorInFullScreen: false).hideCursorInFullScreen, + isFalse, + ); + }); +} From 046b10a29c9b4a1c409cc7e7dd029eb8a3e3d8d3 Mon Sep 17 00:00:00 2001 From: Ortes Date: Thu, 9 Jul 2026 07:49:01 -0400 Subject: [PATCH 4/4] docs: reference PR #954 in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2731be237..53577d878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## [Unreleased] -* ⬆️ Auto-hide the mouse cursor along with the controls while idle in fullscreen, and show it again on mouse movement (works on web and desktop). Configurable via `ChewieController.hideCursorInFullScreen` (default `true`). Thanks [Ortes](https://github.com/Ortes). +* ⬆️ [#954](https://github.com/fluttercommunity/chewie/pull/954): Auto-hide the mouse cursor along with the controls while idle in fullscreen, and show it again on mouse movement (works on web and desktop). Configurable via `ChewieController.hideCursorInFullScreen` (default `true`). Thanks [Ortes](https://github.com/Ortes). ## [1.14.1] * 🛠️ [#945](https://github.com/fluttercommunity/chewie/pull/945): Flutter 3.38 downgrade. Thanks [diegotori](https://github.com/diegotori).