Add pseudo-key synonyms for keys like shift, meta, alt, and control. (#33695)

This adds a list of key synonyms for non-printable keyboard keys that appear in more than one place So keys like LogicalKeyboardKey.shiftLeft and LogicalKeyboardKey.shiftRight now can be mapped to just LogicalKeyboardKey.shift.

I also fixed a bug in the gen_keycodes tool where GLFW entries would get removed if they weren't parsed from the source on the web.
This commit is contained in:
Greg Spencer 2019-06-03 18:41:04 -07:00 committed by GitHub
parent a5a5595c95
commit a70b020e18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 207 additions and 33 deletions

View File

@ -27,6 +27,9 @@ used to generate the source files.
generated data will be inserted.
- `data/printable.json`: contains a mapping between Flutter key name and its
printable character. This character is used as the key label.
- `data/synonyms.json`: contains a mapping between pseudo-keys that represent
other keys, and the sets of keys they represent. For example, this contains
the "shift" key that represents either a "shiftLeft" or "shiftRight" key.
## Running the tool
@ -134,6 +137,13 @@ define. It has values in the following ranges.
that their version of Flutter doesnt support yet. The prefix for this code
is the platform prefix from the previous sections, plus 0x100.
- **0x200 0000 0000 - 0x2FF FFFF FFFF**: For pseudo-keys which represent
combinations of other keys, and conceptual keys which don't have a physical
representation. This is where things like key synonyms are defined (e.g.
"shiftLeft" is a synonym for "shift": the "shift" key is a pseudo-key
representing either the left or right shift key).
**This is intended to get us out of the business of defining key codes where
possible.** We still have to have mapping tables, but at least the actual minting
of codes is deferred to other organizations to a large extent. Coming up with a

View File

@ -166,14 +166,6 @@ class LogicalKeyboardKey extends Diagnosticable {
/// null, if not found.
static LogicalKeyboardKey findKeyByKeyId(int keyId) => _knownLogicalKeys[keyId];
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(StringProperty('keyId', '0x${keyId.toRadixString(16).padLeft(8, '0')}', showName: true));
properties.add(StringProperty('keyLabel', keyLabel, showName: true));
properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
}
/// Returns true if the given label represents a Unicode control character.
///
/// Examples of control characters are characters like "U+000A LINE FEED (LF)"
@ -215,10 +207,35 @@ class LogicalKeyboardKey extends Diagnosticable {
/// platforms that had a "do what I mean" key from then on.
bool get isAutogenerated => (keyId & autogeneratedMask) != 0;
/// Returns a set of pseudo-key synonyms for the given `key`.
///
/// This allows finding the pseudo-keys that also represents a concrete
/// `key` so that a class with a key map can match pseudo-keys as well as the
/// actual generated keys.
///
/// The pseudo-keys returned in the set are typically used to represent keys
/// which appear in multiple places on the keyboard, such as the [shift],
/// [alt], [control], and [meta] keys. The keys in the returned set won't ever
/// be generated directly, but if a more specific key event is received, then
/// this set can be used to find the more general pseudo-key. For example, if
/// this is a [shiftLeft] key, this accessor will return the set
/// `<LogicalKeyboardKey>{ shift }`.
Set<LogicalKeyboardKey> get synonyms {
final LogicalKeyboardKey result = _synonyms[this];
return result == null ? <LogicalKeyboardKey>{} : <LogicalKeyboardKey>{result};
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(StringProperty('keyId', '0x${keyId.toRadixString(16).padLeft(8, '0')}', showName: true));
properties.add(StringProperty('keyLabel', keyLabel, showName: true));
properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
}
/// Mask for the 32-bit value portion of the key code.
///
/// This is used by
/// platform-specific code to generate Flutter key codes.
/// This is used by platform-specific code to generate Flutter key codes.
static const int valueMask = 0x000FFFFFFFF;
/// Mask for the platform prefix portion of the key code.
@ -248,6 +265,10 @@ class LogicalKeyboardKey extends Diagnosticable {
static const Map<int, LogicalKeyboardKey> _knownLogicalKeys = <int, LogicalKeyboardKey>{
@@@LOGICAL_KEY_MAP@@@
};
// A map of keys to the pseudo-key synonym for that key. Used by getSynonyms.
static final Map<LogicalKeyboardKey, LogicalKeyboardKey> _synonyms = <LogicalKeyboardKey, LogicalKeyboardKey>{
@@@LOGICAL_KEY_SYNONYMS@@@ };
}
/// A class with static values that describe the keys that are returned from

View File

@ -0,0 +1,6 @@
{
"shift": ["ShiftLeft", "ShiftRight"],
"meta": ["MetaLeft", "MetaRight"],
"alt": ["AltLeft", "AltRight"],
"control": ["ControlLeft", "ControlRight"]
}

View File

@ -55,26 +55,59 @@ $otherComments static const PhysicalKeyboardKey ${entry.constantName} = Physica
String get logicalDefinitions {
String escapeLabel(String label) => label.contains("'") ? 'r"$label"' : "r'$label'";
final StringBuffer definitions = StringBuffer();
for (Key entry in keyData.data) {
final String firstComment = wrapString('Represents the logical "${entry.commentName}" key on the keyboard.');
final String otherComments = wrapString('See the function [RawKeyEvent.logicalKey] for more information.');
if (entry.keyLabel == null) {
void printKey(int flutterId, String keyLabel, String constantName, String commentName, {String otherComments}) {
final String firstComment = wrapString('Represents the logical "$commentName" key on the keyboard.');
otherComments ??= wrapString('See the function [RawKeyEvent.logicalKey] for more information.');
if (keyLabel == null) {
definitions.write('''
$firstComment ///
$otherComments static const LogicalKeyboardKey ${entry.constantName} = LogicalKeyboardKey(${toHex(entry.flutterId, digits: 11)}, debugName: kReleaseMode ? null : '${entry.commentName}');
$otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardKey(${toHex(flutterId, digits: 11)}, debugName: kReleaseMode ? null : '$commentName');
''');
} else {
definitions.write('''
$firstComment ///
$otherComments static const LogicalKeyboardKey ${entry.constantName} = LogicalKeyboardKey(${toHex(entry.flutterId, digits: 11)}, keyLabel: ${escapeLabel(entry.keyLabel)}, debugName: kReleaseMode ? null : '${entry.commentName}');
$otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardKey(${toHex(flutterId, digits: 11)}, keyLabel: ${escapeLabel(keyLabel)}, debugName: kReleaseMode ? null : '$commentName');
''');
}
}
for (Key entry in keyData.data) {
printKey(
entry.flutterId,
entry.keyLabel,
entry.constantName,
entry.commentName,
);
}
for (String name in Key.synonyms.keys) {
// Use the first item in the synonyms as a template for the ID to use.
// It won't end up being the same value because it'll be in the pseudo-key
// plane.
final Key entry = keyData.data.firstWhere((Key item) => item.name == Key.synonyms[name][0]);
final Set<String> unionNames = Key.synonyms[name].map<String>((dynamic name) {
return upperCamelToLowerCamel(name);
}).toSet();
printKey(Key.synonymPlane | entry.flutterId, entry.keyLabel, name, Key.getCommentName(name),
otherComments: wrapString('This key represents the union of the keys '
'$unionNames when comparing keys. This key will never be generated '
'directly, its main use is in defining key maps.'));
}
return definitions.toString();
}
String get logicalSynonyms {
final StringBuffer synonyms = StringBuffer();
for (String name in Key.synonyms.keys) {
for (String synonym in Key.synonyms[name]) {
final String keyName = upperCamelToLowerCamel(synonym);
synonyms.writeln(' $keyName: $name,');
}
}
return synonyms.toString();
}
/// This generates the map of USB HID codes to physical keys.
String get predefinedHidCodeMap {
final StringBuffer scanCodeMap = StringBuffer();
@ -90,6 +123,16 @@ $otherComments static const LogicalKeyboardKey ${entry.constantName} = LogicalK
for (Key entry in keyData.data) {
keyCodeMap.writeln(' ${toHex(entry.flutterId, digits: 10)}: ${entry.constantName},');
}
for (String entry in Key.synonyms.keys) {
// Use the first item in the synonyms as a template for the ID to use.
// It won't end up being the same value because it'll be in the pseudo-key
// plane.
final Key primaryKey = keyData.data.firstWhere((Key item) {
return item.name == Key.synonyms[entry][0];
}, orElse: () => null);
assert(primaryKey != null);
keyCodeMap.writeln(' ${toHex(Key.synonymPlane | primaryKey.flutterId, digits: 10)}: $entry,');
}
return keyCodeMap.toString().trimRight();
}
@ -229,6 +272,7 @@ $otherComments static const LogicalKeyboardKey ${entry.constantName} = LogicalK
'PHYSICAL_KEY_MAP': predefinedHidCodeMap,
'LOGICAL_KEY_MAP': predefinedKeyCodeMap,
'LOGICAL_KEY_DEFINITIONS': logicalDefinitions,
'LOGICAL_KEY_SYNONYMS': logicalSynonyms,
'PHYSICAL_KEY_DEFINITIONS': physicalDefinitions,
};

View File

@ -293,6 +293,8 @@ class Key {
xKbScanCode: map['scanCodes']['xkb'],
windowsScanCode: map['scanCodes']['windows'],
macOsScanCode: map['scanCodes']['macos'],
glfwKeyNames: map['names']['glfw']?.cast<String>(),
glfwKeyCodes: map['keyCodes']['glfw']?.cast<int>(),
);
}
@ -371,16 +373,18 @@ class Key {
return hidPlane | (usbHidCode & valueMask);
}
/// Gets the name of the key suitable for placing in comments.
///
/// Takes the [constantName] and converts it from lower camel case to capitalized
/// separate words (e.g. "wakeUp" converts to "Wake Up").
String get commentName {
static String getCommentName(String constantName) {
String upperCamel = lowerCamelToUpperCamel(constantName);
upperCamel = upperCamel.replaceAllMapped(RegExp(r'(Digit|Numpad|Lang)([0-9]+)'), (Match match) => '${match.group(1)} ${match.group(2)}');
return upperCamel.replaceAllMapped(RegExp(r'([A-Z])'), (Match match) => ' ${match.group(1)}').trim();
}
/// Gets the name of the key suitable for placing in comments.
///
/// Takes the [constantName] and converts it from lower camel case to capitalized
/// separate words (e.g. "wakeUp" converts to "Wake Up").
String get commentName => getCommentName(constantName);
/// Gets the named used for the key constant in the definitions in
/// keyboard_keys.dart.
///
@ -428,6 +432,21 @@ class Key {
}
static Map<String, String> _printable;
/// Returns the static map of synonym representations.
///
/// These include synonyms for keys which don't have printable
/// representations, and appear in more than one place on the keyboard (e.g.
/// SHIFT, ALT, etc.).
static Map<String, List<dynamic>> get synonyms {
if (_synonym == null) {
final String synonymKeys = File(path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'synonyms.json',)).readAsStringSync();
final Map<String, dynamic> synonym = json.decode(synonymKeys);
_synonym = synonym.cast<String, List<dynamic>>();
}
return _synonym;
}
static Map<String, List<dynamic>> _synonym;
/// Mask for the 32-bit value portion of the code.
static const int valueMask = 0x000FFFFFFFF;
@ -437,4 +456,7 @@ class Key {
/// The code prefix for keys which do not have a Unicode representation, but
/// do have a USB HID ID.
static const int hidPlane = 0x00100000000;
/// The code prefix for pseudo-keys which represent collections of key synonyms.
static const int synonymPlane = 0x20000000000;
}

View File

@ -166,14 +166,6 @@ class LogicalKeyboardKey extends Diagnosticable {
/// null, if not found.
static LogicalKeyboardKey findKeyByKeyId(int keyId) => _knownLogicalKeys[keyId];
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(StringProperty('keyId', '0x${keyId.toRadixString(16).padLeft(8, '0')}', showName: true));
properties.add(StringProperty('keyLabel', keyLabel, showName: true));
properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
}
/// Returns true if the given label represents a Unicode control character.
///
/// Examples of control characters are characters like "U+000A LINE FEED (LF)"
@ -215,10 +207,35 @@ class LogicalKeyboardKey extends Diagnosticable {
/// platforms that had a "do what I mean" key from then on.
bool get isAutogenerated => (keyId & autogeneratedMask) != 0;
/// Returns a set of pseudo-key synonyms for the given `key`.
///
/// This allows finding the pseudo-keys that also represents a concrete
/// `key` so that a class with a key map can match pseudo-keys as well as the
/// actual generated keys.
///
/// The pseudo-keys returned in the set are typically used to represent keys
/// which appear in multiple places on the keyboard, such as the [shift],
/// [alt], [control], and [meta] keys. The keys in the returned set won't ever
/// be generated directly, but if a more specific key event is received, then
/// this set can be used to find the more general pseudo-key. For example, if
/// this is a [shiftLeft] key, this accessor will return the set
/// `<LogicalKeyboardKey>{ shift }`.
Set<LogicalKeyboardKey> get synonyms {
final LogicalKeyboardKey result = _synonyms[this];
return result == null ? <LogicalKeyboardKey>{} : <LogicalKeyboardKey>{result};
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(StringProperty('keyId', '0x${keyId.toRadixString(16).padLeft(8, '0')}', showName: true));
properties.add(StringProperty('keyLabel', keyLabel, showName: true));
properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
}
/// Mask for the 32-bit value portion of the key code.
///
/// This is used by
/// platform-specific code to generate Flutter key codes.
/// This is used by platform-specific code to generate Flutter key codes.
static const int valueMask = 0x000FFFFFFFF;
/// Mask for the platform prefix portion of the key code.
@ -1413,6 +1430,34 @@ class LogicalKeyboardKey extends Diagnosticable {
/// See the function [RawKeyEvent.logicalKey] for more information.
static const LogicalKeyboardKey showAllWindows = LogicalKeyboardKey(0x001000c029f, debugName: kReleaseMode ? null : 'Show All Windows');
/// Represents the logical "Shift" key on the keyboard.
///
/// This key represents the union of the keys {shiftLeft, shiftRight} when
/// comparing keys. This key will never be generated directly, its main use is
/// in defining key maps.
static const LogicalKeyboardKey shift = LogicalKeyboardKey(0x201000700e1, debugName: kReleaseMode ? null : 'Shift');
/// Represents the logical "Meta" key on the keyboard.
///
/// This key represents the union of the keys {metaLeft, metaRight} when
/// comparing keys. This key will never be generated directly, its main use is
/// in defining key maps.
static const LogicalKeyboardKey meta = LogicalKeyboardKey(0x201000700e3, debugName: kReleaseMode ? null : 'Meta');
/// Represents the logical "Alt" key on the keyboard.
///
/// This key represents the union of the keys {altLeft, altRight} when
/// comparing keys. This key will never be generated directly, its main use is
/// in defining key maps.
static const LogicalKeyboardKey alt = LogicalKeyboardKey(0x201000700e2, debugName: kReleaseMode ? null : 'Alt');
/// Represents the logical "Control" key on the keyboard.
///
/// This key represents the union of the keys {controlLeft, controlRight} when
/// comparing keys. This key will never be generated directly, its main use is
/// in defining key maps.
static const LogicalKeyboardKey control = LogicalKeyboardKey(0x201000700e0, debugName: kReleaseMode ? null : 'Control');
// A list of all predefined constant LogicalKeyboardKeys so they can be
// searched.
static const Map<int, LogicalKeyboardKey> _knownLogicalKeys = <int, LogicalKeyboardKey>{
@ -1650,6 +1695,22 @@ class LogicalKeyboardKey extends Diagnosticable {
0x01000c028c: mailSend,
0x01000c029d: keyboardLayoutSelect,
0x01000c029f: showAllWindows,
0x201000700e1: shift,
0x201000700e3: meta,
0x201000700e2: alt,
0x201000700e0: control,
};
// A map of keys to the pseudo-key synonym for that key. Used by getSynonyms.
static final Map<LogicalKeyboardKey, LogicalKeyboardKey> _synonyms = <LogicalKeyboardKey, LogicalKeyboardKey>{
shiftLeft: shift,
shiftRight: shift,
metaLeft: meta,
metaRight: meta,
altLeft: alt,
altRight: alt,
controlLeft: control,
controlRight: control,
};
}

View File

@ -50,5 +50,15 @@ void main() {
expect(key1, equals(key1));
expect(key1, equals(key2));
});
test('Basic synonyms can be looked up.', () async {
expect(LogicalKeyboardKey.shiftLeft.synonyms.first, equals(LogicalKeyboardKey.shift));
expect(LogicalKeyboardKey.controlLeft.synonyms.first, equals(LogicalKeyboardKey.control));
expect(LogicalKeyboardKey.altLeft.synonyms.first, equals(LogicalKeyboardKey.alt));
expect(LogicalKeyboardKey.metaLeft.synonyms.first, equals(LogicalKeyboardKey.meta));
expect(LogicalKeyboardKey.shiftRight.synonyms.first, equals(LogicalKeyboardKey.shift));
expect(LogicalKeyboardKey.controlRight.synonyms.first, equals(LogicalKeyboardKey.control));
expect(LogicalKeyboardKey.altRight.synonyms.first, equals(LogicalKeyboardKey.alt));
expect(LogicalKeyboardKey.metaRight.synonyms.first, equals(LogicalKeyboardKey.meta));
});
});
}