| // Copyright 2013 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. | |
| part of dart.ui; | |
| /// The type of a key event. | |
| // Must match the KeyEventType enum in ui/window/key_data.h. | |
| enum KeyEventType { | |
| /// The key is pressed. | |
| down, | |
| /// The key is released. | |
| up, | |
| /// The key is held, causing a repeated key input. | |
| repeat, | |
| } | |
| /// Information about a key event. | |
| class KeyData { | |
| /// Creates an object that represents a key event. | |
| const KeyData({ | |
| required this.timeStamp, | |
| required this.type, | |
| required this.physical, | |
| required this.logical, | |
| required this.character, | |
| required this.synthesized, | |
| }); | |
| /// Time of event dispatch, relative to an arbitrary timeline. | |
| /// | |
| /// For synthesized events, the [timeStamp] might not be the actual time that | |
| /// the key press or release happens. | |
| final Duration timeStamp; | |
| /// The type of the event. | |
| final KeyEventType type; | |
| /// The key code for the physical key that has changed. | |
| final int physical; | |
| /// The key code for the logical key that has changed. | |
| final int logical; | |
| /// Character input from the event. | |
| /// | |
| /// Ignored for up events. | |
| final String? character; | |
| /// If [synthesized] is true, this event does not correspond to a native event. | |
| /// | |
| /// Although most of Flutter's keyboard events are transformed from native | |
| /// events, some events are not based on native events, and are synthesized | |
| /// only to conform Flutter's key event model (as documented in | |
| /// the `HardwareKeyboard` class in the framework). | |
| /// | |
| /// For example, some key downs or ups might be lost when the window loses | |
| /// focus. Some platforms provides ways to query whether a key is being held. | |
| /// If the embedder detects an inconsistency between its internal record and | |
| /// the state returned by the system, the embedder will synthesize a | |
| /// corresponding event to synchronize the state without breaking the event | |
| /// model. | |
| /// | |
| /// As another example, macOS treats CapsLock in a special way by sending | |
| /// down and up events at the down of alterate presses to indicate the | |
| /// direction in which the lock is toggled instead of that the physical key is | |
| /// going. A macOS embedder should normalize the behavior by converting a | |
| /// native down event into a down event followed immediately by a synthesized | |
| /// up event, and the native up event also into a down event followed | |
| /// immediately by a synthesized up event. | |
| /// | |
| /// Synthesized events do not have a trustworthy [timeStamp], and should not be | |
| /// processed as if the key actually went down or up at the time of the | |
| /// callback. | |
| /// | |
| /// [KeyRepeatEvent] is never synthesized. | |
| final bool synthesized; | |
| // 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 operations and needs to use division | |
| // instead. | |
| static int _nonValueBits(int n) { | |
| const int valueMask = 0x000FFFFFFFF; | |
| // `n >> valueMaskWidth` is equivalent to `n / divisorForValueMask`. | |
| const int divisorForValueMask = valueMask + 1; | |
| const int valueMaskWidth = 32; | |
| // 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 (identical(0, 0.0)) { // Detects if we are on the web. | |
| return (n / divisorForValueMask).floor() & nonValueMask; | |
| } else { | |
| return (n >> valueMaskWidth) & nonValueMask; | |
| } | |
| } | |
| String _logicalToString() { | |
| final String result = '0x${logical.toRadixString(16)}'; | |
| final int planeNum = _nonValueBits(logical) & 0x0FF; | |
| final String planeDescription = (() { | |
| switch (planeNum) { | |
| case 0x000: | |
| return ' (Unicode)'; | |
| case 0x001: | |
| return ' (Unprintable)'; | |
| case 0x002: | |
| return ' (Flutter)'; | |
| case 0x011: | |
| return ' (Android)'; | |
| case 0x012: | |
| return ' (Fuchsia)'; | |
| case 0x013: | |
| return ' (iOS)'; | |
| case 0x014: | |
| return ' (macOS)'; | |
| case 0x015: | |
| return ' (GTK)'; | |
| case 0x016: | |
| return ' (Windows)'; | |
| case 0x017: | |
| return ' (Web)'; | |
| case 0x018: | |
| return ' (GLFW)'; | |
| } | |
| return ''; | |
| })(); | |
| return '$result$planeDescription'; | |
| } | |
| String? _escapeCharacter() { | |
| if (character == null) { | |
| return '<none>'; | |
| } | |
| switch (character!) { | |
| case '\n': | |
| return r'"\n"'; | |
| case '\t': | |
| return r'"\t"'; | |
| case '\r': | |
| return r'"\r"'; | |
| case '\b': | |
| return r'"\b"'; | |
| case '\f': | |
| return r'"\f"'; | |
| default: | |
| return '"$character"'; | |
| } | |
| } | |
| String? _quotedCharCode() { | |
| if (character == null) { | |
| return ''; | |
| } | |
| final Iterable<String> hexChars = character!.codeUnits | |
| .map((int code) => code.toRadixString(16).padLeft(2, '0')); | |
| return ' (0x${hexChars.join(' ')})'; | |
| } | |
| @override | |
| String toString() => 'KeyData(key ${_typeToString(type)}, physical: 0x${physical.toRadixString(16)}, ' | |
| 'logical: ${_logicalToString()}, character: ${_escapeCharacter()}${_quotedCharCode()}${synthesized ? ', synthesized' : ''})'; | |
| /// Returns a complete textual description of the information in this object. | |
| String toStringFull() { | |
| return '$runtimeType(' | |
| 'type: ${_typeToString(type)}, ' | |
| 'timeStamp: $timeStamp, ' | |
| 'physical: 0x${physical.toRadixString(16)}, ' | |
| 'logical: 0x${logical.toRadixString(16)}, ' | |
| 'character: ${_escapeCharacter()}, ' | |
| 'synthesized: $synthesized' | |
| ')'; | |
| } | |
| static String _typeToString(KeyEventType type) { | |
| switch (type) { | |
| case KeyEventType.up: | |
| return 'up'; | |
| case KeyEventType.down: | |
| return 'down'; | |
| case KeyEventType.repeat: | |
| return 'repeat'; | |
| } | |
| } | |
| } |