mirror of
https://github.com/flutter/flutter
synced 2024-10-05 15:59:49 +00:00
Activate shortcuts based on NumLock state (#145146)
## Description The PR updates `SingleActivator` in order to add a parameter for specifying that a shortcut depends on <kbd>NumLock</kbd> key state. Somewhat similarly to what is possible with common modifiers expect that a boolean is not enough in this case because: by default, a shortcut should ignore the <kbd>NumLock</kbd> state and it should be possible to define shortcuts that require <kbd>NumLock</kbd> to be locked and other that require it to be unlocked. @gspencergoog I considered defining a new `ShortcutActivator` implementation for this, but I thinks that adding the feature directly to `SingleActivator` results in a cleaner API. ## Related Issue Fixes https://github.com/flutter/flutter/issues/145144 Preparation for https://github.com/flutter/flutter/issues/144936 ## Tests Adds 3 tests.
This commit is contained in:
parent
88a9b58dd8
commit
6f61f6135f
|
@ -142,6 +142,16 @@ class KeySet<T extends KeyboardKey> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Determines how the state of a lock key is used to accept a shortcut.
|
||||
enum LockState {
|
||||
/// The lock key state is not used to determine [SingleActivator.accepts] result.
|
||||
ignored,
|
||||
/// The lock key must be locked to trigger the shortcut.
|
||||
locked,
|
||||
/// The lock key must be unlocked to trigger the shortcut.
|
||||
unlocked,
|
||||
}
|
||||
|
||||
/// An interface to define the keyboard key combination to trigger a shortcut.
|
||||
///
|
||||
/// [ShortcutActivator]s are used by [Shortcuts] widgets, and are mapped to
|
||||
|
@ -430,6 +440,7 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S
|
|||
this.shift = false,
|
||||
this.alt = false,
|
||||
this.meta = false,
|
||||
this.numLock = LockState.ignored,
|
||||
this.includeRepeats = true,
|
||||
}) : // The enumerated check with `identical` is cumbersome but the only way
|
||||
// since const constructors can not call functions such as `==` or
|
||||
|
@ -505,6 +516,19 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S
|
|||
/// * [LogicalKeyboardKey.metaLeft], [LogicalKeyboardKey.metaRight].
|
||||
final bool meta;
|
||||
|
||||
/// Whether the NumLock key state should be checked for [trigger] to activate
|
||||
/// the shortcut.
|
||||
///
|
||||
/// It defaults to [LockState.ignored], meaning the NumLock state is ignored
|
||||
/// when the event is received in order to activate the shortcut.
|
||||
/// If it's [LockState.locked], then the NumLock key must be locked.
|
||||
/// If it's [LockState.unlocked], then the NumLock key must be unlocked.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [LogicalKeyboardKey.numLock].
|
||||
final LockState numLock;
|
||||
|
||||
/// Whether this activator accepts repeat events of the [trigger] key.
|
||||
///
|
||||
/// If [includeRepeats] is true, the activator is checked on all
|
||||
|
@ -525,11 +549,20 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S
|
|||
&& meta == pressed.intersection(_metaSynonyms).isNotEmpty;
|
||||
}
|
||||
|
||||
bool _shouldAcceptNumLock(HardwareKeyboard state) {
|
||||
return switch (numLock) {
|
||||
LockState.ignored => true,
|
||||
LockState.locked => state.lockModesEnabled.contains(KeyboardLockMode.numLock),
|
||||
LockState.unlocked => !state.lockModesEnabled.contains(KeyboardLockMode.numLock),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool accepts(KeyEvent event, HardwareKeyboard state) {
|
||||
return (event is KeyDownEvent || (includeRepeats && event is KeyRepeatEvent))
|
||||
&& triggers.contains(event.logicalKey)
|
||||
&& _shouldAcceptModifiers(state.logicalKeysPressed);
|
||||
&& _shouldAcceptModifiers(state.logicalKeysPressed)
|
||||
&& _shouldAcceptNumLock(state);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -474,7 +474,7 @@ void main() {
|
|||
|
||||
const SingleActivator singleActivator = SingleActivator(LogicalKeyboardKey.keyA, control: true);
|
||||
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
|
||||
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);
|
||||
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
|
||||
|
@ -497,6 +497,111 @@ void main() {
|
|||
expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isFalse);
|
||||
});
|
||||
|
||||
testWidgets('numLock works as expected when set to LockState.locked', (WidgetTester tester) async {
|
||||
// Collect some key events to use for testing.
|
||||
final List<KeyEvent> events = <KeyEvent>[];
|
||||
await tester.pumpWidget(
|
||||
Focus(
|
||||
autofocus: true,
|
||||
onKeyEvent: (FocusNode node, KeyEvent event) {
|
||||
events.add(event);
|
||||
return KeyEventResult.ignored;
|
||||
},
|
||||
child: const SizedBox(),
|
||||
),
|
||||
);
|
||||
|
||||
const SingleActivator singleActivator = SingleActivator(LogicalKeyboardKey.numpad4, numLock: LockState.locked);
|
||||
|
||||
// Lock NumLock.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
|
||||
expect(HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock), isTrue);
|
||||
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
|
||||
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);
|
||||
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);
|
||||
|
||||
// Unlock NumLock.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
|
||||
expect(HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock), isFalse);
|
||||
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
|
||||
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isFalse);
|
||||
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);
|
||||
});
|
||||
|
||||
testWidgets('numLock works as expected when set to LockState.unlocked', (WidgetTester tester) async {
|
||||
// Collect some key events to use for testing.
|
||||
final List<KeyEvent> events = <KeyEvent>[];
|
||||
await tester.pumpWidget(
|
||||
Focus(
|
||||
autofocus: true,
|
||||
onKeyEvent: (FocusNode node, KeyEvent event) {
|
||||
events.add(event);
|
||||
return KeyEventResult.ignored;
|
||||
},
|
||||
child: const SizedBox(),
|
||||
),
|
||||
);
|
||||
|
||||
const SingleActivator singleActivator = SingleActivator(LogicalKeyboardKey.numpad4, numLock: LockState.unlocked);
|
||||
|
||||
// Lock NumLock.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
|
||||
expect(HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock), isTrue);
|
||||
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
|
||||
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isFalse);
|
||||
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);
|
||||
|
||||
// Unlock NumLock.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
|
||||
expect(HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock), isFalse);
|
||||
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
|
||||
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);
|
||||
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);
|
||||
});
|
||||
|
||||
testWidgets('numLock works as expected when set to LockState.ignored', (WidgetTester tester) async {
|
||||
// Collect some key events to use for testing.
|
||||
final List<KeyEvent> events = <KeyEvent>[];
|
||||
await tester.pumpWidget(
|
||||
Focus(
|
||||
autofocus: true,
|
||||
onKeyEvent: (FocusNode node, KeyEvent event) {
|
||||
events.add(event);
|
||||
return KeyEventResult.ignored;
|
||||
},
|
||||
child: const SizedBox(),
|
||||
),
|
||||
);
|
||||
|
||||
const SingleActivator singleActivator = SingleActivator(LogicalKeyboardKey.numpad4);
|
||||
|
||||
// Lock NumLock.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
|
||||
expect(HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock), isTrue);
|
||||
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
|
||||
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);
|
||||
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);
|
||||
|
||||
// Unlock NumLock.
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
|
||||
expect(HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock), isFalse);
|
||||
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad4);
|
||||
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);
|
||||
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad4);
|
||||
});
|
||||
|
||||
group('diagnostics.', () {
|
||||
test('single key', () {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
|
|
Loading…
Reference in a new issue