blob: 25eab4ac947548c7ba9199bfe8b168af5cfc82df [file] [log] [blame]
// 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'
')';
}
}