Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
01c98fa9
Unverified
Commit
01c98fa9
authored
May 19, 2021
by
Tong Mu
Committed by
GitHub
May 19, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Character activator (#81807)
parent
7cdd33fe
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
289 additions
and
54 deletions
+289
-54
shortcuts.dart
packages/flutter/lib/src/widgets/shortcuts.dart
+148
-26
shortcuts_test.dart
packages/flutter/test/widgets/shortcuts_test.dart
+111
-2
controller.dart
packages/flutter_test/lib/src/controller.dart
+2
-2
event_simulation.dart
packages/flutter_test/lib/src/event_simulation.dart
+28
-24
No files found.
packages/flutter/lib/src/widgets/shortcuts.dart
View file @
01c98fa9
...
@@ -160,6 +160,8 @@ class KeySet<T extends KeyboardKey> {
...
@@ -160,6 +160,8 @@ class KeySet<T extends KeyboardKey> {
///
///
/// * [SingleActivator], an implementation that represents a single key combined
/// * [SingleActivator], an implementation that represents a single key combined
/// with modifiers (control, shift, alt, meta).
/// 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
/// * [LogicalKeySet], an implementation that requires one or more
/// [LogicalKeyboardKey]s to be pressed at the same time. Prefer
/// [LogicalKeyboardKey]s to be pressed at the same time. Prefer
/// [SingleActivator] when possible.
/// [SingleActivator] when possible.
...
@@ -179,7 +181,13 @@ abstract class ShortcutActivator {
...
@@ -179,7 +181,13 @@ abstract class ShortcutActivator {
/// [Intent]s are stored in a [Map] and indexed by trigger keys. Subclasses
/// [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
/// should make sure that the return value of this method does not change
/// throughout the lifespan of this object.
/// 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
/// Whether the triggering `event` and the keyboard `state` at the time of the
/// event meet required conditions, providing that the event is a triggering
/// event meet required conditions, providing that the event is a triggering
...
@@ -194,6 +202,9 @@ abstract class ShortcutActivator {
...
@@ -194,6 +202,9 @@ abstract class ShortcutActivator {
/// this is only used to query whether [RawKeyboard.keysPressed] contains
/// this is only used to query whether [RawKeyboard.keysPressed] contains
/// a key.
/// a key.
///
///
/// Since [ShortcutActivator] accepts all event types, subclasses might want
/// to check the event type in [accepts].
///
/// See also:
/// See also:
///
///
/// * [LogicalKeyboardKey.collapseSynonyms], which helps deciding whether a
/// * [LogicalKeyboardKey.collapseSynonyms], which helps deciding whether a
...
@@ -323,9 +334,6 @@ class LogicalKeySet extends KeySet<LogicalKeyboardKey> with Diagnosticable
...
@@ -323,9 +334,6 @@ class LogicalKeySet extends KeySet<LogicalKeyboardKey> with Diagnosticable
LogicalKeyboardKey
.
meta
:
<
LogicalKeyboardKey
>[
LogicalKeyboardKey
.
metaLeft
,
LogicalKeyboardKey
.
metaRight
],
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
@override
String
debugDescribeKeys
()
{
String
debugDescribeKeys
()
{
final
List
<
LogicalKeyboardKey
>
sortedKeys
=
keys
.
toList
()..
sort
(
final
List
<
LogicalKeyboardKey
>
sortedKeys
=
keys
.
toList
()..
sort
(
...
@@ -387,7 +395,7 @@ class ShortcutMapProperty extends DiagnosticsProperty<Map<ShortcutActivator, Int
...
@@ -387,7 +395,7 @@ class ShortcutMapProperty extends DiagnosticsProperty<Map<ShortcutActivator, Int
/// A shortcut key combination of a single key and modifiers.
/// A shortcut key combination of a single key and modifiers.
///
///
/// Th
is [Shortcut
Activator] implements typical shortcuts such as:
/// Th
e [Single
Activator] implements typical shortcuts such as:
///
///
/// * ArrowLeft
/// * ArrowLeft
/// * Shift + Delete
/// * Shift + Delete
...
@@ -412,6 +420,11 @@ class ShortcutMapProperty extends DiagnosticsProperty<Map<ShortcutActivator, Int
...
@@ -412,6 +420,11 @@ class ShortcutMapProperty extends DiagnosticsProperty<Map<ShortcutActivator, Int
/// * [SingleActivator]s do not consider modifiers to be a trigger key. For
/// * [SingleActivator]s do not consider modifiers to be a trigger key. For
/// example, pressing ControlLeft while holding key X *will not* activate a
/// example, pressing ControlLeft while holding key X *will not* activate a
/// `SingleActivator(LogicalKeyboardKey.keyX, control: true)`.
/// `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 {
class SingleActivator with Diagnosticable implements ShortcutActivator {
/// Create an activator of a trigger key and modifiers.
/// Create an activator of a trigger key and modifiers.
///
///
...
@@ -474,21 +487,21 @@ class SingleActivator with Diagnosticable implements ShortcutActivator {
...
@@ -474,21 +487,21 @@ class SingleActivator with Diagnosticable implements ShortcutActivator {
this.meta = false,
this.meta = false,
}) : // The enumerated check with `identical` is cumbersome but the only way
}) : // The enumerated check with `identical` is cumbersome but the only way
// since const constructors can not call functions such as `==` or
// since const constructors can not call functions such as `==` or
// `Set.contains`. Checking with `identical`
is sufficient sinc
e
// `Set.contains`. Checking with `identical`
might not work when th
e
//
`LogicalKeyboardKey` only provides cached valu
es.
//
key object is created from ID, but it covers common cas
es.
assert(
assert(
!identical(trigger, LogicalKeyboardKey.control) &&
!identical(trigger, LogicalKeyboardKey.control) &&
!identical(trigger, LogicalKeyboardKey.controlLeft) &&
!identical(trigger, LogicalKeyboardKey.controlLeft) &&
!identical(trigger, LogicalKeyboardKey.controlRight) &&
!identical(trigger, LogicalKeyboardKey.controlRight) &&
!identical(trigger, LogicalKeyboardKey.shift) &&
!identical(trigger, LogicalKeyboardKey.shift) &&
!identical(trigger, LogicalKeyboardKey.shiftLeft) &&
!identical(trigger, LogicalKeyboardKey.shiftLeft) &&
!identical(trigger, LogicalKeyboardKey.shiftRight) &&
!identical(trigger, LogicalKeyboardKey.shiftRight) &&
!identical(trigger, LogicalKeyboardKey.alt) &&
!identical(trigger, LogicalKeyboardKey.alt) &&
!identical(trigger, LogicalKeyboardKey.altLeft) &&
!identical(trigger, LogicalKeyboardKey.altLeft) &&
!identical(trigger, LogicalKeyboardKey.altRight) &&
!identical(trigger, LogicalKeyboardKey.altRight) &&
!identical(trigger, LogicalKeyboardKey.meta) &&
!identical(trigger, LogicalKeyboardKey.meta) &&
!identical(trigger, LogicalKeyboardKey.metaLeft) &&
!identical(trigger, LogicalKeyboardKey.metaLeft) &&
!identical(trigger, LogicalKeyboardKey.metaRight),
!identical(trigger, LogicalKeyboardKey.metaRight),
);
);
/// The non-modifier key of the shortcut that is pressed after all modifiers
/// The non-modifier key of the shortcut that is pressed after all modifiers
...
@@ -585,6 +598,110 @@ class SingleActivator with Diagnosticable implements ShortcutActivator {
...
@@ -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
{
class
_ActivatorIntentPair
with
Diagnosticable
{
const
_ActivatorIntentPair
(
this
.
activator
,
this
.
intent
);
const
_ActivatorIntentPair
(
this
.
activator
,
this
.
intent
);
final
ShortcutActivator
activator
;
final
ShortcutActivator
activator
;
...
@@ -639,20 +756,22 @@ class ShortcutManager extends ChangeNotifier with Diagnosticable {
...
@@ -639,20 +756,22 @@ class ShortcutManager extends ChangeNotifier with Diagnosticable {
}
}
}
}
static
Map
<
LogicalKeyboardKey
,
List
<
_ActivatorIntentPair
>>
_indexShortcuts
(
Map
<
ShortcutActivator
,
Intent
>
source
)
{
static
Map
<
LogicalKeyboardKey
?
,
List
<
_ActivatorIntentPair
>>
_indexShortcuts
(
Map
<
ShortcutActivator
,
Intent
>
source
)
{
final
Map
<
LogicalKeyboardKey
,
List
<
_ActivatorIntentPair
>>
result
=
<
LogicalKeyboardKey
,
List
<
_ActivatorIntentPair
>>{};
final
Map
<
LogicalKeyboardKey
?,
List
<
_ActivatorIntentPair
>>
result
=
<
LogicalKeyboardKey
?
,
List
<
_ActivatorIntentPair
>>{};
source
.
forEach
((
ShortcutActivator
activator
,
Intent
intent
)
{
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
>[])
result
.
putIfAbsent
(
trigger
,
()
=>
<
_ActivatorIntentPair
>[])
.
add
(
_ActivatorIntentPair
(
activator
,
intent
));
.
add
(
_ActivatorIntentPair
(
activator
,
intent
));
}
}
});
});
return
result
;
return
result
;
}
}
Map
<
LogicalKeyboardKey
,
List
<
_ActivatorIntentPair
>>
get
_indexedShortcuts
{
Map
<
LogicalKeyboardKey
?
,
List
<
_ActivatorIntentPair
>>
get
_indexedShortcuts
{
return
_indexedShortcutsCache
??=
_indexShortcuts
(
_shortcuts
);
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
/// Returns the [Intent], if any, that matches the current set of pressed
/// keys.
/// keys.
...
@@ -662,9 +781,12 @@ class ShortcutManager extends ChangeNotifier with Diagnosticable {
...
@@ -662,9 +781,12 @@ class ShortcutManager extends ChangeNotifier with Diagnosticable {
/// Defaults to a set derived from [RawKeyboard.keysPressed] if `keysPressed`
/// Defaults to a set derived from [RawKeyboard.keysPressed] if `keysPressed`
/// is not supplied.
/// is not supplied.
Intent
?
_find
(
RawKeyEvent
event
,
RawKeyboard
state
)
{
Intent
?
_find
(
RawKeyEvent
event
,
RawKeyboard
state
)
{
final
List
<
_ActivatorIntentPair
>?
candidates
=
_indexedShortcuts
[
event
.
logicalKey
];
final
List
<
_ActivatorIntentPair
>?
candidatesByKey
=
_indexedShortcuts
[
event
.
logicalKey
];
if
(
candidates
==
null
)
final
List
<
_ActivatorIntentPair
>?
candidatesByNull
=
_indexedShortcuts
[
null
];
return
null
;
final
List
<
_ActivatorIntentPair
>
candidates
=
<
_ActivatorIntentPair
>[
if
(
candidatesByKey
!=
null
)
...
candidatesByKey
,
if
(
candidatesByNull
!=
null
)
...
candidatesByNull
,
];
for
(
final
_ActivatorIntentPair
activatorIntent
in
candidates
)
{
for
(
final
_ActivatorIntentPair
activatorIntent
in
candidates
)
{
if
(
activatorIntent
.
activator
.
accepts
(
event
,
state
))
{
if
(
activatorIntent
.
activator
.
accepts
(
event
,
state
))
{
return
activatorIntent
.
intent
;
return
activatorIntent
.
intent
;
...
...
packages/flutter/test/widgets/shortcuts_test.dart
View file @
01c98fa9
...
@@ -9,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart';
...
@@ -9,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart';
typedef
PostInvokeCallback
=
void
Function
({
Action
<
Intent
>
action
,
Intent
intent
,
BuildContext
?
context
,
ActionDispatcher
dispatcher
});
typedef
PostInvokeCallback
=
void
Function
({
Action
<
Intent
>
action
,
Intent
intent
,
BuildContext
?
context
,
ActionDispatcher
dispatcher
});
class
TestAction
extends
CallbackAction
<
Test
Intent
>
{
class
TestAction
extends
CallbackAction
<
Intent
>
{
TestAction
({
TestAction
({
required
OnInvokeCallback
onInvoke
,
required
OnInvokeCallback
onInvoke
,
})
:
assert
(
onInvoke
!=
null
),
})
:
assert
(
onInvoke
!=
null
),
...
@@ -31,10 +31,47 @@ class TestDispatcher extends ActionDispatcher {
...
@@ -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
{
class
TestIntent
extends
Intent
{
const
TestIntent
();
const
TestIntent
();
}
}
class
TestIntent2
extends
Intent
{
const
TestIntent2
();
}
class
TestShortcutManager
extends
ShortcutManager
{
class
TestShortcutManager
extends
ShortcutManager
{
TestShortcutManager
(
this
.
keys
);
TestShortcutManager
(
this
.
keys
);
...
@@ -49,7 +86,13 @@ class TestShortcutManager extends ShortcutManager {
...
@@ -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
(
return
Actions
(
key:
GlobalKey
(),
key:
GlobalKey
(),
actions:
<
Type
,
Action
<
Intent
>>{
actions:
<
Type
,
Action
<
Intent
>>{
...
@@ -57,10 +100,16 @@ Widget activatorTester(ShortcutActivator activator, ValueSetter<Intent> onInvoke
...
@@ -57,10 +100,16 @@ Widget activatorTester(ShortcutActivator activator, ValueSetter<Intent> onInvoke
onInvoke
(
intent
);
onInvoke
(
intent
);
return
true
;
return
true
;
}),
}),
if
(
hasSecond
)
TestIntent2:
TestAction
(
onInvoke:
(
Intent
intent
)
{
onInvoke2
(
intent
);
}),
},
},
child:
Shortcuts
(
child:
Shortcuts
(
shortcuts:
<
ShortcutActivator
,
Intent
>{
shortcuts:
<
ShortcutActivator
,
Intent
>{
activator:
const
TestIntent
(),
activator:
const
TestIntent
(),
if
(
hasSecond
)
activator2:
const
TestIntent2
(),
},
},
child:
const
Focus
(
child:
const
Focus
(
autofocus:
true
,
autofocus:
true
,
...
@@ -967,5 +1016,65 @@ void main() {
...
@@ -967,5 +1016,65 @@ void main() {
expect
(
value
,
isTrue
);
expect
(
value
,
isTrue
);
expect
(
controller
.
position
.
pixels
,
0.0
);
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
;
});
});
});
}
}
packages/flutter_test/lib/src/controller.dart
View file @
01c98fa9
...
@@ -1021,10 +1021,10 @@ abstract class WidgetController {
...
@@ -1021,10 +1021,10 @@ abstract class WidgetController {
///
///
/// - [sendKeyUpEvent] to simulate the corresponding key up event.
/// - [sendKeyUpEvent] to simulate the corresponding key up event.
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
/// - [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
);
assert
(
platform
!=
null
);
// Internally wrapped in async guard.
// 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.
/// Simulates sending a physical key up event through the system channel.
...
...
packages/flutter_test/lib/src/event_simulation.dart
View file @
01c98fa9
...
@@ -194,6 +194,7 @@ class KeyEventSimulator {
...
@@ -194,6 +194,7 @@ class KeyEventSimulator {
required
String
platform
,
required
String
platform
,
bool
isDown
=
true
,
bool
isDown
=
true
,
PhysicalKeyboardKey
?
physicalKey
,
PhysicalKeyboardKey
?
physicalKey
,
String
?
character
,
})
{
})
{
assert
(
_osIsSupported
(
platform
),
'Platform
$platform
not supported for key simulation'
);
assert
(
_osIsSupported
(
platform
),
'Platform
$platform
not supported for key simulation'
);
...
@@ -211,27 +212,31 @@ class KeyEventSimulator {
...
@@ -211,27 +212,31 @@ class KeyEventSimulator {
'keymap'
:
platform
,
'keymap'
:
platform
,
};
};
if
(
kIsWeb
)
{
final
String
resultCharacter
=
character
??
_keyLabel
(
key
);
void
assignWeb
()
{
result
[
'code'
]
=
_getWebKeyCode
(
key
);
result
[
'code'
]
=
_getWebKeyCode
(
key
);
result
[
'key'
]
=
_keyLabel
(
key
)
;
result
[
'key'
]
=
resultCharacter
;
result
[
'metaState'
]
=
_getWebModifierFlags
(
key
,
isDown
);
result
[
'metaState'
]
=
_getWebModifierFlags
(
key
,
isDown
);
}
if
(
kIsWeb
)
{
assignWeb
();
return
result
;
return
result
;
}
}
switch
(
platform
)
{
switch
(
platform
)
{
case
'android'
:
case
'android'
:
result
[
'keyCode'
]
=
keyCode
;
result
[
'keyCode'
]
=
keyCode
;
if
(
_keyLabel
(
key
)
.
isNotEmpty
)
{
if
(
resultCharacter
.
isNotEmpty
)
{
result
[
'codePoint'
]
=
_keyLabel
(
key
)
.
codeUnitAt
(
0
);
result
[
'codePoint'
]
=
resultCharacter
.
codeUnitAt
(
0
);
result
[
'character'
]
=
_keyLabel
(
key
)
;
result
[
'character'
]
=
resultCharacter
;
}
}
result
[
'scanCode'
]
=
scanCode
;
result
[
'scanCode'
]
=
scanCode
;
result
[
'metaState'
]
=
_getAndroidModifierFlags
(
key
,
isDown
);
result
[
'metaState'
]
=
_getAndroidModifierFlags
(
key
,
isDown
);
break
;
break
;
case
'fuchsia'
:
case
'fuchsia'
:
result
[
'hidUsage'
]
=
physicalKey
.
usbHidUsage
;
result
[
'hidUsage'
]
=
physicalKey
.
usbHidUsage
;
if
(
_keyLabel
(
key
)
.
isNotEmpty
)
{
if
(
resultCharacter
.
isNotEmpty
)
{
result
[
'codePoint'
]
=
_keyLabel
(
key
)
.
codeUnitAt
(
0
);
result
[
'codePoint'
]
=
resultCharacter
.
codeUnitAt
(
0
);
}
}
result
[
'modifiers'
]
=
_getFuchsiaModifierFlags
(
key
,
isDown
);
result
[
'modifiers'
]
=
_getFuchsiaModifierFlags
(
key
,
isDown
);
break
;
break
;
...
@@ -240,34 +245,33 @@ class KeyEventSimulator {
...
@@ -240,34 +245,33 @@ class KeyEventSimulator {
result
[
'keyCode'
]
=
keyCode
;
result
[
'keyCode'
]
=
keyCode
;
result
[
'scanCode'
]
=
scanCode
;
result
[
'scanCode'
]
=
scanCode
;
result
[
'modifiers'
]
=
_getGlfwModifierFlags
(
key
,
isDown
);
result
[
'modifiers'
]
=
_getGlfwModifierFlags
(
key
,
isDown
);
result
[
'unicodeScalarValues'
]
=
_keyLabel
(
key
).
isNotEmpty
?
_keyLabel
(
key
)
.
codeUnitAt
(
0
)
:
0
;
result
[
'unicodeScalarValues'
]
=
resultCharacter
.
isNotEmpty
?
resultCharacter
.
codeUnitAt
(
0
)
:
0
;
break
;
break
;
case
'macos'
:
case
'macos'
:
result
[
'keyCode'
]
=
scanCode
;
result
[
'keyCode'
]
=
scanCode
;
if
(
_keyLabel
(
key
)
.
isNotEmpty
)
{
if
(
resultCharacter
.
isNotEmpty
)
{
result
[
'characters'
]
=
_keyLabel
(
key
)
;
result
[
'characters'
]
=
resultCharacter
;
result
[
'charactersIgnoringModifiers'
]
=
_keyLabel
(
key
)
;
result
[
'charactersIgnoringModifiers'
]
=
resultCharacter
;
}
}
result
[
'modifiers'
]
=
_getMacOsModifierFlags
(
key
,
isDown
);
result
[
'modifiers'
]
=
_getMacOsModifierFlags
(
key
,
isDown
);
break
;
break
;
case
'ios'
:
case
'ios'
:
result
[
'keyCode'
]
=
scanCode
;
result
[
'keyCode'
]
=
scanCode
;
result
[
'characters'
]
=
_keyLabel
(
key
)
;
result
[
'characters'
]
=
resultCharacter
;
result
[
'charactersIgnoringModifiers'
]
=
_keyLabel
(
key
)
;
result
[
'charactersIgnoringModifiers'
]
=
resultCharacter
;
result
[
'modifiers'
]
=
_getIOSModifierFlags
(
key
,
isDown
);
result
[
'modifiers'
]
=
_getIOSModifierFlags
(
key
,
isDown
);
break
;
break
;
case
'web'
:
result
[
'code'
]
=
_getWebKeyCode
(
key
);
result
[
'key'
]
=
_keyLabel
(
key
);
result
[
'metaState'
]
=
_getWebModifierFlags
(
key
,
isDown
);
break
;
case
'windows'
:
case
'windows'
:
result
[
'keyCode'
]
=
keyCode
;
result
[
'keyCode'
]
=
keyCode
;
result
[
'scanCode'
]
=
scanCode
;
result
[
'scanCode'
]
=
scanCode
;
if
(
_keyLabel
(
key
)
.
isNotEmpty
)
{
if
(
resultCharacter
.
isNotEmpty
)
{
result
[
'characterCodePoint'
]
=
_keyLabel
(
key
)
.
codeUnitAt
(
0
);
result
[
'characterCodePoint'
]
=
resultCharacter
.
codeUnitAt
(
0
);
}
}
result
[
'modifiers'
]
=
_getWindowsModifierFlags
(
key
,
isDown
);
result
[
'modifiers'
]
=
_getWindowsModifierFlags
(
key
,
isDown
);
break
;
case
'web'
:
assignWeb
();
break
;
}
}
return
result
;
return
result
;
}
}
...
@@ -631,12 +635,12 @@ class KeyEventSimulator {
...
@@ -631,12 +635,12 @@ class KeyEventSimulator {
/// See also:
/// See also:
///
///
/// - [simulateKeyUpEvent] to simulate the corresponding key up event.
/// - [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
{
return
TestAsyncUtils
.
guard
<
bool
>(()
async
{
platform
??=
Platform
.
operatingSystem
;
platform
??=
Platform
.
operatingSystem
;
assert
(
_osIsSupported
(
platform
!),
'Platform
$platform
not supported for key simulation'
);
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
;
bool
result
=
false
;
await
ServicesBinding
.
instance
!.
defaultBinaryMessenger
.
handlePlatformMessage
(
await
ServicesBinding
.
instance
!.
defaultBinaryMessenger
.
handlePlatformMessage
(
SystemChannels
.
keyEvent
.
name
,
SystemChannels
.
keyEvent
.
name
,
...
@@ -715,8 +719,8 @@ class KeyEventSimulator {
...
@@ -715,8 +719,8 @@ class KeyEventSimulator {
/// See also:
/// See also:
///
///
/// - [simulateKeyUpEvent] to simulate the corresponding key up event.
/// - [simulateKeyUpEvent] to simulate the corresponding key up event.
Future
<
bool
>
simulateKeyDownEvent
(
LogicalKeyboardKey
key
,
{
String
?
platform
,
PhysicalKeyboardKey
?
physicalKey
})
{
Future
<
bool
>
simulateKeyDownEvent
(
LogicalKeyboardKey
key
,
{
String
?
platform
,
PhysicalKeyboardKey
?
physicalKey
,
String
?
character
})
{
return
KeyEventSimulator
.
simulateKeyDownEvent
(
key
,
platform:
platform
,
physicalKey:
physicalKey
);
return
KeyEventSimulator
.
simulateKeyDownEvent
(
key
,
platform:
platform
,
physicalKey:
physicalKey
,
character:
character
);
}
}
/// Simulates sending a hardware key up event through the system channel.
/// Simulates sending a hardware key up event through the system channel.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment