| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'common.dart'; | |
| import 'layout_types.dart'; | |
| // Maps all mandatory goals from the character to eventScanCode. | |
| // | |
| // Mandatory goals are all the alnum keys. These keys must be assigned at the | |
| // end of layout planning. | |
| final Map<String, String> _kMandatoryGoalsByChar = Map<String, String>.fromEntries( | |
| kLayoutGoals | |
| .entries | |
| .where((MapEntry<String, String> entry) => isLetter(entry.value.codeUnitAt(0))) | |
| .map((MapEntry<String, String?> entry) => MapEntry<String, String>(entry.value!, entry.key)) | |
| ); | |
| /// Plan a layout into a map from eventCode to logical key. | |
| /// | |
| /// If a eventCode does not exist in this map, then this event's logical key | |
| /// should be derived on the fly. | |
| /// | |
| /// This function defines the benchmark planning algorithm if there were a way | |
| /// to know the current keyboard layout. | |
| Map<String, int> planLayout(Map<String, LayoutEntry> entries) { | |
| // The logical key is derived in the following rules: | |
| // | |
| // 1. If any clue (the four possible printables) of the key is a mandatory | |
| // goal (alnum), then the goal is the logical key. | |
| // 2. If a mandatory goal is not assigned in the way of #1, then it is | |
| // assigned to the physical key as mapped in the US layout. | |
| // 3. Derived on the fly from key, code, and keyCode. | |
| // | |
| // The map returned from this function contains the first two rules. | |
| // Unresolved mandatory goals, mapped from printables to KeyboardEvent.code. | |
| // This map will be modified during this function and thus is a clone. | |
| final Map<String, String> mandatoryGoalsByChar = <String, String>{..._kMandatoryGoalsByChar}; | |
| // The result mapping from KeyboardEvent.code to logical key. | |
| final Map<String, int> result = <String, int>{}; | |
| entries.forEach((String eventCode, LayoutEntry entry) { | |
| for (final String printable in entry.printables) { | |
| if (mandatoryGoalsByChar.containsKey(printable)) { | |
| result[eventCode] = printable.codeUnitAt(0); | |
| mandatoryGoalsByChar.remove(printable); | |
| break; | |
| } | |
| } | |
| }); | |
| // Ensure all mandatory goals are assigned. | |
| mandatoryGoalsByChar.forEach((String character, String code) { | |
| assert(!result.containsKey(code), 'Code $code conflicts.'); | |
| result[code] = character.codeUnitAt(0); | |
| }); | |
| return result; | |
| } | |
| bool _isLetterOrMappedToKeyCode(int charCode) { | |
| return isLetterChar(charCode) || charCode == kUseKeyCode; | |
| } | |
| /// Plan all layouts, and summarize them into a huge table of EventCode -> | |
| /// EventKey -> logicalKey. | |
| /// | |
| /// The resulting logicalKey can also be kUseKeyCode. | |
| /// | |
| /// If a eventCode does not exist in this map, then this event's logical key | |
| /// should be derived on the fly. | |
| /// | |
| /// Entries that can be derived using heuristics are omitted. | |
| Map<String, Map<String, int>> combineLayouts(Iterable<Layout> layouts) { | |
| final Map<String, Map<String, int>> result = <String, Map<String, int>>{}; | |
| for (final Layout layout in layouts) { | |
| planLayout(layout.entries).forEach((String eventCode, int logicalKey) { | |
| final Map<String, int> codeMap = result.putIfAbsent(eventCode, () => <String, int>{}); | |
| final LayoutEntry entry = layout.entries[eventCode]!; | |
| for (final String eventKey in entry.printables) { | |
| if (eventKey.isEmpty) { | |
| continue; | |
| } | |
| // Found conflict. Assert that all such cases can be solved with | |
| // keyCode. | |
| if (codeMap.containsKey(eventKey) && codeMap[eventKey] != logicalKey) { | |
| assert(isLetterChar(logicalKey)); | |
| assert(_isLetterOrMappedToKeyCode(codeMap[eventKey]!), '$eventCode, $eventKey, ${codeMap[eventKey]!}'); | |
| codeMap[eventKey] = kUseKeyCode; | |
| } else { | |
| codeMap[eventKey] = logicalKey; | |
| } | |
| } | |
| }); | |
| } | |
| // Remove mapping results that can be derived using heuristics. | |
| result.removeWhere((String eventCode, Map<String, int> codeMap) { | |
| codeMap.removeWhere((String eventKey, int logicalKey) => | |
| heuristicMapper(eventCode, eventKey) == logicalKey, | |
| ); | |
| return codeMap.isEmpty; | |
| }); | |
| return result; | |
| } |