Add support for Alt to CharacterActivator, add tests (#113466)
diff --git a/packages/flutter/lib/src/widgets/platform_menu_bar.dart b/packages/flutter/lib/src/widgets/platform_menu_bar.dart index f5ded7a..b670c96 100644 --- a/packages/flutter/lib/src/widgets/platform_menu_bar.dart +++ b/packages/flutter/lib/src/widgets/platform_menu_bar.dart
@@ -45,10 +45,23 @@ /// Creates a [ShortcutSerialization] representing a single character. /// /// This is used by a [CharacterActivator] to serialize itself. - ShortcutSerialization.character(String character) - : _internal = <String, Object?>{_kShortcutCharacter: character}, + ShortcutSerialization.character(String character, { + bool alt = false, + bool control = false, + bool meta = false, + }) : assert(character.length == 1), _character = character, - assert(character.length == 1); + _trigger = null, + _alt = alt, + _control = control, + _meta = meta, + _shift = null, + _internal = <String, Object?>{ + _kShortcutCharacter: character, + _kShortcutModifiers: (control ? _shortcutModifierControl : 0) | + (alt ? _shortcutModifierAlt : 0) | + (meta ? _shortcutModifierMeta : 0), + }; /// Creates a [ShortcutSerialization] representing a specific /// [LogicalKeyboardKey] and modifiers. @@ -56,14 +69,11 @@ /// This is used by a [SingleActivator] to serialize itself. ShortcutSerialization.modifier( LogicalKeyboardKey trigger, { - bool control = false, - bool shift = false, bool alt = false, + bool control = false, bool meta = false, - }) : assert(trigger != LogicalKeyboardKey.shift && - trigger != LogicalKeyboardKey.shiftLeft && - trigger != LogicalKeyboardKey.shiftRight && - trigger != LogicalKeyboardKey.alt && + bool shift = false, + }) : assert(trigger != LogicalKeyboardKey.alt && trigger != LogicalKeyboardKey.altLeft && trigger != LogicalKeyboardKey.altRight && trigger != LogicalKeyboardKey.control && @@ -71,52 +81,64 @@ trigger != LogicalKeyboardKey.controlRight && trigger != LogicalKeyboardKey.meta && trigger != LogicalKeyboardKey.metaLeft && - trigger != LogicalKeyboardKey.metaRight, + trigger != LogicalKeyboardKey.metaRight && + trigger != LogicalKeyboardKey.shift && + trigger != LogicalKeyboardKey.shiftLeft && + trigger != LogicalKeyboardKey.shiftRight, 'Specifying a modifier key as a trigger is not allowed. ' 'Use provided boolean parameters instead.'), _trigger = trigger, - _control = control, - _shift = shift, + _character = null, _alt = alt, + _control = control, _meta = meta, + _shift = shift, _internal = <String, Object?>{ _kShortcutTrigger: trigger.keyId, - _kShortcutModifiers: (control ? _shortcutModifierControl : 0) | - (alt ? _shortcutModifierAlt : 0) | - (shift ? _shortcutModifierShift : 0) | - (meta ? _shortcutModifierMeta : 0), + _kShortcutModifiers: (alt ? _shortcutModifierAlt : 0) | + (control ? _shortcutModifierControl : 0) | + (meta ? _shortcutModifierMeta : 0) | + (shift ? _shortcutModifierShift : 0), }; final Map<String, Object?> _internal; /// The keyboard key that triggers this shortcut, if any. LogicalKeyboardKey? get trigger => _trigger; - LogicalKeyboardKey? _trigger; + final LogicalKeyboardKey? _trigger; /// The character that triggers this shortcut, if any. String? get character => _character; - String? _character; - - /// If this shortcut has a [trigger], this indicates whether or not the - /// control modifier needs to be down or not. - bool? get control => _control; - bool? _control; - - /// If this shortcut has a [trigger], this indicates whether or not the - /// shift modifier needs to be down or not. - bool? get shift => _shift; - bool? _shift; + final String? _character; /// If this shortcut has a [trigger], this indicates whether or not the /// alt modifier needs to be down or not. bool? get alt => _alt; - bool? _alt; + final bool? _alt; + + /// If this shortcut has a [trigger], this indicates whether or not the + /// control modifier needs to be down or not. + bool? get control => _control; + final bool? _control; /// If this shortcut has a [trigger], this indicates whether or not the meta /// (also known as the Windows or Command key) modifier needs to be down or /// not. bool? get meta => _meta; - bool? _meta; + final bool? _meta; + + /// If this shortcut has a [trigger], this indicates whether or not the + /// shift modifier needs to be down or not. + bool? get shift => _shift; + final bool? _shift; + + /// The bit mask for the [LogicalKeyboardKey.alt] key (or it's left/right + /// equivalents) being down. + static const int _shortcutModifierAlt = 1 << 2; + + /// The bit mask for the [LogicalKeyboardKey.control] key (or it's left/right + /// equivalents) being down. + static const int _shortcutModifierControl = 1 << 3; /// The bit mask for the [LogicalKeyboardKey.meta] key (or it's left/right /// equivalents) being down. @@ -126,14 +148,6 @@ /// equivalents) being down. static const int _shortcutModifierShift = 1 << 1; - /// The bit mask for the [LogicalKeyboardKey.alt] key (or it's left/right - /// equivalents) being down. - static const int _shortcutModifierAlt = 1 << 2; - - /// The bit mask for the [LogicalKeyboardKey.alt] key (or it's left/right - /// equivalents) being down. - static const int _shortcutModifierControl = 1 << 3; - /// Converts the internal representation to the format needed for a /// [PlatformMenuItem] to include it in its serialized form for sending to the /// platform.
diff --git a/packages/flutter/lib/src/widgets/shortcuts.dart b/packages/flutter/lib/src/widgets/shortcuts.dart index 7625284..fec74e8 100644 --- a/packages/flutter/lib/src/widgets/shortcuts.dart +++ b/packages/flutter/lib/src/widgets/shortcuts.dart
@@ -580,25 +580,40 @@ /// See also: /// /// * [SingleActivator], an activator that represents a single key combined -/// with modifiers, such as `Ctrl+C`. +/// with modifiers, such as `Ctrl+C` or `Ctrl-Right Arrow`. class CharacterActivator with Diagnosticable, MenuSerializableShortcut implements ShortcutActivator { /// 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. + /// The [alt], [control], and [meta] flags represent whether the respective + /// modifier keys should be held (true) or released (false). They default to + /// false. [CharacterActivator] cannot check Shift keys, since the shift key + /// affects the resulting character, and will accept whether either of the + /// Shift keys are pressed or not, as long as the key event produces the + /// correct character. /// /// 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. + /// the [character] in combination with the requested modifier keys. If + /// `includeRepeats` is false, only the [character] events with a false + /// [RawKeyDownEvent.repeat] attribute will be considered. const CharacterActivator(this.character, { + this.alt = false, this.control = false, this.meta = false, this.includeRepeats = true, }); + /// Whether either (or both) alt keys should be held for the [character] 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: + /// + /// * [LogicalKeyboardKey.altLeft], [LogicalKeyboardKey.altRight]. + final bool alt; + /// Whether either (or both) control keys should be held for the [character] /// to activate the shortcut. /// @@ -631,7 +646,7 @@ /// attribute will be considered. final bool includeRepeats; - /// The character of the triggering event. + /// The character which triggers the shortcut. /// /// This is typically a single-character string, such as '?' or 'œ', although /// [CharacterActivator] doesn't check the length of [character] or whether it @@ -653,6 +668,7 @@ return event is RawKeyDownEvent && event.character == character && (includeRepeats || !event.repeat) + && (alt == (pressed.contains(LogicalKeyboardKey.altLeft) || pressed.contains(LogicalKeyboardKey.altRight))) && (control == (pressed.contains(LogicalKeyboardKey.controlLeft) || pressed.contains(LogicalKeyboardKey.controlRight))) && (meta == (pressed.contains(LogicalKeyboardKey.metaLeft) || pressed.contains(LogicalKeyboardKey.metaRight))); } @@ -662,6 +678,7 @@ String result = ''; assert(() { final List<String> keys = <String>[ + if (alt) 'Alt', if (control) 'Control', if (meta) 'Meta', "'$character'", @@ -674,7 +691,7 @@ @override ShortcutSerialization serializeForMenu() { - return ShortcutSerialization.character(character); + return ShortcutSerialization.character(character, alt: alt, control: control, meta: meta); } @override
diff --git a/packages/flutter/test/widgets/platform_menu_bar_test.dart b/packages/flutter/test/widgets/platform_menu_bar_test.dart index 429e116..c8209c0 100644 --- a/packages/flutter/test/widgets/platform_menu_bar_test.dart +++ b/packages/flutter/test/widgets/platform_menu_bar_test.dart
@@ -228,6 +228,34 @@ ]); }); }); + + group('ShortcutSerialization', () { + testWidgets('character constructor', (WidgetTester tester) async { + final ShortcutSerialization serialization = ShortcutSerialization.character('?'); + expect(serialization.toChannelRepresentation(), equals(<String, Object?>{ + 'shortcutCharacter': '?', + 'shortcutModifiers': 0, + })); + final ShortcutSerialization serializationWithModifiers = ShortcutSerialization.character('?', alt: true, control: true, meta: true); + expect(serializationWithModifiers.toChannelRepresentation(), equals(<String, Object?>{ + 'shortcutCharacter': '?', + 'shortcutModifiers': 13, + })); + }); + + testWidgets('modifier constructor', (WidgetTester tester) async { + final ShortcutSerialization serialization = ShortcutSerialization.modifier(LogicalKeyboardKey.home); + expect(serialization.toChannelRepresentation(), equals(<String, Object?>{ + 'shortcutTrigger': LogicalKeyboardKey.home.keyId, + 'shortcutModifiers': 0, + })); + final ShortcutSerialization serializationWithModifiers = ShortcutSerialization.modifier(LogicalKeyboardKey.home, alt: true, control: true, meta: true, shift: true); + expect(serializationWithModifiers.toChannelRepresentation(), equals(<String, Object?>{ + 'shortcutTrigger': LogicalKeyboardKey.home.keyId, + 'shortcutModifiers': 15, + })); + }); + }); } const List<String> mainMenu = <String>[
diff --git a/packages/flutter/test/widgets/shortcuts_test.dart b/packages/flutter/test/widgets/shortcuts_test.dart index d0bb609..c566945 100644 --- a/packages/flutter/test/widgets/shortcuts_test.dart +++ b/packages/flutter/test/widgets/shortcuts_test.dart
@@ -1162,10 +1162,10 @@ invoked = 0; }, variant: KeySimulatorTransitModeVariant.all()); - testWidgets('handles Ctrl and Meta', (WidgetTester tester) async { + testWidgets('handles Alt, Ctrl and Meta', (WidgetTester tester) async { int invoked = 0; await tester.pumpWidget(activatorTester( - const CharacterActivator('?', meta: true, control: true), + const CharacterActivator('?', alt: true, meta: true, control: true), (Intent intent) { invoked += 1; }, )); await tester.pump(); @@ -1176,7 +1176,8 @@ await tester.sendKeyUpEvent(LogicalKeyboardKey.slash); expect(invoked, 0); - // Press Ctrl + Meta + Shift + / + // Press Left Alt + Ctrl + Meta + Shift + / + await tester.sendKeyDownEvent(LogicalKeyboardKey.altLeft); await tester.sendKeyDownEvent(LogicalKeyboardKey.metaLeft); await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); expect(invoked, 0); @@ -1185,9 +1186,26 @@ await tester.sendKeyUpEvent(LogicalKeyboardKey.slash); await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft); await tester.sendKeyUpEvent(LogicalKeyboardKey.metaLeft); + await tester.sendKeyUpEvent(LogicalKeyboardKey.altLeft); await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); expect(invoked, 1); invoked = 0; + + // Press Right Alt + Ctrl + Meta + Shift + / + await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftRight); + await tester.sendKeyDownEvent(LogicalKeyboardKey.altRight); + await tester.sendKeyDownEvent(LogicalKeyboardKey.metaRight); + await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight); + expect(invoked, 0); + await tester.sendKeyDownEvent(LogicalKeyboardKey.slash, character: '?'); + expect(invoked, 1); + await tester.sendKeyUpEvent(LogicalKeyboardKey.slash); + await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftRight); + await tester.sendKeyUpEvent(LogicalKeyboardKey.metaRight); + await tester.sendKeyUpEvent(LogicalKeyboardKey.altRight); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight); + expect(invoked, 1); + invoked = 0; }, variant: KeySimulatorTransitModeVariant.all()); testWidgets('isActivatedBy works as expected', (WidgetTester tester) async {