4
4
5
5
import 'package:meta/meta.dart' ;
6
6
import 'package:ui/ui.dart' as ui;
7
+ import 'package:web_locale_keymap/web_locale_keymap.dart' as locale_keymap;
7
8
8
9
import '../engine.dart' show registerHotRestartListener;
9
10
import 'browser_detection.dart' ;
@@ -54,16 +55,6 @@ final Map<int, _ModifierGetter> _kLogicalKeyToModifierGetter = <int, _ModifierGe
54
55
_kLogicalMetaRight: (FlutterHtmlKeyboardEvent event) => event.metaKey,
55
56
};
56
57
57
- // ASCII for a, z, A, and Z
58
- const int _kCharLowerA = 0x61 ;
59
- const int _kCharLowerZ = 0x7a ;
60
- const int _kCharUpperA = 0x41 ;
61
- const int _kCharUpperZ = 0x5a ;
62
- bool isAlphabet (int charCode) {
63
- return (charCode >= _kCharLowerA && charCode <= _kCharLowerZ)
64
- || (charCode >= _kCharUpperA && charCode <= _kCharUpperZ);
65
- }
66
-
67
58
const String _kPhysicalCapsLock = 'CapsLock' ;
68
59
69
60
const String _kLogicalDead = 'Dead' ;
@@ -98,9 +89,24 @@ Duration _eventTimeStampToDuration(num milliseconds) {
98
89
return Duration (milliseconds: ms, microseconds: micro);
99
90
}
100
91
92
+ // Returns a function that caches the result of `body`, ensuring that `body` is
93
+ // only run once.
94
+ ValueGetter <T > _cached <T >(ValueGetter <T > body) {
95
+ T ? cache;
96
+ return () {
97
+ return cache ?? = body ();
98
+ };
99
+ }
100
+
101
101
class KeyboardBinding {
102
102
KeyboardBinding ._() {
103
- _setup ();
103
+ _addEventListener ('keydown' , allowInterop ((DomEvent domEvent) {
104
+ final FlutterHtmlKeyboardEvent event = FlutterHtmlKeyboardEvent (domEvent as DomKeyboardEvent );
105
+ return _converter.handleEvent (event);
106
+ }));
107
+ _addEventListener ('keyup' , allowInterop ((DomEvent event) {
108
+ return _converter.handleEvent (FlutterHtmlKeyboardEvent (event as DomKeyboardEvent ));
109
+ }));
104
110
}
105
111
106
112
/// The singleton instance of this object.
@@ -117,8 +123,19 @@ class KeyboardBinding {
117
123
}
118
124
}
119
125
126
+ /// The platform as used in the initialization.
127
+ ///
128
+ /// By default it is derived from [operatingSystem] .
129
+ @protected
130
+ OperatingSystem get localPlatform {
131
+ return operatingSystem;
132
+ }
133
+
120
134
KeyboardConverter get converter => _converter;
121
- late final KeyboardConverter _converter;
135
+ late final KeyboardConverter _converter = KeyboardConverter (
136
+ _onKeyData,
137
+ localPlatform,
138
+ );
122
139
final Map <String , DomEventListener > _listeners = < String , DomEventListener > {};
123
140
124
141
void _addEventListener (String eventName, DomEventListener handler) {
@@ -154,16 +171,6 @@ class KeyboardBinding {
154
171
return result! ;
155
172
}
156
173
157
- void _setup () {
158
- _addEventListener ('keydown' , allowInterop ((DomEvent event) {
159
- return _converter.handleEvent (FlutterHtmlKeyboardEvent (event as DomKeyboardEvent ));
160
- }));
161
- _addEventListener ('keyup' , allowInterop ((DomEvent event) {
162
- return _converter.handleEvent (FlutterHtmlKeyboardEvent (event as DomKeyboardEvent ));
163
- }));
164
- _converter = KeyboardConverter (_onKeyData, onMacOs: operatingSystem == OperatingSystem .macOs);
165
- }
166
-
167
174
void _reset () {
168
175
_clearListeners ();
169
176
_converter.dispose ();
@@ -211,10 +218,30 @@ class FlutterHtmlKeyboardEvent {
211
218
// [dispatchKeyData] as given in the constructor. Some key data might be
212
219
// dispatched asynchronously.
213
220
class KeyboardConverter {
214
- KeyboardConverter (this .performDispatchKeyData, {this .onMacOs = false });
221
+ KeyboardConverter (this .performDispatchKeyData, OperatingSystem platform)
222
+ : onMacOs = platform == OperatingSystem .macOs,
223
+ _mapping = _mappingFromPlatform (platform);
215
224
216
225
final DispatchKeyData performDispatchKeyData;
226
+ /// Whether the current platform is macOS, which affects how certain key events
227
+ /// are comprehended.
217
228
final bool onMacOs;
229
+ /// Maps logical keys from key event properties.
230
+ final locale_keymap.LocaleKeymap _mapping;
231
+
232
+ static locale_keymap.LocaleKeymap _mappingFromPlatform (OperatingSystem platform) {
233
+ switch (platform) {
234
+ case OperatingSystem .iOs:
235
+ case OperatingSystem .macOs:
236
+ return locale_keymap.LocaleKeymap .darwin ();
237
+ case OperatingSystem .windows:
238
+ return locale_keymap.LocaleKeymap .win ();
239
+ case OperatingSystem .android:
240
+ case OperatingSystem .linux:
241
+ case OperatingSystem .unknown:
242
+ return locale_keymap.LocaleKeymap .linux ();
243
+ }
244
+ }
218
245
219
246
// The `performDispatchKeyData` wrapped with tracking logic.
220
247
//
@@ -273,29 +300,14 @@ class KeyboardConverter {
273
300
(metaDown ? _kDeadKeyMeta : 0 );
274
301
}
275
302
276
- // Whether `event.key` should be considered a key name.
303
+ // Whether `event.key` is a key name, such as "Shift", or otherwise a
304
+ // character, such as "S" or "ж".
277
305
//
278
- // The `event.key` can either be a key name or the printable character. If the
279
- // first character is an alphabet, it must be either 'A' to 'Z' ( and return
280
- // true), or be a key name (and return false). Otherwise, return true.
281
- static bool _eventKeyIsKeyname (String key) {
282
- assert (key.isNotEmpty);
283
- return isAlphabet (key.codeUnitAt (0 )) && key.length > 1 ;
284
- }
285
-
286
- static int _characterToLogicalKey (String key) {
287
- // Assume the length being <= 2 to be sufficient in all cases. If not,
288
- // extend the algorithm.
289
- assert (key.length <= 2 );
290
- int result = key.codeUnitAt (0 ) & 0xffff ;
291
- if (key.length == 2 ) {
292
- result += key.codeUnitAt (1 ) << 16 ;
293
- }
294
- // Convert upper letters to lower letters
295
- if (result >= _kCharUpperA && result <= _kCharUpperZ) {
296
- result = result + _kCharLowerA - _kCharUpperA;
297
- }
298
- return result;
306
+ // A key name always has more than 1 code unit, and they are all alnums.
307
+ // Character keys, however, can also have more than 1 code unit: en-in
308
+ // maps KeyL to L̥/l̥. To resolve this, we check the second code unit.
309
+ static bool _eventKeyIsKeyName (String key) {
310
+ return key.length > 1 && key.codeUnitAt (0 ) < 0x7F && key.codeUnitAt (1 ) < 0x7F ;
299
311
}
300
312
301
313
static int _deadKeyToLogicalKey (int physicalKey, FlutterHtmlKeyboardEvent event) {
@@ -307,10 +319,6 @@ class KeyboardConverter {
307
319
return physicalKey + _getModifierMask (event) + _kWebKeyIdPlane;
308
320
}
309
321
310
- static int _otherLogicalKey (String key) {
311
- return kWebToLogicalKey[key] ?? (key.hashCode + _kWebKeyIdPlane);
312
- }
313
-
314
322
// Map from pressed physical key to corresponding pressed logical key.
315
323
//
316
324
// Multiple physical keys can be mapped to the same logical key, usually due
@@ -369,22 +377,36 @@ class KeyboardConverter {
369
377
final String eventKey = event.key! ;
370
378
371
379
final int physicalKey = _getPhysicalCode (event.code! );
372
- final bool logicalKeyIsCharacter = ! _eventKeyIsKeyname (eventKey);
373
- final String ? character = logicalKeyIsCharacter ? eventKey : null ;
374
- final int logicalKey = () {
380
+ final bool logicalKeyIsCharacter = ! _eventKeyIsKeyName (eventKey);
381
+ // The function body might or might not be evaluated. If the event is a key
382
+ // up event, the resulting event will simply use the currently pressed
383
+ // logical key.
384
+ final ValueGetter <int > logicalKey = _cached <int >(() {
385
+ // Mapped logical keys, such as ArrowLeft, Escape, AudioVolumeDown.
386
+ final int ? mappedLogicalKey = kWebToLogicalKey[eventKey];
387
+ if (mappedLogicalKey != null ) {
388
+ return mappedLogicalKey;
389
+ }
390
+ // Keys with locations, such as modifier keys (Shift) or numpad keys.
375
391
if (kWebLogicalLocationMap.containsKey (event.key)) {
376
392
final int ? result = kWebLogicalLocationMap[event.key! ]? [event.location! ];
377
393
assert (result != null , 'Invalid modifier location: ${event .key }, ${event .location }' );
378
394
return result! ;
379
395
}
380
- if (character != null ) {
381
- return _characterToLogicalKey (character);
396
+ // Locale-sensitive keys: letters, digits, and certain symbols.
397
+ if (logicalKeyIsCharacter) {
398
+ final int ? localeLogicalKeys = _mapping.getLogicalKey (event.code, event.key, event.keyCode);
399
+ if (localeLogicalKeys != null ) {
400
+ return localeLogicalKeys;
401
+ }
382
402
}
403
+ // Dead keys that are not handled by the locale mapping.
383
404
if (eventKey == _kLogicalDead) {
384
405
return _deadKeyToLogicalKey (physicalKey, event);
385
406
}
386
- return _otherLogicalKey (eventKey);
387
- }();
407
+ // Minted logical keys.
408
+ return eventKey.hashCode + _kWebKeyIdPlane;
409
+ });
388
410
389
411
assert (event.type == 'keydown' || event.type == 'keyup' );
390
412
final bool isPhysicalDown = event.type == 'keydown' ||
@@ -406,7 +428,7 @@ class KeyboardConverter {
406
428
timeStamp: timeStamp,
407
429
type: ui.KeyEventType .up,
408
430
physical: physicalKey,
409
- logical: logicalKey,
431
+ logical: logicalKey () ,
410
432
character: null ,
411
433
synthesized: true ,
412
434
),
@@ -441,7 +463,7 @@ class KeyboardConverter {
441
463
timeStamp: timeStamp,
442
464
type: ui.KeyEventType .up,
443
465
physical: physicalKey,
444
- logical: logicalKey,
466
+ logical: logicalKey () ,
445
467
character: null ,
446
468
synthesized: true ,
447
469
));
@@ -474,7 +496,7 @@ class KeyboardConverter {
474
496
switch (type) {
475
497
case ui.KeyEventType .down:
476
498
assert (lastLogicalRecord == null );
477
- nextLogicalRecord = logicalKey;
499
+ nextLogicalRecord = logicalKey () ;
478
500
break ;
479
501
case ui.KeyEventType .up:
480
502
assert (lastLogicalRecord != null );
@@ -499,7 +521,7 @@ class KeyboardConverter {
499
521
_kLogicalKeyToModifierGetter.forEach ((int testeeLogicalKey, _ModifierGetter getModifier) {
500
522
// Do not synthesize for the key of the current event. The event is the
501
523
// ground truth.
502
- if (logicalKey == testeeLogicalKey) {
524
+ if (logicalKey () == testeeLogicalKey) {
503
525
return ;
504
526
}
505
527
if (_pressingRecords.containsValue (testeeLogicalKey) && ! getModifier (event)) {
@@ -525,17 +547,18 @@ class KeyboardConverter {
525
547
// Update key guards
526
548
if (logicalKeyIsCharacter) {
527
549
if (nextLogicalRecord != null ) {
528
- _startGuardingKey (physicalKey, logicalKey, timeStamp);
550
+ _startGuardingKey (physicalKey, logicalKey () , timeStamp);
529
551
} else {
530
552
_stopGuardingKey (physicalKey);
531
553
}
532
554
}
533
555
556
+ final String ? character = logicalKeyIsCharacter ? eventKey : null ;
534
557
final ui.KeyData keyData = ui.KeyData (
535
558
timeStamp: timeStamp,
536
559
type: type,
537
560
physical: physicalKey,
538
- logical: lastLogicalRecord ?? logicalKey,
561
+ logical: lastLogicalRecord ?? logicalKey () ,
539
562
character: type == ui.KeyEventType .up ? null : character,
540
563
synthesized: false ,
541
564
);
0 commit comments