mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-10-31 00:48:47 +08:00 
			
		
		
		
	Adding custom mouse cursors for flame (#935)
* Adding custom mouse cursors for flame * linting and adding fvm to gitignore * PR suggestions * Apply suggestions from code review Co-authored-by: Luan Nico <luanpotter27@gmail.com> Co-authored-by: Luan Nico <luanpotter27@gmail.com>
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -18,3 +18,5 @@ desktop/ | ||||
| build/ | ||||
|  | ||||
| coverage | ||||
|  | ||||
| .fvm | ||||
|  | ||||
| @ -102,6 +102,22 @@ and [MouseRegion widget](https://api.flutter.dev/flutter/widgets/MouseRegion-cla | ||||
| also read more about Flutter's gestures | ||||
| [here](https://api.flutter.dev/flutter/gestures/gestures-library.html). | ||||
|  | ||||
| It is also possible to change the current mouse cursor displayed on the `GameWidget` region. To do | ||||
| so the following code can be used inside the `Game` class | ||||
|  | ||||
| ```dart | ||||
| mouseCursor.value = SystemMouseCursors.move; | ||||
| ``` | ||||
|  | ||||
| To already initialize the `GameWidget` with a custom cursor, the `mouseCursor` property can be used | ||||
|  | ||||
| ```dart | ||||
| GameWidget( | ||||
|   game: MouseCursorGame(), | ||||
|   mouseCursor: SystemMouseCursors.move, | ||||
| ); | ||||
| ``` | ||||
|  | ||||
| ## Event coordinate system | ||||
|  | ||||
| On events that have positions, like for example `Tap*` or `Drag`, you will notice that the `eventPosition` | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| import 'package:dashbook/dashbook.dart'; | ||||
| import 'package:flame/game.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| import '../../commons/commons.dart'; | ||||
| import 'draggables.dart'; | ||||
| @ -7,6 +8,7 @@ import 'hoverables.dart'; | ||||
| import 'joystick.dart'; | ||||
| import 'joystick_advanced.dart'; | ||||
| import 'keyboard.dart'; | ||||
| import 'mouse_cursor.dart'; | ||||
| import 'mouse_movement.dart'; | ||||
| import 'multitap.dart'; | ||||
| import 'multitap_advanced.dart'; | ||||
| @ -26,6 +28,18 @@ void addInputStories(Dashbook dashbook) { | ||||
|       (_) => GameWidget(game: MouseMovementGame()), | ||||
|       codeLink: baseLink('input/mouse_movement.dart'), | ||||
|     ) | ||||
|     ..add( | ||||
|       'Mouse Cursor', | ||||
|       (_) => GameWidget( | ||||
|         game: MouseCursorGame(), | ||||
|         mouseCursor: SystemMouseCursors.move, | ||||
|       ), | ||||
|       codeLink: baseLink('input/mouse_cursor.dart'), | ||||
|       info: ''' | ||||
|       Example showcasing the ability to change the game cursor in runtime | ||||
|       hover the little square to see the cursor changing | ||||
|       ''', | ||||
|     ) | ||||
|     ..add( | ||||
|       'Scroll', | ||||
|       (_) => GameWidget(game: ScrollGame()), | ||||
|  | ||||
							
								
								
									
										53
									
								
								examples/lib/stories/input/mouse_cursor.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								examples/lib/stories/input/mouse_cursor.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | ||||
| import 'package:flame/extensions.dart'; | ||||
| import 'package:flame/game.dart'; | ||||
| import 'package:flame/input.dart'; | ||||
| import 'package:flame/palette.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/widgets.dart'; | ||||
|  | ||||
| class MouseCursorGame extends Game with MouseMovementDetector { | ||||
|   static const speed = 200; | ||||
|   static final Paint _blue = BasicPalette.blue.paint(); | ||||
|   static final Paint _white = BasicPalette.white.paint(); | ||||
|   static final Vector2 objSize = Vector2.all(150); | ||||
|  | ||||
|   Vector2 position = Vector2(100, 100); | ||||
|   Vector2? target; | ||||
|  | ||||
|   bool onTarget = false; | ||||
|  | ||||
|   @override | ||||
|   void onMouseMove(PointerHoverInfo info) { | ||||
|     target = info.eventPosition.game; | ||||
|   } | ||||
|  | ||||
|   Rect _toRect() => position.toPositionedRect(objSize); | ||||
|  | ||||
|   @override | ||||
|   void render(Canvas canvas) { | ||||
|     canvas.drawRect( | ||||
|       _toRect(), | ||||
|       onTarget ? _blue : _white, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void update(double dt) { | ||||
|     final target = this.target; | ||||
|     if (target != null) { | ||||
|       final hovering = _toRect().contains(target.toOffset()); | ||||
|       if (hovering) { | ||||
|         if (!onTarget) { | ||||
|           //Entered | ||||
|           mouseCursor.value = SystemMouseCursors.grab; | ||||
|         } | ||||
|       } else { | ||||
|         if (onTarget) { | ||||
|           // Exited | ||||
|           mouseCursor.value = SystemMouseCursors.move; | ||||
|         } | ||||
|       } | ||||
|       onTarget = hovering; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1 +1 @@ | ||||
| 54.8 | ||||
| 57.0 | ||||
|  | ||||
| @ -44,6 +44,7 @@ | ||||
|  - Add `loadAllImages` to `Images`, which loads all images from the prefixed path | ||||
|  - Reviewed the keyboard API with new mixins (`KeyboardHandler` and `HasKeyboardHandlerComponents`) | ||||
|  - Added `FocusNode` on the game widget and improved keyboard handling in the game. | ||||
|  - Added ability to have custom mouse cursor on the `GameWidget` region | ||||
|  | ||||
| ## [1.0.0-releasecandidate.13] | ||||
|  - Fix camera not ending up in the correct position on long jumps | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/rendering.dart'; | ||||
| import 'package:flutter/scheduler.dart'; | ||||
| import 'package:flutter/widgets.dart'; | ||||
| @ -209,6 +210,11 @@ abstract class Game extends Projector { | ||||
|   /// - GameWidget | ||||
|   /// - [Game.overlays] | ||||
|   final overlays = ActiveOverlaysNotifier(); | ||||
|  | ||||
|   /// Used to change the mouse cursor of the GameWidget running this game. | ||||
|   /// Setting the value to null will make the GameWidget defer the choice | ||||
|   /// of the cursor to the closest region available on the tree. | ||||
|   final mouseCursor = ValueNotifier<MouseCursor?>(null); | ||||
| } | ||||
|  | ||||
| /// A [ChangeNotifier] used to control the visibility of overlays on a [Game] instance. | ||||
|  | ||||
| @ -68,6 +68,10 @@ class GameWidget<T extends Game> extends StatefulWidget { | ||||
|   /// Defaults to true. | ||||
|   final bool autofocus; | ||||
|  | ||||
|   /// Initial mouse cursor for this [GameWidget] | ||||
|   /// mouse cursor can be changed in runtime using [Game.mouseCursor] | ||||
|   final MouseCursor? mouseCursor; | ||||
|  | ||||
|   /// Renders a [game] in a flutter widget tree. | ||||
|   /// | ||||
|   /// Ex: | ||||
| @ -116,6 +120,7 @@ class GameWidget<T extends Game> extends StatefulWidget { | ||||
|     this.initialActiveOverlays, | ||||
|     this.focusNode, | ||||
|     this.autofocus = true, | ||||
|     this.mouseCursor, | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   /// Renders a [game] in a flutter widget tree alongside widgets overlays. | ||||
| @ -128,6 +133,8 @@ class GameWidget<T extends Game> extends StatefulWidget { | ||||
| class _GameWidgetState<T extends Game> extends State<GameWidget<T>> { | ||||
|   Set<String> initialActiveOverlays = {}; | ||||
|  | ||||
|   MouseCursor? _mouseCursor; | ||||
|  | ||||
|   Future<void>? _gameLoaderFuture; | ||||
|  | ||||
|   Future<void> get _gameLoaderFutureCache => | ||||
| @ -139,8 +146,18 @@ class _GameWidgetState<T extends Game> extends State<GameWidget<T>> { | ||||
|  | ||||
|     // Add the initial overlays | ||||
|     _initActiveOverlays(); | ||||
|     addOverlaysListener(); | ||||
|  | ||||
|     addOverlaysListener(widget.game); | ||||
|     // Add the initial mouse cursor | ||||
|     _initMouseCursor(); | ||||
|     addMouseCursorListener(); | ||||
|   } | ||||
|  | ||||
|   void _initMouseCursor() { | ||||
|     if (widget.mouseCursor != null) { | ||||
|       widget.game.mouseCursor.value = widget.mouseCursor; | ||||
|       _mouseCursor = widget.game.mouseCursor.value; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void _initActiveOverlays() { | ||||
| @ -161,7 +178,11 @@ class _GameWidgetState<T extends Game> extends State<GameWidget<T>> { | ||||
|  | ||||
|       // Reset the overlays | ||||
|       _initActiveOverlays(); | ||||
|       addOverlaysListener(widget.game); | ||||
|       addOverlaysListener(); | ||||
|  | ||||
|       // Reset mouse cursor | ||||
|       _initMouseCursor(); | ||||
|       addMouseCursorListener(); | ||||
|  | ||||
|       // Reset the loader future | ||||
|       _gameLoaderFuture = null; | ||||
| @ -174,8 +195,18 @@ class _GameWidgetState<T extends Game> extends State<GameWidget<T>> { | ||||
|     removeOverlaysListener(widget.game); | ||||
|   } | ||||
|  | ||||
|   void addMouseCursorListener() { | ||||
|     widget.game.mouseCursor.addListener(onChangeMouseCursor); | ||||
|   } | ||||
|  | ||||
|   void onChangeMouseCursor() { | ||||
|     setState(() { | ||||
|       _mouseCursor = widget.game.mouseCursor.value; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   // widget overlay stuff | ||||
|   void addOverlaysListener(T game) { | ||||
|   void addOverlaysListener() { | ||||
|     widget.game.overlays.addListener(onChangeActiveOverlays); | ||||
|     initialActiveOverlays = widget.game.overlays.value; | ||||
|   } | ||||
| @ -249,7 +280,9 @@ class _GameWidgetState<T extends Game> extends State<GameWidget<T>> { | ||||
|     // We can use Directionality.maybeOf when that method lands on stable | ||||
|     final textDir = widget.textDirection ?? TextDirection.ltr; | ||||
|  | ||||
|     return Focus( | ||||
|     return MouseRegion( | ||||
|       cursor: _mouseCursor ?? MouseCursor.defer, | ||||
|       child: Focus( | ||||
|         focusNode: widget.focusNode, | ||||
|         autofocus: widget.autofocus, | ||||
|         onKey: _handleKeyEvent, | ||||
| @ -281,6 +314,7 @@ class _GameWidgetState<T extends Game> extends State<GameWidget<T>> { | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|  | ||||
| @ -0,0 +1,63 @@ | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:flame/game.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_test/flutter_test.dart'; | ||||
|  | ||||
| class TestGame extends Game { | ||||
|   @override | ||||
|   void render(Canvas canvas) {} | ||||
|  | ||||
|   @override | ||||
|   void update(double dt) {} | ||||
| } | ||||
|  | ||||
| Finder byMouseCursor(MouseCursor cursor) { | ||||
|   return find.byWidgetPredicate( | ||||
|     (widget) => widget is MouseRegion && widget.cursor == cursor, | ||||
|   ); | ||||
| } | ||||
|  | ||||
| void main() { | ||||
|   group('GameWidget - MouseCursor', () { | ||||
|     testWidgets('renders with the initial cursor', (tester) async { | ||||
|       await tester.pumpWidget( | ||||
|         MaterialApp( | ||||
|           home: GameWidget( | ||||
|             game: TestGame(), | ||||
|             mouseCursor: SystemMouseCursors.grab, | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|  | ||||
|       expect( | ||||
|         byMouseCursor(SystemMouseCursors.grab), | ||||
|         findsOneWidget, | ||||
|       ); | ||||
|     }); | ||||
|  | ||||
|     testWidgets('can change the cursor', (tester) async { | ||||
|       final game = TestGame(); | ||||
|  | ||||
|       await tester.pumpWidget( | ||||
|         MaterialApp( | ||||
|           home: GameWidget( | ||||
|             game: game, | ||||
|             mouseCursor: SystemMouseCursors.grab, | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|  | ||||
|       // Making sure this cursor isn't showing yet | ||||
|       expect(byMouseCursor(SystemMouseCursors.copy), findsNothing); | ||||
|  | ||||
|       game.mouseCursor.value = SystemMouseCursors.copy; | ||||
|       await tester.pump(); | ||||
|  | ||||
|       expect( | ||||
|         byMouseCursor(SystemMouseCursors.copy), | ||||
|         findsOneWidget, | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Erick
					Erick