@@ -76,9 +76,9 @@ abstract class KeyboardKey with Diagnosticable {
7676/// _message = 'Pressed the "Q" key!';
7777/// } else {
7878/// if (kReleaseMode) {
79- /// _message = 'Not a Q: Key label is " ${event.logicalKey.keyLabel}" ';
79+ /// _message = 'Not a Q: Pressed 0x ${event.logicalKey.keyId.toRadixString(16)} ';
8080/// } else {
81- /// // This will only print useful information in debug mode.
81+ /// // The debugName will only print useful information in debug mode.
8282/// _message = 'Not a Q: Pressed ${event.logicalKey.debugName}';
8383/// }
8484/// }
@@ -124,59 +124,61 @@ abstract class KeyboardKey with Diagnosticable {
124124/// keyboard events.
125125@immutable
126126class LogicalKeyboardKey extends KeyboardKey {
127- /// Creates a LogicalKeyboardKey object with an optional key label and debug
128- /// name.
127+ /// A LogicalKeyboardKey object for a key ID.
129128 ///
130- /// [keyId] must not be null.
131- ///
132- /// {@tool snippet}
133- /// To save executable size, it is recommended that the [debugName] be null in
134- /// release mode. You can do this by using the [kReleaseMode] constant.
135- ///
136- /// ```dart
137- /// const LogicalKeyboardKey(0x0010000000a, debugName: kReleaseMode ? null : 'Special Key')
138- /// ```
139- /// {@end-tool}
140- const LogicalKeyboardKey(this.keyId, {this.debugName, this.keyLabel = ''})
141- : assert(keyId != null);
129+ /// This factory constructor ensures that the same `keyId` always results
130+ /// in the identical instance. It looks up an object from the predefined values
131+ /// if possible, or construct a new one and cache it.
132+ factory LogicalKeyboardKey(int keyId) {
133+ return findKeyByKeyId(keyId) ??
134+ _additionalKeyPool.putIfAbsent(keyId, () => LogicalKeyboardKey._(keyId));
135+ }
136+
137+ /// Creates a LogicalKeyboardKey object for a key ID.
138+ const LogicalKeyboardKey._(this.keyId);
142139
143140 /// A unique code representing this key.
144141 ///
145142 /// This is an opaque code. It should not be unpacked to derive information
146143 /// from it, as the representation of the code could change at any time.
147144 final int keyId;
148145
146+ // Returns the bits that are not included in [valueMask], shifted to the
147+ // right.
148+ //
149+ // For example, if the input is 0x12abcdabcd, then the result is 0x12.
150+ //
151+ // This could have been a trivial shifting for non-Web environment, but in
152+ // order to support JavaScript, which only supports bitwise operation of up to
153+ // 32 bits, this function extracts them using division.
154+ //
155+ // It returns 0 for release mode.
156+ static int _debugNonValueBits(int numOf64Bits) {
157+ int result = 0;
158+ assert(() {
159+ const int kDivisorForValueMask = valueMask + 1;
160+ result = (numOf64Bits / kDivisorForValueMask).floor();
161+ return true;
162+ }());
163+ return result;
164+ }
165+
149166 /// The debug string to print for this keyboard key, which will be null in
150167 /// release mode.
151- final String? debugName;
152-
153- /// The Unicode string representing the character produced by a [RawKeyEvent].
154- ///
155- /// This value is useful for describing or matching mnemonic keyboard
156- /// shortcuts.
157- ///
158- /// This value is an empty string if there's no key label data for a key.
159- ///
160- /// On most platforms this is a single code point, but it could contain any
161- /// Unicode string. The `keyLabel` differs from [RawKeyEvent.character]
162- /// because `keyLabel` only takes into account the key being pressed, not any
163- /// combining keys pressed before it, so, for example, an “o” that follows a
164- /// combining dieresis (“¨”, COMBINING DIAERESIS (U+0308)) would just return
165- /// “o” for [keyLabel], but would return “ö” for [RawKeyEvent.character].
166- ///
167- /// {@macro flutter.services.RawKeyEventData.keyLabel}
168- final String keyLabel;
169-
170- @override
171- int get hashCode => keyId.hashCode;
172-
173- @override
174- bool operator ==(Object other) {
175- if (other.runtimeType != runtimeType) {
176- return false;
177- }
178- return other is LogicalKeyboardKey
179- && other.keyId == keyId;
168+ String? get debugName {
169+ String? result;
170+ assert(() {
171+ result = _debugNames[keyId];
172+ if (result == null) {
173+ if (_debugNonValueBits(keyId) == 0) {
174+ result = 'Key ${String.fromCharCode(keyId)}';
175+ } else {
176+ result = 'Key with ID 0x${keyId.toRadixString(16).padLeft(11, '0')}';
177+ }
178+ }
179+ return true;
180+ }());
181+ return result;
180182 }
181183
182184 /// Returns the [LogicalKeyboardKey] constant that matches the given ID, or
@@ -261,10 +263,11 @@ class LogicalKeyboardKey extends KeyboardKey {
261263 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
262264 super.debugFillProperties(properties);
263265 properties.add(StringProperty('keyId', '0x${keyId.toRadixString(16).padLeft(8, '0')}', showName: true));
264- properties.add(StringProperty('keyLabel', keyLabel, showName: true));
265266 properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
266267 }
267268
269+ static final Map<int, LogicalKeyboardKey> _additionalKeyPool = <int, LogicalKeyboardKey>{};
270+
268271 /// Mask for the 32-bit value portion of the key code.
269272 ///
270273 /// This is used by platform-specific code to generate Flutter key codes.
@@ -313,6 +316,11 @@ class LogicalKeyboardKey extends KeyboardKey {
313316 // A map of keys to the pseudo-key synonym for that key. Used by getSynonyms.
314317 static final Map<LogicalKeyboardKey, LogicalKeyboardKey> _synonyms = <LogicalKeyboardKey, LogicalKeyboardKey>{
315318@@@LOGICAL_KEY_SYNONYMS@@@ };
319+
320+ static const Map<int, String> _debugNames = kReleaseMode ?
321+ <int, String>{} :
322+ <int, String>{
323+ @@@LOGICAL_KEY_DEBUG_NAMES@@@ };
316324}
317325
318326/// A class with static values that describe the keys that are returned from
@@ -387,7 +395,7 @@ class LogicalKeyboardKey extends KeyboardKey {
387395/// onTap: () {
388396/// FocusScope.of(context).requestFocus(_focusNode);
389397/// },
390- /// child: Text('Tap to focus'),
398+ /// child: const Text('Tap to focus'),
391399/// );
392400/// }
393401/// return Text(_message ?? 'Press a key');
@@ -408,20 +416,18 @@ class LogicalKeyboardKey extends KeyboardKey {
408416/// keyboard events.
409417@immutable
410418class PhysicalKeyboardKey extends KeyboardKey {
411- /// Creates a PhysicalKeyboardKey object with an optional debug name.
412- ///
413- /// The [usbHidUsage] must not be null.
414- ///
415- /// {@tool snippet}
416- /// To save executable size, it is recommended that the [debugName] be null in
417- /// release mode. You can do this using the [kReleaseMode] constant.
419+ /// A PhysicalKeyboardKey object for a USB HID usage.
418420 ///
419- /// ```dart
420- /// const PhysicalKeyboardKey(0x0000ffff, debugName: kReleaseMode ? null : 'Special Key')
421- /// ```
422- /// {@end-tool}
423- const PhysicalKeyboardKey(this.usbHidUsage, {this.debugName})
424- : assert(usbHidUsage != null);
421+ /// This factory constructor ensures that the same `usbHidUsage` always results
422+ /// in the identical instance. It looks up an object from the predefined values
423+ /// if possible, or construct a new one and cache it.
424+ factory PhysicalKeyboardKey(int usbHidUsage) {
425+ return findKeyByCode(usbHidUsage) ??
426+ _additionalKeyPool.putIfAbsent(usbHidUsage, () => PhysicalKeyboardKey._(usbHidUsage));
427+ }
428+
429+ /// Creates a PhysicalKeyboardKey object for a USB HID usage.
430+ const PhysicalKeyboardKey._(this.usbHidUsage);
425431
426432 /// The unique USB HID usage ID of this physical key on the keyboard.
427433 ///
@@ -435,31 +441,29 @@ class PhysicalKeyboardKey extends KeyboardKey {
435441
436442 /// The debug string to print for this keyboard key, which will be null in
437443 /// release mode.
438- final String? debugName;
444+ String? get debugName {
445+ String? result;
446+ assert(() {
447+ result = _debugNames[usbHidUsage] ??
448+ 'Key with ID 0x${usbHidUsage.toRadixString(16).padLeft(8, '0')}';
449+ return true;
450+ }());
451+ return result;
452+ }
439453
440454 /// Finds a known [PhysicalKeyboardKey] that matches the given USB HID usage
441455 /// code.
442456 static PhysicalKeyboardKey? findKeyByCode(int usageCode) => _knownPhysicalKeys[usageCode];
443457
444- @override
445- int get hashCode => usbHidUsage.hashCode;
446-
447- @override
448- bool operator ==(Object other) {
449- if (other.runtimeType != runtimeType) {
450- return false;
451- }
452- return other is PhysicalKeyboardKey
453- && other.usbHidUsage == usbHidUsage;
454- }
455-
456458 @override
457459 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
458460 super.debugFillProperties(properties);
459461 properties.add(StringProperty('usbHidUsage', '0x${usbHidUsage.toRadixString(16).padLeft(8, '0')}', showName: true));
460462 properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
461463 }
462464
465+ static final Map<int, PhysicalKeyboardKey> _additionalKeyPool = <int, PhysicalKeyboardKey>{};
466+
463467 // Key constants for all keyboard keys in the USB HID specification at the
464468 // time Flutter was built.
465469@@@PHYSICAL_KEY_DEFINITIONS@@@
@@ -468,4 +472,9 @@ class PhysicalKeyboardKey extends KeyboardKey {
468472 static const Map<int, PhysicalKeyboardKey> _knownPhysicalKeys = <int, PhysicalKeyboardKey>{
469473@@@PHYSICAL_KEY_MAP@@@
470474 };
475+
476+ static const Map<int, String> _debugNames = kReleaseMode ?
477+ <int, String>{} :
478+ <int, String>{
479+ @@@PHYSICAL_KEY_DEBUG_NAMES@@@ };
471480}
0 commit comments