Make LogicalKeyboardKey.keyLabel a getter and better (#79100)
diff --git a/dev/tools/gen_keycodes/data/keyboard_key.tmpl b/dev/tools/gen_keycodes/data/keyboard_key.tmpl
index b4fa838..6baa963 100644
--- a/dev/tools/gen_keycodes/data/keyboard_key.tmpl
+++ b/dev/tools/gen_keycodes/data/keyboard_key.tmpl
@@ -76,9 +76,9 @@
/// _message = 'Pressed the "Q" key!';
/// } else {
/// if (kReleaseMode) {
-/// _message = 'Not a Q: Key label is "${event.logicalKey.keyLabel}"';
+/// _message = 'Not a Q: Pressed 0x${event.logicalKey.keyId.toRadixString(16)}';
/// } else {
-/// // This will only print useful information in debug mode.
+/// // The debugName will only print useful information in debug mode.
/// _message = 'Not a Q: Pressed ${event.logicalKey.debugName}';
/// }
/// }
@@ -124,21 +124,18 @@
/// keyboard events.
@immutable
class LogicalKeyboardKey extends KeyboardKey {
- /// Creates a LogicalKeyboardKey object with an optional key label and debug
- /// name.
+ /// A LogicalKeyboardKey object for a key ID.
///
- /// [keyId] must not be null.
- ///
- /// {@tool snippet}
- /// To save executable size, it is recommended that the [debugName] be null in
- /// release mode. You can do this by using the [kReleaseMode] constant.
- ///
- /// ```dart
- /// const LogicalKeyboardKey(0x0010000000a, debugName: kReleaseMode ? null : 'Special Key')
- /// ```
- /// {@end-tool}
- const LogicalKeyboardKey(this.keyId, {this.debugName, this.keyLabel = ''})
- : assert(keyId != null);
+ /// This factory constructor ensures that the same `keyId` always results
+ /// in the identical instance. It looks up an object from the predefined values
+ /// if possible, or construct a new one and cache it.
+ factory LogicalKeyboardKey(int keyId) {
+ return findKeyByKeyId(keyId) ??
+ _additionalKeyPool.putIfAbsent(keyId, () => LogicalKeyboardKey._(keyId));
+ }
+
+ /// Creates a LogicalKeyboardKey object for a key ID.
+ const LogicalKeyboardKey._(this.keyId);
/// A unique code representing this key.
///
@@ -146,37 +143,95 @@
/// from it, as the representation of the code could change at any time.
final int keyId;
- /// The debug string to print for this keyboard key, which will be null in
- /// release mode.
- final String? debugName;
+ // Returns the bits that are not included in [valueMask], shifted to the
+ // right.
+ //
+ // For example, if the input is 0x12abcdabcd, then the result is 0x12.
+ //
+ // This is mostly equivalent to a right shift, resolving the problem that
+ // JavaScript only support 32-bit bitwise operation and needs to use division
+ // instead.
+ static int _nonValueBits(int n) {
+ // `n >> valueMaskWidth` is equivalent to `n / divisorForValueMask`.
+ const int divisorForValueMask = valueMask + 1;
+ const int valueMaskWidth = 32;
- /// The Unicode string representing the character produced by a [RawKeyEvent].
+ // Equivalent to assert(divisorForValueMask == (1 << valueMaskWidth)).
+ const int _firstDivisorWidth = 28;
+ assert(divisorForValueMask ==
+ (1 << _firstDivisorWidth) * (1 << (valueMaskWidth - _firstDivisorWidth)));
+
+ // JS only supports up to 2^53 - 1, therefore non-value bits can only
+ // contain (maxSafeIntegerWidth - valueMaskWidth) bits.
+ const int maxSafeIntegerWidth = 52;
+ const int nonValueMask = (1 << (maxSafeIntegerWidth - valueMaskWidth)) - 1;
+
+ if (kIsWeb) {
+ return (n / divisorForValueMask).floor() & nonValueMask;
+ } else {
+ return (n >> valueMaskWidth) & nonValueMask;
+ }
+ }
+
+ static String? _unicodeKeyLabel(int keyId) {
+ if (_nonValueBits(keyId) == 0) {
+ return String.fromCharCode(keyId).toUpperCase();
+ }
+ return null;
+ }
+
+ /// A description representing the character produced by a [RawKeyEvent].
///
- /// This value is useful for describing or matching mnemonic keyboard
- /// shortcuts.
+ /// This value is useful for providing readable strings for keys or keyboard
+ /// shortcuts. Do not use this value to compare equality of keys; compare
+ /// [keyId] instead.
///
- /// This value is an empty string if there's no key label data for a key.
+ /// For printable keys, this is usually the printable character in upper case
+ /// ignoring modifiers or combining keys, such as 'A', '1', or '/'. This
+ /// might also return accented letters (such as 'Ù') for keys labeled as so,
+ /// but not if such character is a result from preceding combining keys ('`̀'
+ /// followed by key U).
///
- /// On most platforms this is a single code point, but it could contain any
- /// Unicode string. The `keyLabel` differs from [RawKeyEvent.character]
- /// because `keyLabel` only takes into account the key being pressed, not any
- /// combining keys pressed before it, so, for example, an “o” that follows a
- /// combining dieresis (“¨”, COMBINING DIAERESIS (U+0308)) would just return
- /// “o” for [keyLabel], but would return “ö” for [RawKeyEvent.character].
+ /// For other keys, [keyLabel] looks up the full key name from a predefined
+ /// map, such as 'F1', 'Shift Left', or 'Media Down'. This value is an empty
+ /// string if there's no key label data for a key.
+ ///
+ /// For the printable representation that takes into consideration the
+ /// modifiers and combining keys, see [RawKeyEvent.character].
///
/// {@macro flutter.services.RawKeyEventData.keyLabel}
- final String keyLabel;
+ String get keyLabel {
+ return _unicodeKeyLabel(keyId)
+ ?? _keyLabels[keyId]
+ ?? '';
+ }
- @override
- int get hashCode => keyId.hashCode;
-
- @override
- bool operator ==(Object other) {
- if (other.runtimeType != runtimeType) {
- return false;
- }
- return other is LogicalKeyboardKey
- && other.keyId == keyId;
+ /// The debug string to print for this keyboard key, which will be null in
+ /// release mode.
+ ///
+ /// For printable keys, this is usually a more descriptive name related to
+ /// [keyLabel], such as 'Key A', 'Digit 1', 'Backslash'. This might
+ /// also return accented letters (such as 'Key Ù') for keys labeled as so.
+ ///
+ /// For other keys, this looks up the full key name from a predefined map (the
+ /// same value as [keyLabel]), such as 'F1', 'Shift Left', or 'Media Down'. If
+ /// there's no key label data for a key, this returns a name that explains the
+ /// ID (such as 'Key with ID 0x00100012345').
+ String? get debugName {
+ String? result;
+ assert(() {
+ result = _keyLabels[keyId];
+ if (result == null) {
+ final String? unicodeKeyLabel = _unicodeKeyLabel(keyId);
+ if (unicodeKeyLabel != null) {
+ result = 'Key $unicodeKeyLabel';
+ } else {
+ result = 'Key with ID 0x${keyId.toRadixString(16).padLeft(11, '0')}';
+ }
+ }
+ return true;
+ }());
+ return result;
}
/// Returns the [LogicalKeyboardKey] constant that matches the given ID, or
@@ -261,10 +316,11 @@
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));
}
+ static final Map<int, LogicalKeyboardKey> _additionalKeyPool = <int, LogicalKeyboardKey>{};
+
/// Mask for the 32-bit value portion of the key code.
///
/// This is used by platform-specific code to generate Flutter key codes.
@@ -313,6 +369,9 @@
// 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@@@ };
+
+ static const Map<int, String> _keyLabels = <int, String>{
+@@@LOGICAL_KEY_KEY_LABELS@@@ };
}
/// A class with static values that describe the keys that are returned from
@@ -387,7 +446,7 @@
/// onTap: () {
/// FocusScope.of(context).requestFocus(_focusNode);
/// },
-/// child: Text('Tap to focus'),
+/// child: const Text('Tap to focus'),
/// );
/// }
/// return Text(_message ?? 'Press a key');
@@ -408,20 +467,18 @@
/// keyboard events.
@immutable
class PhysicalKeyboardKey extends KeyboardKey {
- /// Creates a PhysicalKeyboardKey object with an optional debug name.
+ /// A PhysicalKeyboardKey object for a USB HID usage.
///
- /// The [usbHidUsage] must not be null.
- ///
- /// {@tool snippet}
- /// To save executable size, it is recommended that the [debugName] be null in
- /// release mode. You can do this using the [kReleaseMode] constant.
- ///
- /// ```dart
- /// const PhysicalKeyboardKey(0x0000ffff, debugName: kReleaseMode ? null : 'Special Key')
- /// ```
- /// {@end-tool}
- const PhysicalKeyboardKey(this.usbHidUsage, {this.debugName})
- : assert(usbHidUsage != null);
+ /// This factory constructor ensures that the same `usbHidUsage` always results
+ /// in the identical instance. It looks up an object from the predefined values
+ /// if possible, or construct a new one and cache it.
+ factory PhysicalKeyboardKey(int usbHidUsage) {
+ return findKeyByCode(usbHidUsage) ??
+ _additionalKeyPool.putIfAbsent(usbHidUsage, () => PhysicalKeyboardKey._(usbHidUsage));
+ }
+
+ /// Creates a PhysicalKeyboardKey object for a USB HID usage.
+ const PhysicalKeyboardKey._(this.usbHidUsage);
/// The unique USB HID usage ID of this physical key on the keyboard.
///
@@ -435,31 +492,29 @@
/// The debug string to print for this keyboard key, which will be null in
/// release mode.
- final String? debugName;
+ String? get debugName {
+ String? result;
+ assert(() {
+ result = _debugNames[usbHidUsage] ??
+ 'Key with ID 0x${usbHidUsage.toRadixString(16).padLeft(8, '0')}';
+ return true;
+ }());
+ return result;
+ }
/// Finds a known [PhysicalKeyboardKey] that matches the given USB HID usage
/// code.
static PhysicalKeyboardKey? findKeyByCode(int usageCode) => _knownPhysicalKeys[usageCode];
@override
- int get hashCode => usbHidUsage.hashCode;
-
- @override
- bool operator ==(Object other) {
- if (other.runtimeType != runtimeType) {
- return false;
- }
- return other is PhysicalKeyboardKey
- && other.usbHidUsage == usbHidUsage;
- }
-
- @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(StringProperty('usbHidUsage', '0x${usbHidUsage.toRadixString(16).padLeft(8, '0')}', showName: true));
properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
}
+ static final Map<int, PhysicalKeyboardKey> _additionalKeyPool = <int, PhysicalKeyboardKey>{};
+
// Key constants for all keyboard keys in the USB HID specification at the
// time Flutter was built.
@@@PHYSICAL_KEY_DEFINITIONS@@@
@@ -468,4 +523,9 @@
static const Map<int, PhysicalKeyboardKey> _knownPhysicalKeys = <int, PhysicalKeyboardKey>{
@@@PHYSICAL_KEY_MAP@@@
};
+
+ static const Map<int, String> _debugNames = kReleaseMode ?
+ <int, String>{} :
+ <int, String>{
+@@@PHYSICAL_KEY_DEBUG_NAMES@@@ };
}