Skip to content

Commit 1b4800c

Browse files
authored
Add support for Alt to CharacterActivator, add tests (#113466)
1 parent 4abe6fd commit 1b4800c

File tree

4 files changed

+127
-50
lines changed

4 files changed

+127
-50
lines changed

packages/flutter/lib/src/widgets/platform_menu_bar.dart

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -45,78 +45,100 @@ class ShortcutSerialization {
4545
/// Creates a [ShortcutSerialization] representing a single character.
4646
///
4747
/// This is used by a [CharacterActivator] to serialize itself.
48-
ShortcutSerialization.character(String character)
49-
: _internal = <String, Object?>{_kShortcutCharacter: character},
48+
ShortcutSerialization.character(String character, {
49+
bool alt = false,
50+
bool control = false,
51+
bool meta = false,
52+
}) : assert(character.length == 1),
5053
_character = character,
51-
assert(character.length == 1);
54+
_trigger = null,
55+
_alt = alt,
56+
_control = control,
57+
_meta = meta,
58+
_shift = null,
59+
_internal = <String, Object?>{
60+
_kShortcutCharacter: character,
61+
_kShortcutModifiers: (control ? _shortcutModifierControl : 0) |
62+
(alt ? _shortcutModifierAlt : 0) |
63+
(meta ? _shortcutModifierMeta : 0),
64+
};
5265

5366
/// Creates a [ShortcutSerialization] representing a specific
5467
/// [LogicalKeyboardKey] and modifiers.
5568
///
5669
/// This is used by a [SingleActivator] to serialize itself.
5770
ShortcutSerialization.modifier(
5871
LogicalKeyboardKey trigger, {
59-
bool control = false,
60-
bool shift = false,
6172
bool alt = false,
73+
bool control = false,
6274
bool meta = false,
63-
}) : assert(trigger != LogicalKeyboardKey.shift &&
64-
trigger != LogicalKeyboardKey.shiftLeft &&
65-
trigger != LogicalKeyboardKey.shiftRight &&
66-
trigger != LogicalKeyboardKey.alt &&
75+
bool shift = false,
76+
}) : assert(trigger != LogicalKeyboardKey.alt &&
6777
trigger != LogicalKeyboardKey.altLeft &&
6878
trigger != LogicalKeyboardKey.altRight &&
6979
trigger != LogicalKeyboardKey.control &&
7080
trigger != LogicalKeyboardKey.controlLeft &&
7181
trigger != LogicalKeyboardKey.controlRight &&
7282
trigger != LogicalKeyboardKey.meta &&
7383
trigger != LogicalKeyboardKey.metaLeft &&
74-
trigger != LogicalKeyboardKey.metaRight,
84+
trigger != LogicalKeyboardKey.metaRight &&
85+
trigger != LogicalKeyboardKey.shift &&
86+
trigger != LogicalKeyboardKey.shiftLeft &&
87+
trigger != LogicalKeyboardKey.shiftRight,
7588
'Specifying a modifier key as a trigger is not allowed. '
7689
'Use provided boolean parameters instead.'),
7790
_trigger = trigger,
78-
_control = control,
79-
_shift = shift,
91+
_character = null,
8092
_alt = alt,
93+
_control = control,
8194
_meta = meta,
95+
_shift = shift,
8296
_internal = <String, Object?>{
8397
_kShortcutTrigger: trigger.keyId,
84-
_kShortcutModifiers: (control ? _shortcutModifierControl : 0) |
85-
(alt ? _shortcutModifierAlt : 0) |
86-
(shift ? _shortcutModifierShift : 0) |
87-
(meta ? _shortcutModifierMeta : 0),
98+
_kShortcutModifiers: (alt ? _shortcutModifierAlt : 0) |
99+
(control ? _shortcutModifierControl : 0) |
100+
(meta ? _shortcutModifierMeta : 0) |
101+
(shift ? _shortcutModifierShift : 0),
88102
};
89103

90104
final Map<String, Object?> _internal;
91105

92106
/// The keyboard key that triggers this shortcut, if any.
93107
LogicalKeyboardKey? get trigger => _trigger;
94-
LogicalKeyboardKey? _trigger;
108+
final LogicalKeyboardKey? _trigger;
95109

96110
/// The character that triggers this shortcut, if any.
97111
String? get character => _character;
98-
String? _character;
112+
final String? _character;
113+
114+
/// If this shortcut has a [trigger], this indicates whether or not the
115+
/// alt modifier needs to be down or not.
116+
bool? get alt => _alt;
117+
final bool? _alt;
99118

100119
/// If this shortcut has a [trigger], this indicates whether or not the
101120
/// control modifier needs to be down or not.
102121
bool? get control => _control;
103-
bool? _control;
122+
final bool? _control;
123+
124+
/// If this shortcut has a [trigger], this indicates whether or not the meta
125+
/// (also known as the Windows or Command key) modifier needs to be down or
126+
/// not.
127+
bool? get meta => _meta;
128+
final bool? _meta;
104129

105130
/// If this shortcut has a [trigger], this indicates whether or not the
106131
/// shift modifier needs to be down or not.
107132
bool? get shift => _shift;
108-
bool? _shift;
133+
final bool? _shift;
109134

110-
/// If this shortcut has a [trigger], this indicates whether or not the
111-
/// alt modifier needs to be down or not.
112-
bool? get alt => _alt;
113-
bool? _alt;
135+
/// The bit mask for the [LogicalKeyboardKey.alt] key (or it's left/right
136+
/// equivalents) being down.
137+
static const int _shortcutModifierAlt = 1 << 2;
114138

115-
/// If this shortcut has a [trigger], this indicates whether or not the meta
116-
/// (also known as the Windows or Command key) modifier needs to be down or
117-
/// not.
118-
bool? get meta => _meta;
119-
bool? _meta;
139+
/// The bit mask for the [LogicalKeyboardKey.control] key (or it's left/right
140+
/// equivalents) being down.
141+
static const int _shortcutModifierControl = 1 << 3;
120142

121143
/// The bit mask for the [LogicalKeyboardKey.meta] key (or it's left/right
122144
/// equivalents) being down.
@@ -126,14 +148,6 @@ class ShortcutSerialization {
126148
/// equivalents) being down.
127149
static const int _shortcutModifierShift = 1 << 1;
128150

129-
/// The bit mask for the [LogicalKeyboardKey.alt] key (or it's left/right
130-
/// equivalents) being down.
131-
static const int _shortcutModifierAlt = 1 << 2;
132-
133-
/// The bit mask for the [LogicalKeyboardKey.alt] key (or it's left/right
134-
/// equivalents) being down.
135-
static const int _shortcutModifierControl = 1 << 3;
136-
137151
/// Converts the internal representation to the format needed for a
138152
/// [PlatformMenuItem] to include it in its serialized form for sending to the
139153
/// platform.

packages/flutter/lib/src/widgets/shortcuts.dart

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -580,25 +580,40 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S
580580
/// See also:
581581
///
582582
/// * [SingleActivator], an activator that represents a single key combined
583-
/// with modifiers, such as `Ctrl+C`.
583+
/// with modifiers, such as `Ctrl+C` or `Ctrl-Right Arrow`.
584584
class CharacterActivator with Diagnosticable, MenuSerializableShortcut implements ShortcutActivator {
585585
/// Triggered when the key event yields the given character.
586586
///
587-
/// The [control] and [meta] flags represent whether the respect modifier
588-
/// keys should be held (true) or released (false). They default to false.
589-
/// [CharacterActivator] can not check Shift keys or Alt keys yet, and will
590-
/// accept whether they are pressed or not.
587+
/// The [alt], [control], and [meta] flags represent whether the respective
588+
/// modifier keys should be held (true) or released (false). They default to
589+
/// false. [CharacterActivator] cannot check Shift keys, since the shift key
590+
/// affects the resulting character, and will accept whether either of the
591+
/// Shift keys are pressed or not, as long as the key event produces the
592+
/// correct character.
591593
///
592594
/// By default, the activator is checked on all [RawKeyDownEvent] events for
593-
/// the [character]. If `includeRepeats` is false, only the [character]
594-
/// events with a false [RawKeyDownEvent.repeat] attribute will be
595-
/// considered.
595+
/// the [character] in combination with the requested modifier keys. If
596+
/// `includeRepeats` is false, only the [character] events with a false
597+
/// [RawKeyDownEvent.repeat] attribute will be considered.
596598
const CharacterActivator(this.character, {
599+
this.alt = false,
597600
this.control = false,
598601
this.meta = false,
599602
this.includeRepeats = true,
600603
});
601604

605+
/// Whether either (or both) alt keys should be held for the [character] to
606+
/// activate the shortcut.
607+
///
608+
/// It defaults to false, meaning all Alt keys must be released when the event
609+
/// is received in order to activate the shortcut. If it's true, then either
610+
/// or both Alt keys must be pressed.
611+
///
612+
/// See also:
613+
///
614+
/// * [LogicalKeyboardKey.altLeft], [LogicalKeyboardKey.altRight].
615+
final bool alt;
616+
602617
/// Whether either (or both) control keys should be held for the [character]
603618
/// to activate the shortcut.
604619
///
@@ -631,7 +646,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement
631646
/// attribute will be considered.
632647
final bool includeRepeats;
633648

634-
/// The character of the triggering event.
649+
/// The character which triggers the shortcut.
635650
///
636651
/// This is typically a single-character string, such as '?' or 'œ', although
637652
/// [CharacterActivator] doesn't check the length of [character] or whether it
@@ -653,6 +668,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement
653668
return event is RawKeyDownEvent
654669
&& event.character == character
655670
&& (includeRepeats || !event.repeat)
671+
&& (alt == (pressed.contains(LogicalKeyboardKey.altLeft) || pressed.contains(LogicalKeyboardKey.altRight)))
656672
&& (control == (pressed.contains(LogicalKeyboardKey.controlLeft) || pressed.contains(LogicalKeyboardKey.controlRight)))
657673
&& (meta == (pressed.contains(LogicalKeyboardKey.metaLeft) || pressed.contains(LogicalKeyboardKey.metaRight)));
658674
}
@@ -662,6 +678,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement
662678
String result = '';
663679
assert(() {
664680
final List<String> keys = <String>[
681+
if (alt) 'Alt',
665682
if (control) 'Control',
666683
if (meta) 'Meta',
667684
"'$character'",
@@ -674,7 +691,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement
674691

675692
@override
676693
ShortcutSerialization serializeForMenu() {
677-
return ShortcutSerialization.character(character);
694+
return ShortcutSerialization.character(character, alt: alt, control: control, meta: meta);
678695
}
679696

680697
@override

packages/flutter/test/widgets/platform_menu_bar_test.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,34 @@ void main() {
228228
]);
229229
});
230230
});
231+
232+
group('ShortcutSerialization', () {
233+
testWidgets('character constructor', (WidgetTester tester) async {
234+
final ShortcutSerialization serialization = ShortcutSerialization.character('?');
235+
expect(serialization.toChannelRepresentation(), equals(<String, Object?>{
236+
'shortcutCharacter': '?',
237+
'shortcutModifiers': 0,
238+
}));
239+
final ShortcutSerialization serializationWithModifiers = ShortcutSerialization.character('?', alt: true, control: true, meta: true);
240+
expect(serializationWithModifiers.toChannelRepresentation(), equals(<String, Object?>{
241+
'shortcutCharacter': '?',
242+
'shortcutModifiers': 13,
243+
}));
244+
});
245+
246+
testWidgets('modifier constructor', (WidgetTester tester) async {
247+
final ShortcutSerialization serialization = ShortcutSerialization.modifier(LogicalKeyboardKey.home);
248+
expect(serialization.toChannelRepresentation(), equals(<String, Object?>{
249+
'shortcutTrigger': LogicalKeyboardKey.home.keyId,
250+
'shortcutModifiers': 0,
251+
}));
252+
final ShortcutSerialization serializationWithModifiers = ShortcutSerialization.modifier(LogicalKeyboardKey.home, alt: true, control: true, meta: true, shift: true);
253+
expect(serializationWithModifiers.toChannelRepresentation(), equals(<String, Object?>{
254+
'shortcutTrigger': LogicalKeyboardKey.home.keyId,
255+
'shortcutModifiers': 15,
256+
}));
257+
});
258+
});
231259
}
232260

233261
const List<String> mainMenu = <String>[

packages/flutter/test/widgets/shortcuts_test.dart

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,10 +1162,10 @@ void main() {
11621162
invoked = 0;
11631163
}, variant: KeySimulatorTransitModeVariant.all());
11641164

1165-
testWidgets('handles Ctrl and Meta', (WidgetTester tester) async {
1165+
testWidgets('handles Alt, Ctrl and Meta', (WidgetTester tester) async {
11661166
int invoked = 0;
11671167
await tester.pumpWidget(activatorTester(
1168-
const CharacterActivator('?', meta: true, control: true),
1168+
const CharacterActivator('?', alt: true, meta: true, control: true),
11691169
(Intent intent) { invoked += 1; },
11701170
));
11711171
await tester.pump();
@@ -1176,7 +1176,8 @@ void main() {
11761176
await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
11771177
expect(invoked, 0);
11781178

1179-
// Press Ctrl + Meta + Shift + /
1179+
// Press Left Alt + Ctrl + Meta + Shift + /
1180+
await tester.sendKeyDownEvent(LogicalKeyboardKey.altLeft);
11801181
await tester.sendKeyDownEvent(LogicalKeyboardKey.metaLeft);
11811182
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
11821183
expect(invoked, 0);
@@ -1185,9 +1186,26 @@ void main() {
11851186
await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
11861187
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
11871188
await tester.sendKeyUpEvent(LogicalKeyboardKey.metaLeft);
1189+
await tester.sendKeyUpEvent(LogicalKeyboardKey.altLeft);
11881190
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
11891191
expect(invoked, 1);
11901192
invoked = 0;
1193+
1194+
// Press Right Alt + Ctrl + Meta + Shift + /
1195+
await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftRight);
1196+
await tester.sendKeyDownEvent(LogicalKeyboardKey.altRight);
1197+
await tester.sendKeyDownEvent(LogicalKeyboardKey.metaRight);
1198+
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight);
1199+
expect(invoked, 0);
1200+
await tester.sendKeyDownEvent(LogicalKeyboardKey.slash, character: '?');
1201+
expect(invoked, 1);
1202+
await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
1203+
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftRight);
1204+
await tester.sendKeyUpEvent(LogicalKeyboardKey.metaRight);
1205+
await tester.sendKeyUpEvent(LogicalKeyboardKey.altRight);
1206+
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight);
1207+
expect(invoked, 1);
1208+
invoked = 0;
11911209
}, variant: KeySimulatorTransitModeVariant.all());
11921210

11931211
testWidgets('isActivatedBy works as expected', (WidgetTester tester) async {

0 commit comments

Comments
 (0)