mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Character activator (#81807)
This commit is contained in:
parent
7cdd33fe99
commit
01c98fa95e
@ -160,6 +160,8 @@ class KeySet<T extends KeyboardKey> {
|
||||
///
|
||||
/// * [SingleActivator], an implementation that represents a single key combined
|
||||
/// with modifiers (control, shift, alt, meta).
|
||||
/// * [CharacterActivator], an implementation that represents key combinations
|
||||
/// that result in the specified character, such as question mark.
|
||||
/// * [LogicalKeySet], an implementation that requires one or more
|
||||
/// [LogicalKeyboardKey]s to be pressed at the same time. Prefer
|
||||
/// [SingleActivator] when possible.
|
||||
@ -179,7 +181,13 @@ abstract class ShortcutActivator {
|
||||
/// [Intent]s are stored in a [Map] and indexed by trigger keys. Subclasses
|
||||
/// should make sure that the return value of this method does not change
|
||||
/// throughout the lifespan of this object.
|
||||
Iterable<LogicalKeyboardKey> get triggers;
|
||||
///
|
||||
/// This method might also return null, which means this activator declares
|
||||
/// all keys as the trigger key. All activators whose [triggers] returns null
|
||||
/// will be tested with [accepts] on every event. Since this becomes a
|
||||
/// linear search, and having too many might impact performance, it is
|
||||
/// preferred to return non-null [triggers] whenever possible.
|
||||
Iterable<LogicalKeyboardKey>? get triggers;
|
||||
|
||||
/// Whether the triggering `event` and the keyboard `state` at the time of the
|
||||
/// event meet required conditions, providing that the event is a triggering
|
||||
@ -194,6 +202,9 @@ abstract class ShortcutActivator {
|
||||
/// this is only used to query whether [RawKeyboard.keysPressed] contains
|
||||
/// a key.
|
||||
///
|
||||
/// Since [ShortcutActivator] accepts all event types, subclasses might want
|
||||
/// to check the event type in [accepts].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [LogicalKeyboardKey.collapseSynonyms], which helps deciding whether a
|
||||
@ -323,9 +334,6 @@ class LogicalKeySet extends KeySet<LogicalKeyboardKey> with Diagnosticable
|
||||
LogicalKeyboardKey.meta: <LogicalKeyboardKey>[LogicalKeyboardKey.metaLeft, LogicalKeyboardKey.metaRight],
|
||||
};
|
||||
|
||||
/// Returns a description of the key set that is short and readable.
|
||||
///
|
||||
/// Intended to be used in debug mode for logging purposes.
|
||||
@override
|
||||
String debugDescribeKeys() {
|
||||
final List<LogicalKeyboardKey> sortedKeys = keys.toList()..sort(
|
||||
@ -387,7 +395,7 @@ class ShortcutMapProperty extends DiagnosticsProperty<Map<ShortcutActivator, Int
|
||||
|
||||
/// A shortcut key combination of a single key and modifiers.
|
||||
///
|
||||
/// This [ShortcutActivator] implements typical shortcuts such as:
|
||||
/// The [SingleActivator] implements typical shortcuts such as:
|
||||
///
|
||||
/// * ArrowLeft
|
||||
/// * Shift + Delete
|
||||
@ -412,6 +420,11 @@ class ShortcutMapProperty extends DiagnosticsProperty<Map<ShortcutActivator, Int
|
||||
/// * [SingleActivator]s do not consider modifiers to be a trigger key. For
|
||||
/// example, pressing ControlLeft while holding key X *will not* activate a
|
||||
/// `SingleActivator(LogicalKeyboardKey.keyX, control: true)`.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [CharacterActivator], an activator that represents key combinations
|
||||
/// that result in the specified character, such as question mark.
|
||||
class SingleActivator with Diagnosticable implements ShortcutActivator {
|
||||
/// Create an activator of a trigger key and modifiers.
|
||||
///
|
||||
@ -474,8 +487,8 @@ class SingleActivator with Diagnosticable implements ShortcutActivator {
|
||||
this.meta = false,
|
||||
}) : // The enumerated check with `identical` is cumbersome but the only way
|
||||
// since const constructors can not call functions such as `==` or
|
||||
// `Set.contains`. Checking with `identical` is sufficient since
|
||||
// `LogicalKeyboardKey` only provides cached values.
|
||||
// `Set.contains`. Checking with `identical` might not work when the
|
||||
// key object is created from ID, but it covers common cases.
|
||||
assert(
|
||||
!identical(trigger, LogicalKeyboardKey.control) &&
|
||||
!identical(trigger, LogicalKeyboardKey.controlLeft) &&
|
||||
@ -585,6 +598,110 @@ class SingleActivator with Diagnosticable implements ShortcutActivator {
|
||||
}
|
||||
}
|
||||
|
||||
/// A shortcut combination that is triggered by a key event that produces a
|
||||
/// specific character.
|
||||
///
|
||||
/// Keys often produce different characters when combined with modifiers. For
|
||||
/// example, it might be helpful for the user to bring up a help menu by
|
||||
/// pressing the question mark ('?'). However, there is no logical key that
|
||||
/// directly represents a question mark. Althouh 'Shift+Slash' produces a '?'
|
||||
/// character on a US keyboard, its logical key is still considered a Slash key,
|
||||
/// and hard-coding 'Shift+Slash' in this situation is unfriendly to other
|
||||
/// keyboard layouts.
|
||||
///
|
||||
/// For example, `CharacterActivator('?')` is triggered when a key combination
|
||||
/// results in a question mark, which is 'Shift+Slash' on a US keyboard, but
|
||||
/// 'Shift+Comma' on a French keyboard.
|
||||
///
|
||||
/// {@tool dartpad --template=stateful_widget_scaffold_center}
|
||||
/// In the following example, when a key combination results in a question mark,
|
||||
/// the counter is increased:
|
||||
///
|
||||
/// ```dart preamble
|
||||
/// class HelpMenuIntent extends Intent {
|
||||
/// const HelpMenuIntent();
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ```dart
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return Shortcuts(
|
||||
/// shortcuts: const <ShortcutActivator, Intent>{
|
||||
/// CharacterActivator('?'): HelpMenuIntent(),
|
||||
/// },
|
||||
/// child: Actions(
|
||||
/// actions: <Type, Action<Intent>>{
|
||||
/// HelpMenuIntent: CallbackAction<HelpMenuIntent>(
|
||||
/// onInvoke: (HelpMenuIntent intent) {
|
||||
/// ScaffoldMessenger.of(context).showSnackBar(
|
||||
/// const SnackBar(content: Text('Keep calm and carry on!')),
|
||||
/// );
|
||||
/// return null;
|
||||
/// },
|
||||
/// ),
|
||||
/// },
|
||||
/// child: Focus(
|
||||
/// autofocus: true,
|
||||
/// child: Column(
|
||||
/// children: const <Widget>[
|
||||
/// Text('Press question mark for help'),
|
||||
/// ],
|
||||
/// ),
|
||||
/// ),
|
||||
/// ),
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [SingleActivator], an activator that represents a single key combined
|
||||
/// with modifiers, such as `Ctrl+C`.
|
||||
class CharacterActivator with Diagnosticable implements ShortcutActivator {
|
||||
/// Create a [CharacterActivator] from the triggering character.
|
||||
const CharacterActivator(this.character);
|
||||
|
||||
/// The character of the triggering event.
|
||||
///
|
||||
/// This is typically a single-character string, such as '?' or 'œ', although
|
||||
/// [CharacterActivator] doesn't check the length of [character] or whether it
|
||||
/// can be matched by any key combination at all. It is case-sensitive, since
|
||||
/// the [character] is directly compared by `==` to the character reported by
|
||||
/// the platform.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RawKeyEvent.character], the character of a key event.
|
||||
final String character;
|
||||
|
||||
@override
|
||||
Iterable<LogicalKeyboardKey>? get triggers => null;
|
||||
|
||||
@override
|
||||
bool accepts(RawKeyEvent event, RawKeyboard state) {
|
||||
return event is RawKeyDownEvent
|
||||
&& event.character == character;
|
||||
}
|
||||
|
||||
@override
|
||||
String debugDescribeKeys() {
|
||||
String result = '';
|
||||
assert(() {
|
||||
result = "'$character'";
|
||||
return true;
|
||||
}());
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(StringProperty('character', character));
|
||||
}
|
||||
}
|
||||
|
||||
class _ActivatorIntentPair with Diagnosticable {
|
||||
const _ActivatorIntentPair(this.activator, this.intent);
|
||||
final ShortcutActivator activator;
|
||||
@ -639,20 +756,22 @@ class ShortcutManager extends ChangeNotifier with Diagnosticable {
|
||||
}
|
||||
}
|
||||
|
||||
static Map<LogicalKeyboardKey, List<_ActivatorIntentPair>> _indexShortcuts(Map<ShortcutActivator, Intent> source) {
|
||||
final Map<LogicalKeyboardKey, List<_ActivatorIntentPair>> result = <LogicalKeyboardKey, List<_ActivatorIntentPair>>{};
|
||||
static Map<LogicalKeyboardKey?, List<_ActivatorIntentPair>> _indexShortcuts(Map<ShortcutActivator, Intent> source) {
|
||||
final Map<LogicalKeyboardKey?, List<_ActivatorIntentPair>> result = <LogicalKeyboardKey?, List<_ActivatorIntentPair>>{};
|
||||
source.forEach((ShortcutActivator activator, Intent intent) {
|
||||
for (final LogicalKeyboardKey trigger in activator.triggers) {
|
||||
// This intermediate variable is necessary to comply with Dart analyzer.
|
||||
final Iterable<LogicalKeyboardKey?>? nullableTriggers = activator.triggers;
|
||||
for (final LogicalKeyboardKey? trigger in nullableTriggers ?? <LogicalKeyboardKey?>[null]) {
|
||||
result.putIfAbsent(trigger, () => <_ActivatorIntentPair>[])
|
||||
.add(_ActivatorIntentPair(activator, intent));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
Map<LogicalKeyboardKey, List<_ActivatorIntentPair>> get _indexedShortcuts {
|
||||
Map<LogicalKeyboardKey?, List<_ActivatorIntentPair>> get _indexedShortcuts {
|
||||
return _indexedShortcutsCache ??= _indexShortcuts(_shortcuts);
|
||||
}
|
||||
Map<LogicalKeyboardKey, List<_ActivatorIntentPair>>? _indexedShortcutsCache;
|
||||
Map<LogicalKeyboardKey?, List<_ActivatorIntentPair>>? _indexedShortcutsCache;
|
||||
|
||||
/// Returns the [Intent], if any, that matches the current set of pressed
|
||||
/// keys.
|
||||
@ -662,9 +781,12 @@ class ShortcutManager extends ChangeNotifier with Diagnosticable {
|
||||
/// Defaults to a set derived from [RawKeyboard.keysPressed] if `keysPressed`
|
||||
/// is not supplied.
|
||||
Intent? _find(RawKeyEvent event, RawKeyboard state) {
|
||||
final List<_ActivatorIntentPair>? candidates = _indexedShortcuts[event.logicalKey];
|
||||
if (candidates == null)
|
||||
return null;
|
||||
final List<_ActivatorIntentPair>? candidatesByKey = _indexedShortcuts[event.logicalKey];
|
||||
final List<_ActivatorIntentPair>? candidatesByNull = _indexedShortcuts[null];
|
||||
final List<_ActivatorIntentPair> candidates = <_ActivatorIntentPair>[
|
||||
if (candidatesByKey != null) ...candidatesByKey,
|
||||
if (candidatesByNull != null) ...candidatesByNull,
|
||||
];
|
||||
for (final _ActivatorIntentPair activatorIntent in candidates) {
|
||||
if (activatorIntent.activator.accepts(event, state)) {
|
||||
return activatorIntent.intent;
|
||||
|
@ -9,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
typedef PostInvokeCallback = void Function({Action<Intent> action, Intent intent, BuildContext? context, ActionDispatcher dispatcher});
|
||||
|
||||
class TestAction extends CallbackAction<TestIntent> {
|
||||
class TestAction extends CallbackAction<Intent> {
|
||||
TestAction({
|
||||
required OnInvokeCallback onInvoke,
|
||||
}) : assert(onInvoke != null),
|
||||
@ -31,10 +31,47 @@ class TestDispatcher extends ActionDispatcher {
|
||||
}
|
||||
}
|
||||
|
||||
/// An activator that accepts down events that has [key] as the logical key.
|
||||
///
|
||||
/// This class is used only to tests. It is intentionally designed poorly by
|
||||
/// returning null in [triggers], and checks [key] in [accepts].
|
||||
class DumbLogicalActivator extends ShortcutActivator {
|
||||
const DumbLogicalActivator(this.key);
|
||||
|
||||
final LogicalKeyboardKey key;
|
||||
|
||||
@override
|
||||
Iterable<LogicalKeyboardKey>? get triggers => null;
|
||||
|
||||
@override
|
||||
bool accepts(RawKeyEvent event, RawKeyboard state) {
|
||||
return event is RawKeyDownEvent
|
||||
&& event.logicalKey == key;
|
||||
}
|
||||
|
||||
/// Returns a short and readable description of the key combination.
|
||||
///
|
||||
/// Intended to be used in debug mode for logging purposes. In release mode,
|
||||
/// [debugDescribeKeys] returns an empty string.
|
||||
@override
|
||||
String debugDescribeKeys() {
|
||||
String result = '';
|
||||
assert(() {
|
||||
result = key.keyLabel;
|
||||
return true;
|
||||
}());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class TestIntent extends Intent {
|
||||
const TestIntent();
|
||||
}
|
||||
|
||||
class TestIntent2 extends Intent {
|
||||
const TestIntent2();
|
||||
}
|
||||
|
||||
class TestShortcutManager extends ShortcutManager {
|
||||
TestShortcutManager(this.keys);
|
||||
|
||||
@ -49,7 +86,13 @@ class TestShortcutManager extends ShortcutManager {
|
||||
}
|
||||
}
|
||||
|
||||
Widget activatorTester(ShortcutActivator activator, ValueSetter<Intent> onInvoke) {
|
||||
Widget activatorTester(
|
||||
ShortcutActivator activator,
|
||||
ValueSetter<Intent> onInvoke, [
|
||||
ShortcutActivator? activator2,
|
||||
ValueSetter<Intent>? onInvoke2,
|
||||
]) {
|
||||
final bool hasSecond = activator2 != null && onInvoke2 != null;
|
||||
return Actions(
|
||||
key: GlobalKey(),
|
||||
actions: <Type, Action<Intent>>{
|
||||
@ -57,10 +100,16 @@ Widget activatorTester(ShortcutActivator activator, ValueSetter<Intent> onInvoke
|
||||
onInvoke(intent);
|
||||
return true;
|
||||
}),
|
||||
if (hasSecond)
|
||||
TestIntent2: TestAction(onInvoke: (Intent intent) {
|
||||
onInvoke2(intent);
|
||||
}),
|
||||
},
|
||||
child: Shortcuts(
|
||||
shortcuts: <ShortcutActivator, Intent>{
|
||||
activator: const TestIntent(),
|
||||
if (hasSecond)
|
||||
activator2: const TestIntent2(),
|
||||
},
|
||||
child: const Focus(
|
||||
autofocus: true,
|
||||
@ -967,5 +1016,65 @@ void main() {
|
||||
expect(value, isTrue);
|
||||
expect(controller.position.pixels, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('Shortcuts support activators that returns null in triggers', (WidgetTester tester) async {
|
||||
int invoked = 0;
|
||||
await tester.pumpWidget(activatorTester(
|
||||
const DumbLogicalActivator(LogicalKeyboardKey.keyC),
|
||||
(Intent intent) { invoked += 1; },
|
||||
const SingleActivator(LogicalKeyboardKey.keyC, control: true),
|
||||
(Intent intent) { invoked += 10; },
|
||||
));
|
||||
await tester.pump();
|
||||
|
||||
// Press KeyC: Accepted by DumbLogicalActivator
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
|
||||
expect(invoked, 1);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
|
||||
expect(invoked, 1);
|
||||
invoked = 0;
|
||||
|
||||
// Press ControlLeft + KeyC: Accepted by SingleActivator
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
expect(invoked, 0);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
|
||||
expect(invoked, 10);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
|
||||
expect(invoked, 10);
|
||||
invoked = 0;
|
||||
|
||||
// Press ControlLeft + ShiftLeft + KeyC: Accepted by DumbLogicalActivator
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
expect(invoked, 0);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
|
||||
expect(invoked, 1);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
|
||||
expect(invoked, 1);
|
||||
invoked = 0;
|
||||
});
|
||||
});
|
||||
|
||||
group('CharacterActivator', () {
|
||||
testWidgets('is triggered on events with correct character', (WidgetTester tester) async {
|
||||
int invoked = 0;
|
||||
await tester.pumpWidget(activatorTester(
|
||||
const CharacterActivator('?'),
|
||||
(Intent intent) { invoked += 1; },
|
||||
));
|
||||
await tester.pump();
|
||||
|
||||
// Press KeyC: Accepted by DumbLogicalActivator
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.slash, character: '?');
|
||||
expect(invoked, 1);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
|
||||
expect(invoked, 1);
|
||||
invoked = 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1021,10 +1021,10 @@ abstract class WidgetController {
|
||||
///
|
||||
/// - [sendKeyUpEvent] to simulate the corresponding key up event.
|
||||
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
|
||||
Future<bool> sendKeyDownEvent(LogicalKeyboardKey key, { String platform = _defaultPlatform }) async {
|
||||
Future<bool> sendKeyDownEvent(LogicalKeyboardKey key, { String? character, String platform = _defaultPlatform }) async {
|
||||
assert(platform != null);
|
||||
// Internally wrapped in async guard.
|
||||
return simulateKeyDownEvent(key, platform: platform);
|
||||
return simulateKeyDownEvent(key, character: character, platform: platform);
|
||||
}
|
||||
|
||||
/// Simulates sending a physical key up event through the system channel.
|
||||
|
@ -194,6 +194,7 @@ class KeyEventSimulator {
|
||||
required String platform,
|
||||
bool isDown = true,
|
||||
PhysicalKeyboardKey? physicalKey,
|
||||
String? character,
|
||||
}) {
|
||||
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
|
||||
|
||||
@ -211,27 +212,31 @@ class KeyEventSimulator {
|
||||
'keymap': platform,
|
||||
};
|
||||
|
||||
if (kIsWeb) {
|
||||
final String resultCharacter = character ?? _keyLabel(key);
|
||||
void assignWeb() {
|
||||
result['code'] = _getWebKeyCode(key);
|
||||
result['key'] = _keyLabel(key);
|
||||
result['key'] = resultCharacter;
|
||||
result['metaState'] = _getWebModifierFlags(key, isDown);
|
||||
}
|
||||
if (kIsWeb) {
|
||||
assignWeb();
|
||||
return result;
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'android':
|
||||
result['keyCode'] = keyCode;
|
||||
if (_keyLabel(key).isNotEmpty) {
|
||||
result['codePoint'] = _keyLabel(key).codeUnitAt(0);
|
||||
result['character'] = _keyLabel(key);
|
||||
if (resultCharacter.isNotEmpty) {
|
||||
result['codePoint'] = resultCharacter.codeUnitAt(0);
|
||||
result['character'] = resultCharacter;
|
||||
}
|
||||
result['scanCode'] = scanCode;
|
||||
result['metaState'] = _getAndroidModifierFlags(key, isDown);
|
||||
break;
|
||||
case 'fuchsia':
|
||||
result['hidUsage'] = physicalKey.usbHidUsage;
|
||||
if (_keyLabel(key).isNotEmpty) {
|
||||
result['codePoint'] = _keyLabel(key).codeUnitAt(0);
|
||||
if (resultCharacter.isNotEmpty) {
|
||||
result['codePoint'] = resultCharacter.codeUnitAt(0);
|
||||
}
|
||||
result['modifiers'] = _getFuchsiaModifierFlags(key, isDown);
|
||||
break;
|
||||
@ -240,34 +245,33 @@ class KeyEventSimulator {
|
||||
result['keyCode'] = keyCode;
|
||||
result['scanCode'] = scanCode;
|
||||
result['modifiers'] = _getGlfwModifierFlags(key, isDown);
|
||||
result['unicodeScalarValues'] = _keyLabel(key).isNotEmpty ? _keyLabel(key).codeUnitAt(0) : 0;
|
||||
result['unicodeScalarValues'] = resultCharacter.isNotEmpty ? resultCharacter.codeUnitAt(0) : 0;
|
||||
break;
|
||||
case 'macos':
|
||||
result['keyCode'] = scanCode;
|
||||
if (_keyLabel(key).isNotEmpty) {
|
||||
result['characters'] = _keyLabel(key);
|
||||
result['charactersIgnoringModifiers'] = _keyLabel(key);
|
||||
if (resultCharacter.isNotEmpty) {
|
||||
result['characters'] = resultCharacter;
|
||||
result['charactersIgnoringModifiers'] = resultCharacter;
|
||||
}
|
||||
result['modifiers'] = _getMacOsModifierFlags(key, isDown);
|
||||
break;
|
||||
case 'ios':
|
||||
result['keyCode'] = scanCode;
|
||||
result['characters'] = _keyLabel(key);
|
||||
result['charactersIgnoringModifiers'] = _keyLabel(key);
|
||||
result['characters'] = resultCharacter;
|
||||
result['charactersIgnoringModifiers'] = resultCharacter;
|
||||
result['modifiers'] = _getIOSModifierFlags(key, isDown);
|
||||
break;
|
||||
case 'web':
|
||||
result['code'] = _getWebKeyCode(key);
|
||||
result['key'] = _keyLabel(key);
|
||||
result['metaState'] = _getWebModifierFlags(key, isDown);
|
||||
break;
|
||||
case 'windows':
|
||||
result['keyCode'] = keyCode;
|
||||
result['scanCode'] = scanCode;
|
||||
if (_keyLabel(key).isNotEmpty) {
|
||||
result['characterCodePoint'] = _keyLabel(key).codeUnitAt(0);
|
||||
if (resultCharacter.isNotEmpty) {
|
||||
result['characterCodePoint'] = resultCharacter.codeUnitAt(0);
|
||||
}
|
||||
result['modifiers'] = _getWindowsModifierFlags(key, isDown);
|
||||
break;
|
||||
case 'web':
|
||||
assignWeb();
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -631,12 +635,12 @@ class KeyEventSimulator {
|
||||
/// See also:
|
||||
///
|
||||
/// - [simulateKeyUpEvent] to simulate the corresponding key up event.
|
||||
static Future<bool> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) async {
|
||||
static Future<bool> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey, String? character}) async {
|
||||
return TestAsyncUtils.guard<bool>(() async {
|
||||
platform ??= Platform.operatingSystem;
|
||||
assert(_osIsSupported(platform!), 'Platform $platform not supported for key simulation');
|
||||
|
||||
final Map<String, dynamic> data = getKeyData(key, platform: platform!, isDown: true, physicalKey: physicalKey);
|
||||
final Map<String, dynamic> data = getKeyData(key, platform: platform!, isDown: true, physicalKey: physicalKey, character: character);
|
||||
bool result = false;
|
||||
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.keyEvent.name,
|
||||
@ -715,8 +719,8 @@ class KeyEventSimulator {
|
||||
/// See also:
|
||||
///
|
||||
/// - [simulateKeyUpEvent] to simulate the corresponding key up event.
|
||||
Future<bool> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) {
|
||||
return KeyEventSimulator.simulateKeyDownEvent(key, platform: platform, physicalKey: physicalKey);
|
||||
Future<bool> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey, String? character}) {
|
||||
return KeyEventSimulator.simulateKeyDownEvent(key, platform: platform, physicalKey: physicalKey, character: character);
|
||||
}
|
||||
|
||||
/// Simulates sending a hardware key up event through the system channel.
|
||||
|
Loading…
Reference in New Issue
Block a user