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:
parent
a5a5595c95
commit
a70b020e18
@ -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 doesn’t 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
|
||||
|
@ -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
|
||||
|
6
dev/tools/gen_keycodes/data/synonyms.json
Normal file
6
dev/tools/gen_keycodes/data/synonyms.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"shift": ["ShiftLeft", "ShiftRight"],
|
||||
"meta": ["MetaLeft", "MetaRight"],
|
||||
"alt": ["AltLeft", "AltRight"],
|
||||
"control": ["ControlLeft", "ControlRight"]
|
||||
}
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user