-
-
Notifications
You must be signed in to change notification settings - Fork 899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Added HardwareKeyboardDetector #2257
Merged
Merged
Changes from 3 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
0b401cc
RawKeyboardDetector
st-pasha 6fb5fc1
remove an argument
st-pasha f6351dd
Merge branch 'main' into ps.raw-keyboard-events
st-pasha ff19335
rename onKeyEvent->onRawKeyEvent
st-pasha 4d0cd11
start working on example
st-pasha fb88e15
Merge branch 'main' into ps.raw-keyboard-events
st-pasha 02b33d0
Added example
st-pasha e3016e6
Merge branch 'main' into ps.raw-keyboard-events
st-pasha 8ba5028
add a dictionary word
st-pasha 38dad23
rename RawKeyboardDetector->HardwareKeyboardDetector, and added tests
st-pasha 3065ac8
format
st-pasha c51f5d3
Merge branch 'main' into ps.raw-keyboard-events
st-pasha a886391
added pauseKeyEvents property
st-pasha eb844c2
update doccomments
st-pasha aaec880
Merge branch 'main' into ps.raw-keyboard-events
st-pasha e156502
analyze warnings
st-pasha e59cf4e
more robust pauseKeyEvents
st-pasha b7c8d27
add external links
st-pasha 767fab5
convert HardwareKeyboardDetector into a component
st-pasha 87a096e
analysis
st-pasha 44bc77b
normalize events upon mounting/dismounting
st-pasha 0ae6828
comment
st-pasha a64075d
minor
st-pasha 81bcf7b
Merge branch 'main' into ps.raw-keyboard-events
st-pasha a3d485d
fix tests
st-pasha 6628295
Merge branch 'main' into ps.raw-keyboard-events
st-pasha a87a013
Merge branch 'main' into ps.raw-keyboard-events
spydon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
102 changes: 102 additions & 0 deletions
102
packages/flame/lib/src/events/game_mixins/raw_keyboard_detector.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import 'package:flame/src/game/game.dart'; | ||
import 'package:flutter/services.dart'; | ||
|
||
/// This mixin allows listening to raw keyboard events, bypassing the `Focus` | ||
/// widget in Flutter. | ||
/// | ||
/// The mixin provides two event handlers that can be overridden in your game: | ||
/// - [onKeyEvent] fires whenever the user presses or releases any key on a | ||
/// keyboard. Use this to handle "one-off" key events, such as the user | ||
/// pressing <Space> to jump, or <Esc> to open a menu. | ||
/// - [onKeysPressed] fires on every game tick as long as the user presses at | ||
/// least some keys on the keyboard. Use this to reliably handle repeating | ||
/// key events, such as user pressing arrow keys to move their character, or | ||
/// holding down <Ctrl> to shoot continuously. | ||
mixin RawKeyboardDetector on Game { | ||
/// The list of keys that are currently being pressed on the keyboard (or a | ||
/// keyboard-like device). The keys are listed in the order in which they | ||
/// were pressed, except for the modifier keys which may be listed | ||
/// out-of-order on some systems. | ||
List<PhysicalKeyboardKey> physicalKeysPressed = []; | ||
|
||
/// The set of logical keys that are currently being pressed on the keyboard. | ||
/// This set corresponds to the [physicalKeysPressed] list, and can be used | ||
/// to search for keys in a keyboard-layout-independent way. | ||
Set<LogicalKeyboardKey> logicalKeysPressed = {}; | ||
|
||
/// Is the <Ctrl> key currently being pressed (either left or right)? | ||
bool get isControlPressed => | ||
logicalKeysPressed.contains(LogicalKeyboardKey.controlLeft) || | ||
logicalKeysPressed.contains(LogicalKeyboardKey.controlRight); | ||
|
||
/// Is the <Shift> key currently being pressed (either left or right)? | ||
bool get isShiftPressed => | ||
logicalKeysPressed.contains(LogicalKeyboardKey.shiftLeft) || | ||
logicalKeysPressed.contains(LogicalKeyboardKey.shiftRight); | ||
|
||
/// Is the <Alt> key currently being pressed (either left or right)? | ||
bool get isAltPressed => | ||
logicalKeysPressed.contains(LogicalKeyboardKey.altLeft) || | ||
logicalKeysPressed.contains(LogicalKeyboardKey.altRight); | ||
|
||
/// Override this event handler if you want to get notified whenever any key | ||
/// on a keyboard is pressed or released. The [event] will be either a | ||
/// [RawKeyDownEvent] or a [RawKeyUpEvent], respectively. | ||
/// | ||
/// This event may also fire when the user presses a key and then holds it | ||
/// down. In such a case `event.repeat` property will be set to `true`. | ||
/// However, this should not be used for character navigation, since this | ||
/// behavior may not be reliable, and the frequency of such events is system- | ||
/// dependent. Use [onKeysPressed] event handler instead. | ||
void onKeyEvent(RawKeyEvent event) {} | ||
|
||
/// Override this event handler if you want to get notified whenever any keys | ||
/// are being pressed. This event handler is fired at the start of every game | ||
/// tick. | ||
/// | ||
/// The list of keys currently being pressed can be accessed via the | ||
/// [physicalKeysPressed] or [logicalKeysPressed] properties. | ||
void onKeysPressed() {} | ||
|
||
/// Internal handler of raw key events. | ||
void _onRawKeyEvent(RawKeyEvent event) { | ||
logicalKeysPressed = RawKeyboard.instance.keysPressed; | ||
if (event is RawKeyDownEvent) { | ||
physicalKeysPressed.add(event.physicalKey); | ||
} else if (event is RawKeyUpEvent) { | ||
physicalKeysPressed.remove(event.physicalKey); | ||
} | ||
// The list of physical keys may need to be reconciled with the RawKeyboard | ||
if (physicalKeysPressed.length != logicalKeysPressed.length) { | ||
final rawPhysicalKeysPressed = RawKeyboard.instance.physicalKeysPressed; | ||
physicalKeysPressed | ||
..removeWhere((key) => !rawPhysicalKeysPressed.contains(key)) | ||
..addAll( | ||
rawPhysicalKeysPressed | ||
.where((key) => !physicalKeysPressed.contains(key)) | ||
.toList(), | ||
); | ||
} | ||
onKeyEvent(event); | ||
} | ||
|
||
@override | ||
void onMount() { | ||
super.onMount(); | ||
RawKeyboard.instance.addListener(_onRawKeyEvent); | ||
} | ||
|
||
@override | ||
void onRemove() { | ||
super.onRemove(); | ||
RawKeyboard.instance.removeListener(_onRawKeyEvent); | ||
} | ||
|
||
@override | ||
void update(double dt) { | ||
if (physicalKeysPressed.isNotEmpty) { | ||
onKeysPressed(); | ||
} | ||
super.update(dt); | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this issue flutter/flutter#99330 I would not duplicate this info that is already present and accessible on RawKeyboard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This field is copied from the
RawKeyboard
on every key event:Thus, it is always kept in sync with the information available in RawKeyboard. The reason I decided to have this copy is because in
RawKeyboard
this field is defined as follows:That is, they return a copy of the internal state on every access. This is good for safety, but not so much for efficiency: if your code needs to check whether a number of certain keys are pressed, there will be a new set created on every check. Even such simple methods as
isShiftPressed
/isControlPressed
/isAltPressed
each create two new copies of this set.So I thought that a good solution to this is to simply keep a local copy of the set of logical keys, and make sure it is always kept up-to-date with
RawKeyboard
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for
physicalKeysPressed
-- I believe it's important to know not only which keys are currently being pressed, but also in what order they were pressed. This information is currently missing inRawKeyboard
, they return physical keys as an orderless set, and keep it internally as a dictionary.The reason why the order may be important is a scenario like this: suppose the user presses [Left Arrow], holds it, and then presses [Right Arrow]. Now two arrows are pressed simultaneously, and the game needs to decide how to respond to this. One possible choice is to make the player character stand still; another choice is to make the player character go right, since the right arrow was pressed last. By keeping the physical keys in a list instead of a set, we give the game an opportunity to choose which behavior to implement (my personal preference is for the latter).