| // 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'; |
| } |
| } |
| } |