| // 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; | |
| String get label { | |
| return switch (this) { | |
| down => 'Key Down', | |
| up => 'Key Up', | |
| repeat => 'Key Repeat', | |
| }; | |
| } | |
| } | |
| /// The source device for the key event. | |
| /// | |
| /// Not all platforms supply an accurate type. | |
| // Must match the KeyEventDeviceType enum in ui/window/key_data.h. | |
| enum KeyEventDeviceType { | |
| /// The device is a keyboard. | |
| keyboard, | |
| /// The device is a directional pad on something like a television remote | |
| /// control or similar. | |
| directionalPad, | |
| /// The device is a gamepad button | |
| gamepad, | |
| /// The device is a joystick button | |
| joystick, | |
| /// The device is a device connected to an HDMI bus. | |
| hdmi; | |
| String get label { | |
| return switch (this) { | |
| keyboard => 'Keyboard', | |
| directionalPad => 'Directional Pad', | |
| gamepad => 'Gamepad', | |
| joystick => 'Joystick', | |
| hdmi => 'HDMI', | |
| }; | |
| } | |
| } | |
| /// 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, | |
| this.deviceType = KeyEventDeviceType.keyboard, | |
| }); | |
| /// 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; | |
| /// Describes what type of device (keyboard, directional pad, etc.) this event | |
| /// originated from. | |
| final KeyEventDeviceType deviceType; | |
| /// 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 provide 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 alternate 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() { | |
| return 'KeyData(${type.label}, ' | |
| '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: ${type.label}, ' | |
| 'deviceType: ${deviceType.label}, ' | |
| 'timeStamp: $timeStamp, ' | |
| 'physical: 0x${physical.toRadixString(16)}, ' | |
| 'logical: 0x${logical.toRadixString(16)}, ' | |
| 'character: ${_escapeCharacter()}, ' | |
| 'synthesized: $synthesized' | |
| ')'; | |
| } | |
| } |