Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## [Unreleased]
* ⬆️ [#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).
* Library now supports Flutter and Dart versions `3.38.0` and `3.10` or higher respectively, restoring previous compatibility.
Expand Down
10 changes: 10 additions & 0 deletions lib/src/chewie_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -394,6 +395,7 @@ class ChewieController extends ChangeNotifier {
)?
routePageBuilder,
bool? pauseOnBackgroundTap,
bool? hideCursorInFullScreen,
}) {
return ChewieController(
draggableProgressBar: draggableProgressBar ?? this.draggableProgressBar,
Expand Down Expand Up @@ -458,6 +460,8 @@ class ChewieController extends ChangeNotifier {
progressIndicatorDelay:
progressIndicatorDelay ?? this.progressIndicatorDelay,
pauseOnBackgroundTap: pauseOnBackgroundTap ?? this.pauseOnBackgroundTap,
hideCursorInFullScreen:
hideCursorInFullScreen ?? this.hideCursorInFullScreen,
);
}

Expand Down Expand Up @@ -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<ChewieControllerProvider>()!;
Expand Down
9 changes: 9 additions & 0 deletions lib/src/cupertino/cupertino_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ class _CupertinoControlsState extends State<CupertinoControls>
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();
Expand Down Expand Up @@ -86,6 +94,7 @@ class _CupertinoControlsState extends State<CupertinoControls>
final buttonPadding = orientation == Orientation.portrait ? 16.0 : 24.0;

return MouseRegion(
cursor: _idleCursor,
onHover: (_) => _cancelAndRestartTimer(),
child: GestureDetector(
onTap: () => _cancelAndRestartTimer(),
Expand Down
9 changes: 9 additions & 0 deletions lib/src/material/material_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ class _MaterialControlsState extends State<MaterialControls>
// 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();
Expand All @@ -67,6 +75,7 @@ class _MaterialControlsState extends State<MaterialControls>
}

return MouseRegion(
cursor: _idleCursor,
onHover: (_) {
_cancelAndRestartTimer();
},
Expand Down
9 changes: 9 additions & 0 deletions lib/src/material/material_desktop_controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ class _MaterialDesktopControlsState extends State<MaterialDesktopControls>
// 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();
Expand Down Expand Up @@ -91,6 +99,7 @@ class _MaterialDesktopControlsState extends State<MaterialDesktopControls>
focusNode: _focusNode,
onKeyEvent: _handleKeyPress,
child: MouseRegion(
cursor: _idleCursor,
onHover: (_) {
_focusNode.requestFocus();
_cancelAndRestartTimer();
Expand Down
57 changes: 57 additions & 0 deletions test/hide_cursor_test.dart
Original file line number Diff line number Diff line change
@@ -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,
);
});
}