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@@@    };
 }
diff --git a/dev/tools/gen_keycodes/lib/keyboard_keys_code_gen.dart b/dev/tools/gen_keycodes/lib/keyboard_keys_code_gen.dart
index 3e82d3e..f4decd9 100644
--- a/dev/tools/gen_keycodes/lib/keyboard_keys_code_gen.dart
+++ b/dev/tools/gen_keycodes/lib/keyboard_keys_code_gen.dart
@@ -45,38 +45,38 @@
       definitions.write('''
 
 $firstComment  ///
-$otherComments  static const PhysicalKeyboardKey ${entry.constantName} = PhysicalKeyboardKey(${toHex(entry.usbHidCode, digits: 8)}, debugName: kReleaseMode ? null : '${entry.commentName}');
+$otherComments  static const PhysicalKeyboardKey ${entry.constantName} = PhysicalKeyboardKey._(${toHex(entry.usbHidCode, digits: 8)});
 ''');
     }
     return definitions.toString();
   }
 
+  String get _physicalDebugNames {
+    final StringBuffer result = StringBuffer();
+    for (final Key entry in keyData.data) {
+      result.write('''
+      ${toHex(entry.usbHidCode, digits: 8)}: '${entry.commentName}',
+''');
+    }
+    return result.toString();
+  }
+
   /// Gets the generated definitions of LogicalKeyboardKeys.
   String get _logicalDefinitions {
-    String escapeLabel(String label) => label.contains("'") ? 'r"$label"' : "r'$label'";
     final StringBuffer definitions = StringBuffer();
-    void printKey(int flutterId, String keyLabel, String constantName, String commentName, {String otherComments}) {
+    void printKey(int flutterId, 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('''
+      definitions.write('''
 
 $firstComment  ///
-$otherComments  static const LogicalKeyboardKey $constantName = LogicalKeyboardKey(${toHex(flutterId, digits: 11)}, debugName: kReleaseMode ? null : '$commentName');
+$otherComments  static const LogicalKeyboardKey $constantName = LogicalKeyboardKey._(${toHex(flutterId, digits: 11)});
 ''');
-      } else {
-        definitions.write('''
-
-$firstComment  ///
-$otherComments  static const LogicalKeyboardKey $constantName = LogicalKeyboardKey(${toHex(flutterId, digits: 11)}, keyLabel: ${escapeLabel(keyLabel)}, debugName: kReleaseMode ? null : '$commentName');
-''');
-      }
     }
 
     for (final Key entry in keyData.data) {
       printKey(
         entry.flutterId,
-        entry.keyLabel,
         entry.constantName,
         entry.commentName,
       );
@@ -89,7 +89,7 @@
       final Set<String> unionNames = Key.synonyms[name].map<String>((dynamic name) {
         return upperCamelToLowerCamel(name as String);
       }).toSet();
-      printKey(Key.synonymPlane | entry.flutterId, entry.keyLabel, name, Key.getCommentName(name),
+      printKey(Key.synonymPlane | entry.flutterId, 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.'));
@@ -108,6 +108,25 @@
     return synonyms.toString();
   }
 
+  String get _logicalKeyLabels {
+    final StringBuffer result = StringBuffer();
+    for (final Key entry in keyData.data) {
+      result.write('''
+    ${toHex(entry.flutterId, digits: 11)}: '${entry.commentName}',
+''');
+    }
+    for (final 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]);
+      result.write('''
+    ${toHex(Key.synonymPlane | entry.flutterId, digits: 11)}: '${Key.getCommentName(name)}',
+''');
+    }
+    return result.toString();
+  }
+
   /// This generates the map of USB HID codes to physical keys.
   String get _predefinedHidCodeMap {
     final StringBuffer scanCodeMap = StringBuffer();
@@ -146,7 +165,9 @@
       'LOGICAL_KEY_MAP': _predefinedKeyCodeMap,
       'LOGICAL_KEY_DEFINITIONS': _logicalDefinitions,
       'LOGICAL_KEY_SYNONYMS': _logicalSynonyms,
+      'LOGICAL_KEY_KEY_LABELS': _logicalKeyLabels,
       'PHYSICAL_KEY_DEFINITIONS': _physicalDefinitions,
+      'PHYSICAL_KEY_DEBUG_NAMES': _physicalDebugNames,
     };
   }
 }