mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
... and use it in `engine_tool` to prove everything is working, i.e. on CI and elsewhere. Partial work towards https://github.com/flutter/flutter/issues/147883. Supersedes and closes https://github.com/flutter/engine/pull/51782 (which was a prototype). See also: - https://flutter.dev/go/pub-workspace, the design doc on the feature. - https://github.com/dart-lang/pub-dev/pull/7762, an example PR. I'll probably end up moving the inline docs in `pubspec.yaml` to a `docs/*.md` once that's a thing.
245 lines
7.5 KiB
Dart
245 lines
7.5 KiB
Dart
// 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;
|
|
|
|
// TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917.
|
|
// ignore: public_member_api_docs
|
|
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;
|
|
|
|
// TODO(matanlurey): have original authors document; see https://github.com/flutter/flutter/issues/151917.
|
|
// ignore: public_member_api_docs
|
|
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'
|
|
')';
|
|
}
|
|
}
|