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
cda8041e
Unverified
Commit
cda8041e
authored
Jul 19, 2022
by
Tong Mu
Committed by
GitHub
Jul 19, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Keyboard] Make CharacterActivator support Ctrl and Meta modifiers, and repeats (#107195)
parent
74ac8678
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
172 additions
and
18 deletions
+172
-18
shortcuts.dart
packages/flutter/lib/src/widgets/shortcuts.dart
+78
-17
shortcuts_test.dart
packages/flutter/test/widgets/shortcuts_test.dart
+94
-1
No files found.
packages/flutter/lib/src/widgets/shortcuts.dart
View file @
cda8041e
...
...
@@ -393,12 +393,13 @@ class ShortcutMapProperty extends DiagnosticsProperty<Map<ShortcutActivator, Int
class SingleActivator with Diagnosticable, MenuSerializableShortcut implements ShortcutActivator {
/// Triggered when the [trigger] key is pressed while the modifiers are held.
///
/// The
`trigger`
should be the non-modifier key that is pressed after all the
/// The
[trigger]
should be the non-modifier key that is pressed after all the
/// modifiers, such as [LogicalKeyboardKey.keyC] as in `Ctrl+C`. It must not be
/// a modifier key (sided or unsided).
///
/// The `control`, `shift`, `alt`, and `meta` flags represent whether
/// the respect modifier keys should be held (true) or released (false)
/// The [control], [shift], [alt], and [meta] flags represent whether
/// the respect modifier keys should be held (true) or released (false).
/// They default to false.
///
/// By default, the activator is checked on all [RawKeyDownEvent] events for
/// the [trigger] key. If `includeRepeats` is false, only the [trigger] key
...
...
@@ -445,8 +446,9 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S
/// Whether either (or both) control keys should be held for [trigger] to
/// activate the shortcut.
///
/// If false, then all control keys must be released when the event is received
/// in order to activate the shortcut.
/// It defaults to false, meaning all Control keys must be released when the
/// event is received in order to activate the shortcut. If it'
s
true
,
then
/// either or both Control keys must be pressed.
///
/// See also:
///
...
...
@@ -456,8 +458,9 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S
/// Whether either (or both) shift keys should be held for [trigger] to
/// activate the shortcut.
///
/// If false, then all shift keys must be released when the event is received
/// in order to activate the shortcut.
/// It defaults to false, meaning all Shift keys must be released when the
/// event is received in order to activate the shortcut. If it's true, then
/// either or both Shift keys must be pressed.
///
/// See also:
///
...
...
@@ -467,8 +470,9 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S
/// Whether either (or both) alt keys should be held for [trigger] to
/// activate the shortcut.
///
/// If false, then all alt keys must be released when the event is received
/// in order to activate the shortcut.
/// It defaults to false, meaning all Alt keys must be released when the
/// event is received in order to activate the shortcut. If it's true, then
/// either or both Alt keys must be pressed.
///
/// See also:
///
...
...
@@ -478,8 +482,9 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S
/// Whether either (or both) meta keys should be held for [trigger] to
/// activate the shortcut.
///
/// If false, then all meta keys must be released when the event is received
/// in order to activate the shortcut.
/// It defaults to false, meaning all Meta keys must be released when the
/// event is received in order to activate the shortcut. If it's true, then
/// either or both Meta keys must be pressed.
///
/// See also:
///
...
...
@@ -545,7 +550,7 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties.add(
DiagnosticsProperty<String>
('
keys
', debugDescribeKeys()));
properties
.
add
(
MessageProperty
(
'keys'
,
debugDescribeKeys
()));
properties
.
add
(
FlagProperty
(
'includeRepeats'
,
value:
includeRepeats
,
ifFalse:
'excluding repeats'
));
}
}
...
...
@@ -577,8 +582,54 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S
/// * [SingleActivator], an activator that represents a single key combined
/// with modifiers, such as `Ctrl+C`.
class
CharacterActivator
with
Diagnosticable
,
MenuSerializableShortcut
implements
ShortcutActivator
{
/// Create a [CharacterActivator] from the triggering character.
const CharacterActivator(this.character);
/// Triggered when the key event yields the given character.
///
/// The [control] and [meta] flags represent whether the respect modifier
/// keys should be held (true) or released (false). They default to false.
/// [CharacterActivator] can not check Shift keys or Alt keys yet, and will
/// accept whether they are pressed or not.
///
/// By default, the activator is checked on all [RawKeyDownEvent] events for
/// the [character]. If `includeRepeats` is false, only the [character]
/// events with a false [RawKeyDownEvent.repeat] attribute will be
/// considered.
const
CharacterActivator
(
this
.
character
,
{
this
.
control
=
false
,
this
.
meta
=
false
,
this
.
includeRepeats
=
true
,
});
/// Whether either (or both) control keys should be held for the [character]
/// to activate the shortcut.
///
/// It defaults to false, meaning all Control keys must be released when the
/// event is received in order to activate the shortcut. If it's true, then
/// either or both Control keys must be pressed.
///
/// See also:
///
/// * [LogicalKeyboardKey.controlLeft], [LogicalKeyboardKey.controlRight].
final
bool
control
;
/// Whether either (or both) meta keys should be held for the [character] to
/// activate the shortcut.
///
/// It defaults to false, meaning all Meta keys must be released when the
/// event is received in order to activate the shortcut. If it's true, then
/// either or both Meta keys must be pressed.
///
/// See also:
///
/// * [LogicalKeyboardKey.metaLeft], [LogicalKeyboardKey.metaRight].
final
bool
meta
;
/// Whether this activator accepts repeat events of the [character].
///
/// If [includeRepeats] is true, the activator is checked on all
/// [RawKeyDownEvent] events for the [character]. If `includeRepeats` is
/// false, only the [character] events with a false [RawKeyDownEvent.repeat]
/// attribute will be considered.
final
bool
includeRepeats
;
/// The character of the triggering event.
///
...
...
@@ -598,15 +649,24 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement
@override
bool
accepts
(
RawKeyEvent
event
,
RawKeyboard
state
)
{
final
Set
<
LogicalKeyboardKey
>
pressed
=
state
.
keysPressed
;
return
event
is
RawKeyDownEvent
&&
event
.
character
==
character
;
&&
event
.
character
==
character
&&
(
includeRepeats
||
!
event
.
repeat
)
&&
(
control
==
(
pressed
.
contains
(
LogicalKeyboardKey
.
controlLeft
)
||
pressed
.
contains
(
LogicalKeyboardKey
.
controlRight
)))
&&
(
meta
==
(
pressed
.
contains
(
LogicalKeyboardKey
.
metaLeft
)
||
pressed
.
contains
(
LogicalKeyboardKey
.
metaRight
)));
}
@override
String
debugDescribeKeys
()
{
String
result
=
''
;
assert
(()
{
result
=
"'
$character
'"
;
final
List
<
String
>
keys
=
<
String
>[
if
(
control
)
'Control'
,
if
(
meta
)
'Meta'
,
"'
$character
'"
,
];
result
=
keys
.
join
(
' + '
);
return
true
;
}());
return
result
;
...
...
@@ -620,7 +680,8 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
StringProperty
(
'character'
,
character
));
properties
.
add
(
MessageProperty
(
'character'
,
debugDescribeKeys
()));
properties
.
add
(
FlagProperty
(
'includeRepeats'
,
value:
includeRepeats
,
ifFalse:
'excluding repeats'
));
}
}
...
...
packages/flutter/test/widgets/shortcuts_test.dart
View file @
cda8041e
...
...
@@ -1112,7 +1112,7 @@ void main() {
));
await
tester
.
pump
();
// Press
KeyC: Accepted by DumbLogicalActivator
// Press
Shift + /
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
shiftLeft
);
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
slash
,
character:
'?'
);
expect
(
invoked
,
1
);
...
...
@@ -1142,6 +1142,53 @@ void main() {
invoked
=
0
;
},
variant:
KeySimulatorTransitModeVariant
.
all
());
testWidgets
(
'rejects repeated events if requested'
,
(
WidgetTester
tester
)
async
{
int
invoked
=
0
;
await
tester
.
pumpWidget
(
activatorTester
(
const
CharacterActivator
(
'?'
,
includeRepeats:
false
),
(
Intent
intent
)
{
invoked
+=
1
;
},
));
await
tester
.
pump
();
// Press Shift + /
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
shiftLeft
);
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
slash
,
character:
'?'
);
expect
(
invoked
,
1
);
await
tester
.
sendKeyRepeatEvent
(
LogicalKeyboardKey
.
slash
,
character:
'?'
);
expect
(
invoked
,
1
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
slash
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
shiftLeft
);
expect
(
invoked
,
1
);
invoked
=
0
;
},
variant:
KeySimulatorTransitModeVariant
.
all
());
testWidgets
(
'handles Ctrl and Meta'
,
(
WidgetTester
tester
)
async
{
int
invoked
=
0
;
await
tester
.
pumpWidget
(
activatorTester
(
const
CharacterActivator
(
'?'
,
meta:
true
,
control:
true
),
(
Intent
intent
)
{
invoked
+=
1
;
},
));
await
tester
.
pump
();
// Press Shift + /
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
shiftLeft
);
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
slash
,
character:
'?'
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
slash
);
expect
(
invoked
,
0
);
// Press Ctrl + Meta + Shift + /
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
metaLeft
);
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
controlLeft
);
expect
(
invoked
,
0
);
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
slash
,
character:
'?'
);
expect
(
invoked
,
1
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
slash
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
shiftLeft
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
metaLeft
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
controlLeft
);
expect
(
invoked
,
1
);
invoked
=
0
;
},
variant:
KeySimulatorTransitModeVariant
.
all
());
testWidgets
(
'isActivatedBy works as expected'
,
(
WidgetTester
tester
)
async
{
// Collect some key events to use for testing.
...
...
@@ -1163,6 +1210,52 @@ void main() {
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
keyA
);
expect
(
ShortcutActivator
.
isActivatedBy
(
characterActivator
,
events
[
0
]),
isTrue
);
});
group
(
'diagnostics.'
,
()
{
test
(
'single key'
,
()
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
const
CharacterActivator
(
'A'
).
debugFillProperties
(
builder
);
final
List
<
String
>
description
=
builder
.
properties
.
where
((
DiagnosticsNode
node
)
{
return
!
node
.
isFiltered
(
DiagnosticLevel
.
info
);
}).
map
((
DiagnosticsNode
node
)
=>
node
.
toString
()).
toList
();
expect
(
description
.
length
,
equals
(
1
));
expect
(
description
[
0
],
equals
(
"character: 'A'"
));
});
test
(
'no repeats'
,
()
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
const
CharacterActivator
(
'A'
,
includeRepeats:
false
)
.
debugFillProperties
(
builder
);
final
List
<
String
>
description
=
builder
.
properties
.
where
((
DiagnosticsNode
node
)
{
return
!
node
.
isFiltered
(
DiagnosticLevel
.
info
);
}).
map
((
DiagnosticsNode
node
)
=>
node
.
toString
()).
toList
();
expect
(
description
.
length
,
equals
(
2
));
expect
(
description
[
0
],
equals
(
"character: 'A'"
));
expect
(
description
[
1
],
equals
(
'excluding repeats'
));
});
test
(
'combination'
,
()
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
const
CharacterActivator
(
'A'
,
control:
true
,
meta:
true
,
).
debugFillProperties
(
builder
);
final
List
<
String
>
description
=
builder
.
properties
.
where
((
DiagnosticsNode
node
)
{
return
!
node
.
isFiltered
(
DiagnosticLevel
.
info
);
}).
map
((
DiagnosticsNode
node
)
=>
node
.
toString
()).
toList
();
expect
(
description
.
length
,
equals
(
1
));
expect
(
description
[
0
],
equals
(
"character: Control + Meta + 'A'"
));
});
});
});
group
(
'CallbackShortcuts'
,
()
{
...
...
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