-
Notifications
You must be signed in to change notification settings - Fork 6k
[Web, keyboard] Locale layout mapping #34625
Changes from 60 commits
8cf086a
21c50f0
0883f87
22d2ccf
3c05c3d
becbf08
e82bc25
416f574
5ed152d
cb3b06d
e7a6473
b88d06f
2175a4a
4f1083f
0a85ed9
4b0d8e4
d4920f4
249542c
3c4cac8
84703c7
1a7b3d9
8798bbc
f05dc33
f5704c5
c1a964b
35d42b1
162e790
0227c4c
66eb8a8
9d8e1ce
ca87473
f1f5b80
f329b68
b894f71
49198b0
0a40d4d
ae05f6b
a6eb42d
1a80988
7f985ee
013ea9a
9cec217
0010a73
ca82c56
d0a7c45
5638316
3a68127
4953e1f
d2636a3
4d78abf
d089597
1343f01
d0cd62c
d4fbac1
cf2df77
af178f6
b2a63bf
0c51f44
7675615
f594a0d
c4d0aab
5dc792a
0c5dffe
6450c92
0ab2d9c
ec0a719
f1334ce
c0ba71e
2809d26
8b36170
3754b08
f7de833
7857246
3b938c3
1582aa2
b1bce40
edfdd0f
13e6fb4
f970ce1
de98688
fdd3225
0e32e71
0d570e2
a507c33
3cfaf33
c33c9f2
cedb9bf
fe356c0
884e783
3c48e3d
e27af08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
|
|
||
| import 'package:meta/meta.dart'; | ||
| import 'package:ui/ui.dart' as ui; | ||
| import 'package:web_locale_keymap/web_locale_keymap.dart' as keyboard_layouts; | ||
|
|
||
| import '../engine.dart' show registerHotRestartListener; | ||
| import 'browser_detection.dart'; | ||
|
|
@@ -98,13 +99,28 @@ Duration _eventTimeStampToDuration(num milliseconds) { | |
| return Duration(milliseconds: ms, microseconds: micro); | ||
| } | ||
|
|
||
| // Returns a function that caches the result of `body`, ensuring that `body` is | ||
| // only run once. | ||
| ValueGetter<T> _cached<T>(ValueGetter<T> body) { | ||
| T? cache; | ||
| return () { | ||
| return cache ??= body(); | ||
| }; | ||
| } | ||
|
|
||
| class KeyboardBinding { | ||
| KeyboardBinding._() { | ||
| _setup(); | ||
| _addEventListener('keydown', allowInterop((DomEvent domEvent) { | ||
| final FlutterHtmlKeyboardEvent event = FlutterHtmlKeyboardEvent(domEvent as DomKeyboardEvent); | ||
| return _converter.handleEvent(event); | ||
| })); | ||
| _addEventListener('keyup', allowInterop((DomEvent event) { | ||
| return _converter.handleEvent(FlutterHtmlKeyboardEvent(event as DomKeyboardEvent)); | ||
| })); | ||
| } | ||
|
|
||
| /// The singleton instance of this object. | ||
| static KeyboardBinding? get instance => _instance; | ||
| static KeyboardBinding get instance => _instance!; | ||
| static KeyboardBinding? _instance; | ||
|
|
||
| static void initInstance() { | ||
|
|
@@ -117,8 +133,23 @@ class KeyboardBinding { | |
| } | ||
| } | ||
|
|
||
| static void debugClearInstance() { | ||
| _instance = null; | ||
| } | ||
|
|
||
| /// The platform as used in the initialization. | ||
| /// | ||
| /// By default it is derived from [operatingSystem]. | ||
| @protected | ||
| OperatingSystem get localPlatform { | ||
| return operatingSystem; | ||
| } | ||
|
|
||
| KeyboardConverter get converter => _converter; | ||
| late final KeyboardConverter _converter; | ||
| late final KeyboardConverter _converter = KeyboardConverter( | ||
| _onKeyData, | ||
| localPlatform, | ||
| ); | ||
| final Map<String, DomEventListener> _listeners = <String, DomEventListener>{}; | ||
|
|
||
| void _addEventListener(String eventName, DomEventListener handler) { | ||
|
|
@@ -154,16 +185,6 @@ class KeyboardBinding { | |
| return result!; | ||
| } | ||
|
|
||
| void _setup() { | ||
| _addEventListener('keydown', allowInterop((DomEvent event) { | ||
| return _converter.handleEvent(FlutterHtmlKeyboardEvent(event as DomKeyboardEvent)); | ||
| })); | ||
| _addEventListener('keyup', allowInterop((DomEvent event) { | ||
| return _converter.handleEvent(FlutterHtmlKeyboardEvent(event as DomKeyboardEvent)); | ||
| })); | ||
| _converter = KeyboardConverter(_onKeyData, onMacOs: operatingSystem == OperatingSystem.macOs); | ||
| } | ||
|
|
||
| void _reset() { | ||
| _clearListeners(); | ||
| _converter.dispose(); | ||
|
|
@@ -210,10 +231,28 @@ class FlutterHtmlKeyboardEvent { | |
| // [dispatchKeyData] as given in the constructor. Some key data might be | ||
| // dispatched asynchronously. | ||
| class KeyboardConverter { | ||
| KeyboardConverter(this.performDispatchKeyData, {this.onMacOs = false}); | ||
| KeyboardConverter(this.performDispatchKeyData, OperatingSystem platform) | ||
| : onMacOs = platform == OperatingSystem.macOs, | ||
| _mapping = _mappingFromPlatform(platform); | ||
|
|
||
| final DispatchKeyData performDispatchKeyData; | ||
| // Whether the current platform is macOS, which affects how certain key events | ||
|
||
| // are comprehended. | ||
dkwingsmt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| final bool onMacOs; | ||
| // Maps logical keys from key event properties. | ||
dkwingsmt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| final keyboard_layouts.LocaleKeymap _mapping; | ||
|
|
||
| static keyboard_layouts.LocaleKeymap _mappingFromPlatform(OperatingSystem platform) { | ||
| switch (platform) { | ||
| case OperatingSystem.iOs: | ||
| case OperatingSystem.macOs: | ||
| return keyboard_layouts.LocaleKeymap.darwin(); | ||
| case OperatingSystem.windows: | ||
| return keyboard_layouts.LocaleKeymap.win(); | ||
| default: | ||
dkwingsmt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return keyboard_layouts.LocaleKeymap.linux(); | ||
| } | ||
| } | ||
|
|
||
| // The `performDispatchKeyData` wrapped with tracking logic. | ||
| // | ||
|
|
@@ -272,31 +311,16 @@ class KeyboardConverter { | |
| (metaDown ? _kDeadKeyMeta : 0); | ||
| } | ||
|
|
||
| // Whether `event.key` should be considered a key name. | ||
| // Whether `event.key` is a key name, such as "Shift", or otherwise a | ||
| // character, such as "S" or "ж". | ||
| // | ||
| // The `event.key` can either be a key name or the printable character. If the | ||
| // first character is an alphabet, it must be either 'A' to 'Z' ( and return | ||
| // true), or be a key name (and return false). Otherwise, return true. | ||
| // A key name always starts with a capitalized character, and has more than | ||
| // 1 letter. | ||
dkwingsmt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| static bool _eventKeyIsKeyname(String key) { | ||
| assert(key.isNotEmpty); | ||
| return isAlphabet(key.codeUnitAt(0)) && key.length > 1; | ||
| } | ||
|
|
||
| static int _characterToLogicalKey(String key) { | ||
| // Assume the length being <= 2 to be sufficient in all cases. If not, | ||
| // extend the algorithm. | ||
| assert(key.length <= 2); | ||
| int result = key.codeUnitAt(0) & 0xffff; | ||
| if (key.length == 2) { | ||
| result += key.codeUnitAt(1) << 16; | ||
| } | ||
| // Convert upper letters to lower letters | ||
| if (result >= _kCharUpperA && result <= _kCharUpperZ) { | ||
| result = result + _kCharLowerA - _kCharUpperA; | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| static int _deadKeyToLogicalKey(int physicalKey, FlutterHtmlKeyboardEvent event) { | ||
| // 'Dead' is used to represent dead keys, such as a diacritic to the | ||
| // following base letter (such as Option-e results in ´). | ||
|
|
@@ -306,10 +330,6 @@ class KeyboardConverter { | |
| return physicalKey + _getModifierMask(event) + _kWebKeyIdPlane; | ||
| } | ||
|
|
||
| static int _otherLogicalKey(String key) { | ||
| return kWebToLogicalKey[key] ?? (key.hashCode + _kWebKeyIdPlane); | ||
| } | ||
|
|
||
| // Map from pressed physical key to corresponding pressed logical key. | ||
| // | ||
| // Multiple physical keys can be mapped to the same logical key, usually due | ||
|
|
@@ -369,21 +389,32 @@ class KeyboardConverter { | |
|
|
||
| final int physicalKey = _getPhysicalCode(event.code!); | ||
| final bool logicalKeyIsCharacter = !_eventKeyIsKeyname(eventKey); | ||
| final String? character = logicalKeyIsCharacter ? eventKey : null; | ||
| final int logicalKey = () { | ||
| final ValueGetter<int> logicalKey = _cached<int>(() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the reason for using late final int logicalKey = () {
// ...
}();
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function might not be evaluated: if the event is a key up event, the logical key is simply the pressed logical key, and this is not needed at all. In this case, I'm hoping not to run the function body, since it's not a trivial amount of work. And as far as I understand,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to the docs, the https://dart.dev/null-safety/understanding-null-safety#lazy-initialization |
||
| // Dead keys. | ||
| if (eventKey == _kLogicalDead) { | ||
| return _deadKeyToLogicalKey(physicalKey, event); | ||
| } | ||
| // Mapped logical keys, such as ArrowLeft, Escape, AudioVolumeDown. | ||
| final int? mappedLogicalKey = kWebToLogicalKey[eventKey]; | ||
| if (mappedLogicalKey != null) { | ||
| return mappedLogicalKey; | ||
| } | ||
| // Keys with locations, such as modifier keys (Shift) or numpad keys. | ||
| if (kWebLogicalLocationMap.containsKey(event.key)) { | ||
| final int? result = kWebLogicalLocationMap[event.key!]?[event.location!]; | ||
| assert(result != null, 'Invalid modifier location: ${event.key}, ${event.location}'); | ||
| return result!; | ||
| } | ||
| if (character != null) { | ||
| return _characterToLogicalKey(character); | ||
| } | ||
| if (eventKey == _kLogicalDead) { | ||
| return _deadKeyToLogicalKey(physicalKey, event); | ||
| // Locale-sensitive keys: letters, digits, and certain symbols. | ||
| if (logicalKeyIsCharacter) { | ||
| final int? localeLogicalKeys = _mapping.getLogicalKey(event.code, event.key, event.keyCode); | ||
| if (localeLogicalKeys != null) { | ||
| return localeLogicalKeys; | ||
| } | ||
| } | ||
| return _otherLogicalKey(eventKey); | ||
| }(); | ||
| // Minted logical keys. | ||
| return eventKey.hashCode + _kWebKeyIdPlane; | ||
| }); | ||
|
|
||
| assert(event.type == 'keydown' || event.type == 'keyup'); | ||
| final bool isPhysicalDown = event.type == 'keydown' || | ||
|
|
@@ -405,7 +436,7 @@ class KeyboardConverter { | |
| timeStamp: timeStamp, | ||
| type: ui.KeyEventType.up, | ||
| physical: physicalKey, | ||
| logical: logicalKey, | ||
| logical: logicalKey(), | ||
| character: null, | ||
| synthesized: true, | ||
| ), | ||
|
|
@@ -440,7 +471,7 @@ class KeyboardConverter { | |
| timeStamp: timeStamp, | ||
| type: ui.KeyEventType.up, | ||
| physical: physicalKey, | ||
| logical: logicalKey, | ||
| logical: logicalKey(), | ||
| character: null, | ||
| synthesized: true, | ||
| )); | ||
|
|
@@ -473,7 +504,7 @@ class KeyboardConverter { | |
| switch (type) { | ||
| case ui.KeyEventType.down: | ||
| assert(lastLogicalRecord == null); | ||
| nextLogicalRecord = logicalKey; | ||
| nextLogicalRecord = logicalKey(); | ||
| break; | ||
| case ui.KeyEventType.up: | ||
| assert(lastLogicalRecord != null); | ||
|
|
@@ -498,7 +529,7 @@ class KeyboardConverter { | |
| _kLogicalKeyToModifierGetter.forEach((int testeeLogicalKey, _ModifierGetter getModifier) { | ||
| // Do not synthesize for the key of the current event. The event is the | ||
| // ground truth. | ||
| if (logicalKey == testeeLogicalKey) { | ||
| if (logicalKey() == testeeLogicalKey) { | ||
| return; | ||
| } | ||
| if (_pressingRecords.containsValue(testeeLogicalKey) && !getModifier(event)) { | ||
|
|
@@ -524,17 +555,18 @@ class KeyboardConverter { | |
| // Update key guards | ||
| if (logicalKeyIsCharacter) { | ||
| if (nextLogicalRecord != null) { | ||
| _startGuardingKey(physicalKey, logicalKey, timeStamp); | ||
| _startGuardingKey(physicalKey, logicalKey(), timeStamp); | ||
| } else { | ||
| _stopGuardingKey(physicalKey); | ||
| } | ||
| } | ||
|
|
||
| final String? character = logicalKeyIsCharacter ? eventKey : null; | ||
| final ui.KeyData keyData = ui.KeyData( | ||
| timeStamp: timeStamp, | ||
| type: type, | ||
| physical: physicalKey, | ||
| logical: lastLogicalRecord ?? logicalKey, | ||
| logical: lastLogicalRecord ?? logicalKey(), | ||
| character: type == ui.KeyEventType.up ? null : character, | ||
| synthesized: false, | ||
| ); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.