Unverified Commit 1b4800c9 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Add support for Alt to CharacterActivator, add tests (#113466)

parent 4abe6fda
...@@ -45,10 +45,23 @@ class ShortcutSerialization { ...@@ -45,10 +45,23 @@ class ShortcutSerialization {
/// Creates a [ShortcutSerialization] representing a single character. /// Creates a [ShortcutSerialization] representing a single character.
/// ///
/// This is used by a [CharacterActivator] to serialize itself. /// This is used by a [CharacterActivator] to serialize itself.
ShortcutSerialization.character(String character) ShortcutSerialization.character(String character, {
: _internal = <String, Object?>{_kShortcutCharacter: character}, bool alt = false,
bool control = false,
bool meta = false,
}) : assert(character.length == 1),
_character = character, _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 /// Creates a [ShortcutSerialization] representing a specific
/// [LogicalKeyboardKey] and modifiers. /// [LogicalKeyboardKey] and modifiers.
...@@ -56,14 +69,11 @@ class ShortcutSerialization { ...@@ -56,14 +69,11 @@ class ShortcutSerialization {
/// This is used by a [SingleActivator] to serialize itself. /// This is used by a [SingleActivator] to serialize itself.
ShortcutSerialization.modifier( ShortcutSerialization.modifier(
LogicalKeyboardKey trigger, { LogicalKeyboardKey trigger, {
bool control = false,
bool shift = false,
bool alt = false, bool alt = false,
bool control = false,
bool meta = false, bool meta = false,
}) : assert(trigger != LogicalKeyboardKey.shift && bool shift = false,
trigger != LogicalKeyboardKey.shiftLeft && }) : assert(trigger != LogicalKeyboardKey.alt &&
trigger != LogicalKeyboardKey.shiftRight &&
trigger != LogicalKeyboardKey.alt &&
trigger != LogicalKeyboardKey.altLeft && trigger != LogicalKeyboardKey.altLeft &&
trigger != LogicalKeyboardKey.altRight && trigger != LogicalKeyboardKey.altRight &&
trigger != LogicalKeyboardKey.control && trigger != LogicalKeyboardKey.control &&
...@@ -71,52 +81,64 @@ class ShortcutSerialization { ...@@ -71,52 +81,64 @@ class ShortcutSerialization {
trigger != LogicalKeyboardKey.controlRight && trigger != LogicalKeyboardKey.controlRight &&
trigger != LogicalKeyboardKey.meta && trigger != LogicalKeyboardKey.meta &&
trigger != LogicalKeyboardKey.metaLeft && 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. ' 'Specifying a modifier key as a trigger is not allowed. '
'Use provided boolean parameters instead.'), 'Use provided boolean parameters instead.'),
_trigger = trigger, _trigger = trigger,
_control = control, _character = null,
_shift = shift,
_alt = alt, _alt = alt,
_control = control,
_meta = meta, _meta = meta,
_shift = shift,
_internal = <String, Object?>{ _internal = <String, Object?>{
_kShortcutTrigger: trigger.keyId, _kShortcutTrigger: trigger.keyId,
_kShortcutModifiers: (control ? _shortcutModifierControl : 0) | _kShortcutModifiers: (alt ? _shortcutModifierAlt : 0) |
(alt ? _shortcutModifierAlt : 0) | (control ? _shortcutModifierControl : 0) |
(shift ? _shortcutModifierShift : 0) | (meta ? _shortcutModifierMeta : 0) |
(meta ? _shortcutModifierMeta : 0), (shift ? _shortcutModifierShift : 0),
}; };
final Map<String, Object?> _internal; final Map<String, Object?> _internal;
/// The keyboard key that triggers this shortcut, if any. /// The keyboard key that triggers this shortcut, if any.
LogicalKeyboardKey? get trigger => _trigger; LogicalKeyboardKey? get trigger => _trigger;
LogicalKeyboardKey? _trigger; final LogicalKeyboardKey? _trigger;
/// The character that triggers this shortcut, if any. /// The character that triggers this shortcut, if any.
String? get character => _character; String? get character => _character;
String? _character; 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;
final bool? _alt;
/// If this shortcut has a [trigger], this indicates whether or not the /// If this shortcut has a [trigger], this indicates whether or not the
/// control modifier needs to be down or not. /// control modifier needs to be down or not.
bool? get control => _control; bool? get control => _control;
bool? _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;
final bool? _meta;
/// If this shortcut has a [trigger], this indicates whether or not the /// If this shortcut has a [trigger], this indicates whether or not the
/// shift modifier needs to be down or not. /// shift modifier needs to be down or not.
bool? get shift => _shift; bool? get shift => _shift;
bool? _shift; final bool? _shift;
/// If this shortcut has a [trigger], this indicates whether or not the /// The bit mask for the [LogicalKeyboardKey.alt] key (or it's left/right
/// alt modifier needs to be down or not. /// equivalents) being down.
bool? get alt => _alt; static const int _shortcutModifierAlt = 1 << 2;
bool? _alt;
/// If this shortcut has a [trigger], this indicates whether or not the meta /// The bit mask for the [LogicalKeyboardKey.control] key (or it's left/right
/// (also known as the Windows or Command key) modifier needs to be down or /// equivalents) being down.
/// not. static const int _shortcutModifierControl = 1 << 3;
bool? get meta => _meta;
bool? _meta;
/// The bit mask for the [LogicalKeyboardKey.meta] key (or it's left/right /// The bit mask for the [LogicalKeyboardKey.meta] key (or it's left/right
/// equivalents) being down. /// equivalents) being down.
...@@ -126,14 +148,6 @@ class ShortcutSerialization { ...@@ -126,14 +148,6 @@ class ShortcutSerialization {
/// equivalents) being down. /// equivalents) being down.
static const int _shortcutModifierShift = 1 << 1; 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 /// Converts the internal representation to the format needed for a
/// [PlatformMenuItem] to include it in its serialized form for sending to the /// [PlatformMenuItem] to include it in its serialized form for sending to the
/// platform. /// platform.
......
...@@ -580,25 +580,40 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S ...@@ -580,25 +580,40 @@ class SingleActivator with Diagnosticable, MenuSerializableShortcut implements S
/// See also: /// See also:
/// ///
/// * [SingleActivator], an activator that represents a single key combined /// * [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 { class CharacterActivator with Diagnosticable, MenuSerializableShortcut implements ShortcutActivator {
/// Triggered when the key event yields the given character. /// Triggered when the key event yields the given character.
/// ///
/// The [control] and [meta] flags represent whether the respect modifier /// The [alt], [control], and [meta] flags represent whether the respective
/// keys should be held (true) or released (false). They default to false. /// modifier keys should be held (true) or released (false). They default to
/// [CharacterActivator] can not check Shift keys or Alt keys yet, and will /// false. [CharacterActivator] cannot check Shift keys, since the shift key
/// accept whether they are pressed or not. /// 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 /// By default, the activator is checked on all [RawKeyDownEvent] events for
/// the [character]. If `includeRepeats` is false, only the [character] /// the [character] in combination with the requested modifier keys. If
/// events with a false [RawKeyDownEvent.repeat] attribute will be /// `includeRepeats` is false, only the [character] events with a false
/// considered. /// [RawKeyDownEvent.repeat] attribute will be considered.
const CharacterActivator(this.character, { const CharacterActivator(this.character, {
this.alt = false,
this.control = false, this.control = false,
this.meta = false, this.meta = false,
this.includeRepeats = true, 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] /// Whether either (or both) control keys should be held for the [character]
/// to activate the shortcut. /// to activate the shortcut.
/// ///
...@@ -631,7 +646,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement ...@@ -631,7 +646,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement
/// attribute will be considered. /// attribute will be considered.
final bool includeRepeats; 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 /// This is typically a single-character string, such as '?' or 'œ', although
/// [CharacterActivator] doesn't check the length of [character] or whether it /// [CharacterActivator] doesn't check the length of [character] or whether it
...@@ -653,6 +668,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement ...@@ -653,6 +668,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement
return event is RawKeyDownEvent return event is RawKeyDownEvent
&& event.character == character && event.character == character
&& (includeRepeats || !event.repeat) && (includeRepeats || !event.repeat)
&& (alt == (pressed.contains(LogicalKeyboardKey.altLeft) || pressed.contains(LogicalKeyboardKey.altRight)))
&& (control == (pressed.contains(LogicalKeyboardKey.controlLeft) || pressed.contains(LogicalKeyboardKey.controlRight))) && (control == (pressed.contains(LogicalKeyboardKey.controlLeft) || pressed.contains(LogicalKeyboardKey.controlRight)))
&& (meta == (pressed.contains(LogicalKeyboardKey.metaLeft) || pressed.contains(LogicalKeyboardKey.metaRight))); && (meta == (pressed.contains(LogicalKeyboardKey.metaLeft) || pressed.contains(LogicalKeyboardKey.metaRight)));
} }
...@@ -662,6 +678,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement ...@@ -662,6 +678,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement
String result = ''; String result = '';
assert(() { assert(() {
final List<String> keys = <String>[ final List<String> keys = <String>[
if (alt) 'Alt',
if (control) 'Control', if (control) 'Control',
if (meta) 'Meta', if (meta) 'Meta',
"'$character'", "'$character'",
...@@ -674,7 +691,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement ...@@ -674,7 +691,7 @@ class CharacterActivator with Diagnosticable, MenuSerializableShortcut implement
@override @override
ShortcutSerialization serializeForMenu() { ShortcutSerialization serializeForMenu() {
return ShortcutSerialization.character(character); return ShortcutSerialization.character(character, alt: alt, control: control, meta: meta);
} }
@override @override
......
...@@ -228,6 +228,34 @@ void main() { ...@@ -228,6 +228,34 @@ void main() {
]); ]);
}); });
}); });
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>[ const List<String> mainMenu = <String>[
......
...@@ -1162,10 +1162,10 @@ void main() { ...@@ -1162,10 +1162,10 @@ void main() {
invoked = 0; invoked = 0;
}, variant: KeySimulatorTransitModeVariant.all()); }, variant: KeySimulatorTransitModeVariant.all());
testWidgets('handles Ctrl and Meta', (WidgetTester tester) async { testWidgets('handles Alt, Ctrl and Meta', (WidgetTester tester) async {
int invoked = 0; int invoked = 0;
await tester.pumpWidget(activatorTester( await tester.pumpWidget(activatorTester(
const CharacterActivator('?', meta: true, control: true), const CharacterActivator('?', alt: true, meta: true, control: true),
(Intent intent) { invoked += 1; }, (Intent intent) { invoked += 1; },
)); ));
await tester.pump(); await tester.pump();
...@@ -1176,7 +1176,8 @@ void main() { ...@@ -1176,7 +1176,8 @@ void main() {
await tester.sendKeyUpEvent(LogicalKeyboardKey.slash); await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
expect(invoked, 0); 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.metaLeft);
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
expect(invoked, 0); expect(invoked, 0);
...@@ -1185,9 +1186,26 @@ void main() { ...@@ -1185,9 +1186,26 @@ void main() {
await tester.sendKeyUpEvent(LogicalKeyboardKey.slash); await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft); await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
await tester.sendKeyUpEvent(LogicalKeyboardKey.metaLeft); await tester.sendKeyUpEvent(LogicalKeyboardKey.metaLeft);
await tester.sendKeyUpEvent(LogicalKeyboardKey.altLeft);
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
expect(invoked, 1); expect(invoked, 1);
invoked = 0; 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()); }, variant: KeySimulatorTransitModeVariant.all());
testWidgets('isActivatedBy works as expected', (WidgetTester tester) async { testWidgets('isActivatedBy works as expected', (WidgetTester tester) async {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment