This reverts commit 5f792ba17088b356bf0d030844ab09fd7f2b812e.
This commit is contained in:
parent
863c42a8b4
commit
2f4f170dd2
@ -327,9 +327,6 @@ class LogicalKeyboardKey extends KeyboardKey {
|
|||||||
|
|
||||||
@@@LOGICAL_KEY_DEFINITIONS@@@
|
@@@LOGICAL_KEY_DEFINITIONS@@@
|
||||||
|
|
||||||
/// A list of all predefined constant [LogicalKeyboardKey]s.
|
|
||||||
static Iterable<LogicalKeyboardKey> get knownLogicalKeys => _knownLogicalKeys.values;
|
|
||||||
|
|
||||||
// A list of all predefined constant LogicalKeyboardKeys so they can be
|
// A list of all predefined constant LogicalKeyboardKeys so they can be
|
||||||
// searched.
|
// searched.
|
||||||
static const Map<int, LogicalKeyboardKey> _knownLogicalKeys = <int, LogicalKeyboardKey>{
|
static const Map<int, LogicalKeyboardKey> _knownLogicalKeys = <int, LogicalKeyboardKey>{
|
||||||
@ -492,9 +489,6 @@ class PhysicalKeyboardKey extends KeyboardKey {
|
|||||||
|
|
||||||
@@@PHYSICAL_KEY_DEFINITIONS@@@
|
@@@PHYSICAL_KEY_DEFINITIONS@@@
|
||||||
|
|
||||||
/// A list of all predefined constant [PhysicalKeyboardKey]s.
|
|
||||||
static Iterable<PhysicalKeyboardKey> get knownPhysicalKeys => _knownPhysicalKeys.values;
|
|
||||||
|
|
||||||
// A list of all the predefined constant PhysicalKeyboardKeys so that they
|
// A list of all the predefined constant PhysicalKeyboardKeys so that they
|
||||||
// can be searched.
|
// can be searched.
|
||||||
static const Map<int, PhysicalKeyboardKey> _knownPhysicalKeys = <int, PhysicalKeyboardKey>{
|
static const Map<int, PhysicalKeyboardKey> _knownPhysicalKeys = <int, PhysicalKeyboardKey>{
|
||||||
|
@ -176,5 +176,41 @@
|
|||||||
"Zoom": ["ZOOM"],
|
"Zoom": ["ZOOM"],
|
||||||
"Noname": ["NONAME"],
|
"Noname": ["NONAME"],
|
||||||
"Pa1": ["PA1"],
|
"Pa1": ["PA1"],
|
||||||
"OemClear": ["OEM_CLEAR"]
|
"OemClear": ["OEM_CLEAR"],
|
||||||
|
"0": ["0"],
|
||||||
|
"1": ["1"],
|
||||||
|
"2": ["2"],
|
||||||
|
"3": ["3"],
|
||||||
|
"4": ["4"],
|
||||||
|
"5": ["5"],
|
||||||
|
"6": ["6"],
|
||||||
|
"7": ["7"],
|
||||||
|
"8": ["8"],
|
||||||
|
"9": ["9"],
|
||||||
|
"KeyA": ["A"],
|
||||||
|
"KeyB": ["B"],
|
||||||
|
"KeyC": ["C"],
|
||||||
|
"KeyD": ["D"],
|
||||||
|
"KeyE": ["E"],
|
||||||
|
"KeyF": ["F"],
|
||||||
|
"KeyG": ["G"],
|
||||||
|
"KeyH": ["H"],
|
||||||
|
"KeyI": ["I"],
|
||||||
|
"KeyJ": ["J"],
|
||||||
|
"KeyK": ["K"],
|
||||||
|
"KeyL": ["L"],
|
||||||
|
"KeyM": ["M"],
|
||||||
|
"KeyN": ["N"],
|
||||||
|
"KeyO": ["O"],
|
||||||
|
"KeyP": ["P"],
|
||||||
|
"KeyQ": ["Q"],
|
||||||
|
"KeyR": ["R"],
|
||||||
|
"KeyS": ["S"],
|
||||||
|
"KeyT": ["T"],
|
||||||
|
"KeyU": ["U"],
|
||||||
|
"KeyV": ["V"],
|
||||||
|
"KeyW": ["W"],
|
||||||
|
"KeyX": ["X"],
|
||||||
|
"KeyY": ["Y"],
|
||||||
|
"KeyZ": ["Z"]
|
||||||
}
|
}
|
||||||
|
@ -24,16 +24,6 @@ bool _isAsciiLetter(String? char) {
|
|||||||
|| (charCode >= charLowerA && charCode <= charLowerZ);
|
|| (charCode >= charLowerA && charCode <= charLowerZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isDigit(String? char) {
|
|
||||||
if (char == null)
|
|
||||||
return false;
|
|
||||||
final int charDigit0 = '0'.codeUnitAt(0);
|
|
||||||
final int charDigit9 = '9'.codeUnitAt(0);
|
|
||||||
assert(char.length == 1);
|
|
||||||
final int charCode = char.codeUnitAt(0);
|
|
||||||
return charCode >= charDigit0 && charCode <= charDigit9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates the keyboard_maps.dart files, based on the information in the key
|
/// Generates the keyboard_maps.dart files, based on the information in the key
|
||||||
/// data structure given to it.
|
/// data structure given to it.
|
||||||
class KeyboardMapsCodeGenerator extends BaseCodeGenerator {
|
class KeyboardMapsCodeGenerator extends BaseCodeGenerator {
|
||||||
@ -181,9 +171,7 @@ class KeyboardMapsCodeGenerator extends BaseCodeGenerator {
|
|||||||
// because they are not used by the embedding. Add them manually.
|
// because they are not used by the embedding. Add them manually.
|
||||||
final List<int>? keyCodes = entry.windowsValues.isNotEmpty
|
final List<int>? keyCodes = entry.windowsValues.isNotEmpty
|
||||||
? entry.windowsValues
|
? entry.windowsValues
|
||||||
: (_isAsciiLetter(entry.keyLabel) ? <int>[entry.keyLabel!.toUpperCase().codeUnitAt(0)] :
|
: (_isAsciiLetter(entry.keyLabel) ? <int>[entry.keyLabel!.toUpperCase().codeUnitAt(0)] : null);
|
||||||
_isDigit(entry.keyLabel) ? <int>[entry.keyLabel!.toUpperCase().codeUnitAt(0)] :
|
|
||||||
null);
|
|
||||||
if (keyCodes != null) {
|
if (keyCodes != null) {
|
||||||
for (final int code in keyCodes) {
|
for (final int code in keyCodes) {
|
||||||
lines.add(code, ' $code: LogicalKeyboardKey.${entry.constantName},');
|
lines.add(code, ' $code: LogicalKeyboardKey.${entry.constantName},');
|
||||||
|
@ -15,11 +15,9 @@ export 'src/services/autofill.dart';
|
|||||||
export 'src/services/binary_messenger.dart';
|
export 'src/services/binary_messenger.dart';
|
||||||
export 'src/services/binding.dart';
|
export 'src/services/binding.dart';
|
||||||
export 'src/services/clipboard.dart';
|
export 'src/services/clipboard.dart';
|
||||||
export 'src/services/debug.dart';
|
|
||||||
export 'src/services/deferred_component.dart';
|
export 'src/services/deferred_component.dart';
|
||||||
export 'src/services/font_loader.dart';
|
export 'src/services/font_loader.dart';
|
||||||
export 'src/services/haptic_feedback.dart';
|
export 'src/services/haptic_feedback.dart';
|
||||||
export 'src/services/hardware_keyboard.dart';
|
|
||||||
export 'src/services/keyboard_key.dart';
|
export 'src/services/keyboard_key.dart';
|
||||||
export 'src/services/keyboard_maps.dart';
|
export 'src/services/keyboard_maps.dart';
|
||||||
export 'src/services/message_codec.dart';
|
export 'src/services/message_codec.dart';
|
||||||
|
@ -14,9 +14,7 @@ import 'package:flutter/scheduler.dart';
|
|||||||
|
|
||||||
import 'asset_bundle.dart';
|
import 'asset_bundle.dart';
|
||||||
import 'binary_messenger.dart';
|
import 'binary_messenger.dart';
|
||||||
import 'hardware_keyboard.dart';
|
|
||||||
import 'message_codec.dart';
|
import 'message_codec.dart';
|
||||||
import 'raw_keyboard.dart';
|
|
||||||
import 'restoration.dart';
|
import 'restoration.dart';
|
||||||
import 'system_channels.dart';
|
import 'system_channels.dart';
|
||||||
|
|
||||||
@ -33,7 +31,6 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
|
|||||||
_instance = this;
|
_instance = this;
|
||||||
_defaultBinaryMessenger = createBinaryMessenger();
|
_defaultBinaryMessenger = createBinaryMessenger();
|
||||||
_restorationManager = createRestorationManager();
|
_restorationManager = createRestorationManager();
|
||||||
_initKeyboard();
|
|
||||||
initLicenses();
|
initLicenses();
|
||||||
SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
|
SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object));
|
||||||
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
|
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
|
||||||
@ -45,23 +42,6 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
|
|||||||
static ServicesBinding? get instance => _instance;
|
static ServicesBinding? get instance => _instance;
|
||||||
static ServicesBinding? _instance;
|
static ServicesBinding? _instance;
|
||||||
|
|
||||||
/// The global singleton instance of [HardwareKeyboard], which can be used to
|
|
||||||
/// query keyboard states.
|
|
||||||
HardwareKeyboard get keyboard => _keyboard;
|
|
||||||
late final HardwareKeyboard _keyboard;
|
|
||||||
|
|
||||||
/// The global singleton instance of [KeyEventManager], which is used
|
|
||||||
/// internally to dispatch key messages.
|
|
||||||
KeyEventManager get keyEventManager => _keyEventManager;
|
|
||||||
late final KeyEventManager _keyEventManager;
|
|
||||||
|
|
||||||
void _initKeyboard() {
|
|
||||||
_keyboard = HardwareKeyboard();
|
|
||||||
_keyEventManager = KeyEventManager(_keyboard, RawKeyboard.instance);
|
|
||||||
window.onKeyData = _keyEventManager.handleKeyData;
|
|
||||||
SystemChannels.keyEvent.setMessageHandler(_keyEventManager.handleRawKeyMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The default instance of [BinaryMessenger].
|
/// The default instance of [BinaryMessenger].
|
||||||
///
|
///
|
||||||
/// This is used to send messages from the application to the platform, and
|
/// This is used to send messages from the application to the platform, and
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
// Copyright 2014 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.
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
import 'hardware_keyboard.dart';
|
|
||||||
|
|
||||||
/// Override the transit mode with which key events are simulated.
|
|
||||||
///
|
|
||||||
/// Setting [debugKeyEventSimulatorTransitModeOverride] is a good way to make
|
|
||||||
/// certain tests simulate the behavior of different type of platforms in terms
|
|
||||||
/// of their extent of support for keyboard API.
|
|
||||||
KeyDataTransitMode? debugKeyEventSimulatorTransitModeOverride;
|
|
||||||
|
|
||||||
/// Returns true if none of the widget library debug variables have been changed.
|
|
||||||
///
|
|
||||||
/// This function is used by the test framework to ensure that debug variables
|
|
||||||
/// haven't been inadvertently changed.
|
|
||||||
///
|
|
||||||
/// See [the services library](services/services-library.html) for a complete list.
|
|
||||||
bool debugAssertAllServicesVarsUnset(String reason) {
|
|
||||||
assert(() {
|
|
||||||
if (debugKeyEventSimulatorTransitModeOverride != null) {
|
|
||||||
throw FlutterError(reason);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
return true;
|
|
||||||
}
|
|
@ -1,941 +0,0 @@
|
|||||||
// Copyright 2014 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.
|
|
||||||
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'binding.dart';
|
|
||||||
import 'keyboard_key.dart';
|
|
||||||
import 'raw_keyboard.dart';
|
|
||||||
|
|
||||||
/// Represents a lock mode of a keyboard, such as [KeyboardLockMode.capsLock].
|
|
||||||
///
|
|
||||||
/// A lock mode locks some of a keyboard's keys into a distinct mode of operation,
|
|
||||||
/// depending on the lock settings selected. The status of the mode is toggled
|
|
||||||
/// with each key down of its corresponding logical key. A [KeyboardLockMode]
|
|
||||||
/// object is used to query whether this mode is enabled on the keyboard.
|
|
||||||
///
|
|
||||||
/// Only a limited number of modes are supported, which are enumerated as
|
|
||||||
/// static members of this class. Manual constructing of this class is
|
|
||||||
/// prohibited.
|
|
||||||
@immutable
|
|
||||||
class KeyboardLockMode {
|
|
||||||
// KeyboardLockMode has a fixed pool of supported keys, enumerated as static
|
|
||||||
// members of this class, therefore constructing is prohibited.
|
|
||||||
const KeyboardLockMode._(this.logicalKey);
|
|
||||||
|
|
||||||
/// The logical key that triggers this lock mode.
|
|
||||||
final LogicalKeyboardKey logicalKey;
|
|
||||||
|
|
||||||
/// Represents the number lock mode on the keyboard.
|
|
||||||
///
|
|
||||||
/// On supporting systems, enabling number lock mode usually allows key
|
|
||||||
/// presses of the number pad to input numbers, instead of acting as up, down,
|
|
||||||
/// left, right, page up, end, etc.
|
|
||||||
static const KeyboardLockMode numLock = KeyboardLockMode._(LogicalKeyboardKey.numLock);
|
|
||||||
|
|
||||||
/// Represents the scrolling lock mode on the keyboard.
|
|
||||||
///
|
|
||||||
/// On supporting systems and applications (such as a spreadsheet), enabling
|
|
||||||
/// scrolling lock mode usually allows key presses of the cursor keys to
|
|
||||||
/// scroll the document instead of the cursor.
|
|
||||||
static const KeyboardLockMode scrollLock = KeyboardLockMode._(LogicalKeyboardKey.scrollLock);
|
|
||||||
|
|
||||||
/// Represents the capital letters lock mode on the keyboard.
|
|
||||||
///
|
|
||||||
/// On supporting systems, enabling capital lock mode allows key presses of
|
|
||||||
/// the letter keys to input uppercase letters instead of lowercase.
|
|
||||||
static const KeyboardLockMode capsLock = KeyboardLockMode._(LogicalKeyboardKey.capsLock);
|
|
||||||
|
|
||||||
static final Map<int, KeyboardLockMode> _knownLockModes = <int, KeyboardLockMode>{
|
|
||||||
numLock.logicalKey.keyId: numLock,
|
|
||||||
scrollLock.logicalKey.keyId: scrollLock,
|
|
||||||
capsLock.logicalKey.keyId: capsLock,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Returns the [KeyboardLockMode] constant from the logical key, or
|
|
||||||
/// null, if not found.
|
|
||||||
static KeyboardLockMode? findLockByLogicalKey(LogicalKeyboardKey logicalKey) => _knownLockModes[logicalKey.keyId];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines the interface for keyboard key events.
|
|
||||||
///
|
|
||||||
/// The [KeyEvent] provides a universal model for key event information from a
|
|
||||||
/// hardware keyboard across platforms.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [HardwareKeyboard] for full introduction to key event model and handling.
|
|
||||||
/// * [KeyDownEvent], a subclass for events representing the user pressing a
|
|
||||||
/// key.
|
|
||||||
/// * [KeyRepeatEvent], a subclass for events representing the user holding a
|
|
||||||
/// key, causing repeated events.
|
|
||||||
/// * [KeyUpEvent], a subclass for events representing the user releasing a
|
|
||||||
/// key.
|
|
||||||
@immutable
|
|
||||||
abstract class KeyEvent with Diagnosticable {
|
|
||||||
/// Create a const KeyEvent by providing each field.
|
|
||||||
const KeyEvent({
|
|
||||||
required this.physicalKey,
|
|
||||||
required this.logicalKey,
|
|
||||||
this.character,
|
|
||||||
required this.timeStamp,
|
|
||||||
this.synthesized = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Returns an object representing the physical location of this key.
|
|
||||||
///
|
|
||||||
/// A [PhysicalKeyboardKey] represents a USB HID code sent from the keyboard,
|
|
||||||
/// ignoring the key map, modifier keys (like SHIFT), and the label on the key.
|
|
||||||
///
|
|
||||||
/// [PhysicalKeyboardKey]s are used to describe and test for keys in a
|
|
||||||
/// particular location. A [PhysicalKeyboardKey] may have a name, but the name
|
|
||||||
/// is a mnemonic ("keyA" is easier to remember than 0x70004), derived from the
|
|
||||||
/// key's effect on a QWERTY keyboard. The name does not represent the key's
|
|
||||||
/// effect whatsoever (a physical "keyA" can be the Q key on an AZERTY
|
|
||||||
/// keyboard.)
|
|
||||||
///
|
|
||||||
/// For instance, if you wanted to make a game where the key to the right of
|
|
||||||
/// the CAPS LOCK key made the player move left, you would be comparing a
|
|
||||||
/// physical key with [PhysicalKeyboardKey.keyA], since that is the key next to
|
|
||||||
/// the CAPS LOCK key on a QWERTY keyboard. This would return the same thing
|
|
||||||
/// even on an AZERTY keyboard where the key next to the CAPS LOCK produces a
|
|
||||||
/// "Q" when pressed.
|
|
||||||
///
|
|
||||||
/// If you want to make your app respond to a key with a particular character
|
|
||||||
/// on it regardless of location of the key, use [KeyEvent.logicalKey] instead.
|
|
||||||
///
|
|
||||||
/// Also, even though physical keys are defined with USB HID codes, their
|
|
||||||
/// values are not necessarily the same HID codes produced by the hardware and
|
|
||||||
/// presented to the driver, because on most platforms Flutter has to map the
|
|
||||||
/// platform representation back to an HID code since the original HID
|
|
||||||
/// code is not provided. USB HID is simply a conveniently well-defined
|
|
||||||
/// standard to list possible keys that a Flutter app can encounter.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [logicalKey] for the non-location specific key generated by this event.
|
|
||||||
/// * [character] for the character generated by this keypress (if any).
|
|
||||||
final PhysicalKeyboardKey physicalKey;
|
|
||||||
|
|
||||||
/// Returns an object representing the logical key that was pressed.
|
|
||||||
///
|
|
||||||
/// {@template flutter.services.KeyEvent.logicalKey}
|
|
||||||
/// This method takes into account the key map and modifier keys (like SHIFT)
|
|
||||||
/// to determine which logical key to return.
|
|
||||||
///
|
|
||||||
/// If you are looking for the character produced by a key event, use
|
|
||||||
/// [KeyEvent.character] instead.
|
|
||||||
///
|
|
||||||
/// If you are collecting text strings, use the [TextField] or
|
|
||||||
/// [CupertinoTextField] widgets, since those automatically handle many of the
|
|
||||||
/// complexities of managing keyboard input, like showing a soft keyboard or
|
|
||||||
/// interacting with an input method editor (IME).
|
|
||||||
/// {@endtemplate}
|
|
||||||
final LogicalKeyboardKey logicalKey;
|
|
||||||
|
|
||||||
/// Returns the Unicode character (grapheme cluster) completed by this
|
|
||||||
/// keystroke, if any.
|
|
||||||
///
|
|
||||||
/// This will only return a character if this keystroke, combined with any
|
|
||||||
/// preceding keystroke(s), generats a character, and only on a "key down"
|
|
||||||
/// event. It will return null if no character has been generated by the
|
|
||||||
/// keystroke (e.g. a "dead" or "combining" key), or if the corresponding key
|
|
||||||
/// is a key without a visual representation, such as a modifier key or a
|
|
||||||
/// control key. It will also return null if this is a "key up" event.
|
|
||||||
///
|
|
||||||
/// This can return multiple Unicode code points, since some characters (more
|
|
||||||
/// accurately referred to as grapheme clusters) are made up of more than one
|
|
||||||
/// code point.
|
|
||||||
///
|
|
||||||
/// The [character] doesn't take into account edits by an input method editor
|
|
||||||
/// (IME), or manage the visibility of the soft keyboard on touch devices. For
|
|
||||||
/// composing text, use the [TextField] or [CupertinoTextField] widgets, since
|
|
||||||
/// those automatically handle many of the complexities of managing keyboard
|
|
||||||
/// input.
|
|
||||||
///
|
|
||||||
/// The [character] is not available on [KeyUpEvent]s.
|
|
||||||
final String? character;
|
|
||||||
|
|
||||||
/// Time of event, relative to an arbitrary start point.
|
|
||||||
///
|
|
||||||
/// All events share the same timeStamp origin.
|
|
||||||
final Duration timeStamp;
|
|
||||||
|
|
||||||
/// Whether this event is synthesized by Flutter to synchronize key states.
|
|
||||||
///
|
|
||||||
/// An non-[synthesized] event is converted from a native event, and a native
|
|
||||||
/// event can only be converted to one non-[synthesized] event. Some properties
|
|
||||||
/// might be changed during the conversion (for example, a native repeat event
|
|
||||||
/// might be converted to a Flutter down event when necessary.)
|
|
||||||
///
|
|
||||||
/// A [synthesized] event is created without a source native event in order to
|
|
||||||
/// synthronize key states. For example, if the native platform shows that a
|
|
||||||
/// shift key that was previously held has been released somehow without the
|
|
||||||
/// key up event dispatched (probably due to loss of focus), a synthesized key
|
|
||||||
/// up event will be added to regularized the event stream.
|
|
||||||
///
|
|
||||||
/// For detailed introduction to the regularized event model, see
|
|
||||||
/// [HardwareKeyboard].
|
|
||||||
///
|
|
||||||
/// Defaults to false.
|
|
||||||
final bool synthesized;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
||||||
super.debugFillProperties(properties);
|
|
||||||
properties.add(DiagnosticsProperty<PhysicalKeyboardKey>('physicalKey', physicalKey));
|
|
||||||
properties.add(DiagnosticsProperty<LogicalKeyboardKey>('logicalKey', logicalKey));
|
|
||||||
properties.add(StringProperty('character', character));
|
|
||||||
properties.add(DiagnosticsProperty<Duration>('timeStamp', timeStamp));
|
|
||||||
properties.add(FlagProperty('synthesized', value: synthesized, ifTrue: 'synthesized'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An event indicating that the user has pressed a key down on the keyboard.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [KeyRepeatEvent], a key event representing the user
|
|
||||||
/// holding a key, causing repeated events.
|
|
||||||
/// * [KeyUpEvent], a key event representing the user
|
|
||||||
/// releasing a key.
|
|
||||||
/// * [HardwareKeyboard], which produces this event.
|
|
||||||
class KeyDownEvent extends KeyEvent {
|
|
||||||
/// Creates a key event that represents the user pressing a key.
|
|
||||||
const KeyDownEvent({
|
|
||||||
required PhysicalKeyboardKey physicalKey,
|
|
||||||
required LogicalKeyboardKey logicalKey,
|
|
||||||
String? character,
|
|
||||||
required Duration timeStamp,
|
|
||||||
bool synthesized = false,
|
|
||||||
}) : super(
|
|
||||||
physicalKey: physicalKey,
|
|
||||||
logicalKey: logicalKey,
|
|
||||||
character: character,
|
|
||||||
timeStamp: timeStamp,
|
|
||||||
synthesized: synthesized,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An event indicating that the user has released a key on the keyboard.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [KeyDownEvent], a key event representing the user
|
|
||||||
/// pressing a key.
|
|
||||||
/// * [KeyRepeatEvent], a key event representing the user
|
|
||||||
/// holding a key, causing repeated events.
|
|
||||||
/// * [HardwareKeyboard], which produces this event.
|
|
||||||
class KeyUpEvent extends KeyEvent {
|
|
||||||
/// Creates a key event that represents the user pressing a key.
|
|
||||||
const KeyUpEvent({
|
|
||||||
required PhysicalKeyboardKey physicalKey,
|
|
||||||
required LogicalKeyboardKey logicalKey,
|
|
||||||
required Duration timeStamp,
|
|
||||||
bool synthesized = false,
|
|
||||||
}) : super(
|
|
||||||
physicalKey: physicalKey,
|
|
||||||
logicalKey: logicalKey,
|
|
||||||
timeStamp: timeStamp,
|
|
||||||
synthesized: synthesized,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An event indicating that the user has been holding a key on the keyboard
|
|
||||||
/// and causing repeated events.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [KeyDownEvent], a key event representing the user
|
|
||||||
/// pressing a key.
|
|
||||||
/// * [KeyUpEvent], a key event representing the user
|
|
||||||
/// releasing a key.
|
|
||||||
/// * [HardwareKeyboard], which produces this event.
|
|
||||||
class KeyRepeatEvent extends KeyEvent {
|
|
||||||
/// Creates a key event that represents the user pressing a key.
|
|
||||||
const KeyRepeatEvent({
|
|
||||||
required PhysicalKeyboardKey physicalKey,
|
|
||||||
required LogicalKeyboardKey logicalKey,
|
|
||||||
String? character,
|
|
||||||
required Duration timeStamp,
|
|
||||||
}) : super(
|
|
||||||
physicalKey: physicalKey,
|
|
||||||
logicalKey: logicalKey,
|
|
||||||
character: character,
|
|
||||||
timeStamp: timeStamp,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The signature for [HardwareKeyboard.addHandler], a callback to to decide whether
|
|
||||||
/// the entire framework handles a key event.
|
|
||||||
typedef KeyEventCallback = bool Function(KeyEvent event);
|
|
||||||
|
|
||||||
/// Manages key events from hardware keyboards.
|
|
||||||
///
|
|
||||||
/// [HardwareKeyboard] manages all key events of the Flutter application from
|
|
||||||
/// hardware keyboards (in contrast to on-screen keyboards). It receives key
|
|
||||||
/// data from the native platform, dispatches key events to registered
|
|
||||||
/// handlers, and records the keyboard state.
|
|
||||||
///
|
|
||||||
/// To stay notified whenever keys are pressed, held, or released, add a
|
|
||||||
/// handler with [addHandler]. To only be notified when a specific part of the
|
|
||||||
/// app is focused, use a [Focus] widget's `onFocusChanged` attribute instead
|
|
||||||
/// of [addHandler]. Handlers should be removed with [removeHandler] when
|
|
||||||
/// notification is no longer necessary, or when the handler is being disposed.
|
|
||||||
///
|
|
||||||
/// To query whether a key is being held, or a lock mode is enabled, use
|
|
||||||
/// [physicalKeysPressed], [logicalKeysPressed], or [lockModesEnabled].
|
|
||||||
/// These states will have been updated with the event when used during a key
|
|
||||||
/// event handler.
|
|
||||||
///
|
|
||||||
/// The singleton [HardwareKeyboard] instance is held by the [ServicesBinding]
|
|
||||||
/// as [ServicesBinding.keyboard], and can be conveniently accessed using the
|
|
||||||
/// [HardwareKeyboard.instance] static accessor.
|
|
||||||
///
|
|
||||||
/// ## Event model
|
|
||||||
///
|
|
||||||
/// Flutter uses a universal event model ([KeyEvent]) and key options
|
|
||||||
/// ([LogicalKeyboardKey] and [PhysicalKeyboardKey]) regardless of the native
|
|
||||||
/// platform, while preserving platform-specific features as much as
|
|
||||||
/// possible.
|
|
||||||
///
|
|
||||||
/// [HardwareKeyboard] guarantees that the key model is "regularized": The key
|
|
||||||
/// event stream consists of "key tap sequences", where a key tap sequence is
|
|
||||||
/// defined as one [KeyDownEvent], zero or more [KeyRepeatEvent]s, and one
|
|
||||||
/// [KeyUpEvent] in order, all with the same physical key and logical key.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// * Tap and hold key A, US layout:
|
|
||||||
/// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "a")
|
|
||||||
/// * KeyRepeatEvent(physicalKey: keyA, logicalKey: keyA, character: "a")
|
|
||||||
/// * KeyUpEvent(physicalKey: keyA, logicalKey: keyA)
|
|
||||||
/// * Press ShiftLeft, tap key A, then release ShiftLeft, US layout:
|
|
||||||
/// * KeyDownEvent(physicalKey: shiftLeft, logicalKey: shiftLeft)
|
|
||||||
/// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "A")
|
|
||||||
/// * KeyRepeatEvent(physicalKey: keyA, logicalKey: keyA, character: "A")
|
|
||||||
/// * KeyUpEvent(physicalKey: keyA, logicalKey: keyA)
|
|
||||||
/// * KeyUpEvent(physicalKey: shiftLeft, logicalKey: shiftLeft)
|
|
||||||
/// * Tap key Q, French layout:
|
|
||||||
/// * KeyDownEvent(physicalKey: keyA, logicalKey: keyQ, character: "q")
|
|
||||||
/// * KeyUpEvent(physicalKey: keyA, logicalKey: keyQ)
|
|
||||||
/// * Tap CapsLock:
|
|
||||||
/// * KeyDownEvent(physicalKey: capsLock, logicalKey: capsLock)
|
|
||||||
/// * KeyUpEvent(physicalKey: capsLock, logicalKey: capsLock)
|
|
||||||
///
|
|
||||||
/// When the Flutter application starts, all keys are released, and all lock
|
|
||||||
/// modes are disabled. Upon key events, [HardwareKeyboard] will update its
|
|
||||||
/// states, then dispatch callbacks: [KeyDownEvent]s and [KeyUpEvent]s set
|
|
||||||
/// or reset the pressing state, while [KeyDownEvent]s also toggle lock modes.
|
|
||||||
///
|
|
||||||
/// Flutter will try to synchronize with the ground truth of keyboard states
|
|
||||||
/// using synthesized events ([KeyEvent.synthesized]), subject to the
|
|
||||||
/// availability of the platform. The desynchronization can be caused by
|
|
||||||
/// non-empty initial state or a change in the focused window or application.
|
|
||||||
/// For example, if CapsLock is enabled when the application starts, then
|
|
||||||
/// immediately before the first key event, a synthesized [KeyDownEvent] and
|
|
||||||
/// [KeyUpEvent] of CapsLock will be dispatched.
|
|
||||||
///
|
|
||||||
/// The resulting event stream does not map one-to-one to the native key event
|
|
||||||
/// stream. Some native events might be skipped, while some events might be
|
|
||||||
/// synthesized and do not correspond to native events. Synthesized events will
|
|
||||||
/// be indicated by [KeyEvent.synthesized].
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// * Flutter starts with CapsLock on, the first press of keyA:
|
|
||||||
/// * KeyDownEvent(physicalKey: capsLock, logicalKey: capsLock, synthesized: true)
|
|
||||||
/// * KeyUpEvent(physicalKey: capsLock, logicalKey: capsLock, synthesized: true)
|
|
||||||
/// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "a")
|
|
||||||
/// * While holding ShiftLeft, lose window focus, release shiftLeft, then focus
|
|
||||||
/// back and press keyA:
|
|
||||||
/// * KeyUpEvent(physicalKey: shiftLeft, logicalKey: shiftLeft, synthesized: true)
|
|
||||||
/// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "a")
|
|
||||||
///
|
|
||||||
/// Flutter does not distinguish between multiple keyboards. Flutter will
|
|
||||||
/// process all events as if they come from a single keyboard, and try to
|
|
||||||
/// resolve any conflicts and provide a regularized key event stream, which
|
|
||||||
/// can deviate from the ground truth.
|
|
||||||
///
|
|
||||||
/// ## Compared to [RawKeyboard]
|
|
||||||
///
|
|
||||||
/// [RawKeyboard] is the legacy API, and will be deprecated and removed in the
|
|
||||||
/// future. It is recommended to always use [HardwareKeyboard] and [KeyEvent]
|
|
||||||
/// APIs (such as [FocusNode.onKeyEvent]) to handle key events.
|
|
||||||
///
|
|
||||||
/// Behavior-wise, [RawKeyboard] provides a less unified, less regular
|
|
||||||
/// event model than [HardwareKeyboard]. For example:
|
|
||||||
///
|
|
||||||
/// * Down events might not be matched with an up event, and vice versa (the
|
|
||||||
/// set of pressed keys is silently updated).
|
|
||||||
/// * The logical key of the down event might not be the same as that of the up
|
|
||||||
/// event.
|
|
||||||
/// * Down events and repeat events are not easily distinguishable (must be
|
|
||||||
/// tracked manually).
|
|
||||||
/// * Lock modes (such as CapsLock) only have their "enabled" state recorded.
|
|
||||||
/// There's no way to acquire their pressing state.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [KeyDownEvent], [KeyRepeatEvent], and [KeyUpEvent], the classes used to
|
|
||||||
/// describe specific key events.
|
|
||||||
/// * [instance], the singleton instance of this class.
|
|
||||||
/// * [RawKeyboard], the legacy API that dispatches key events containing raw
|
|
||||||
/// system data.
|
|
||||||
class HardwareKeyboard {
|
|
||||||
/// Provides convenient access to the current [HardwareKeyboard] singleton from
|
|
||||||
/// the [ServicesBinding] instance.
|
|
||||||
static HardwareKeyboard get instance => ServicesBinding.instance!.keyboard;
|
|
||||||
|
|
||||||
final Map<PhysicalKeyboardKey, LogicalKeyboardKey> _pressedKeys = <PhysicalKeyboardKey, LogicalKeyboardKey>{};
|
|
||||||
|
|
||||||
/// The set of [PhysicalKeyboardKey]s that are pressed.
|
|
||||||
///
|
|
||||||
/// If called from a key event handler, the result will already include the effect
|
|
||||||
/// of the event.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [logicalKeysPressed], which tells if a logical key is being pressed.
|
|
||||||
Set<PhysicalKeyboardKey> get physicalKeysPressed => _pressedKeys.keys.toSet();
|
|
||||||
|
|
||||||
/// The set of [LogicalKeyboardKey]s that are pressed.
|
|
||||||
///
|
|
||||||
/// If called from a key event handler, the result will already include the effect
|
|
||||||
/// of the event.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [physicalKeysPressed], which tells if a physical key is being pressed.
|
|
||||||
Set<LogicalKeyboardKey> get logicalKeysPressed => _pressedKeys.values.toSet();
|
|
||||||
|
|
||||||
/// Returns the logical key that corresponds to the given pressed physical key.
|
|
||||||
///
|
|
||||||
/// Returns null if the physical key is not currently pressed.
|
|
||||||
LogicalKeyboardKey? lookUpLayout(PhysicalKeyboardKey physicalKey) => _pressedKeys[physicalKey];
|
|
||||||
|
|
||||||
final Set<KeyboardLockMode> _lockModes = <KeyboardLockMode>{};
|
|
||||||
/// The set of [KeyboardLockMode] that are enabled.
|
|
||||||
///
|
|
||||||
/// Lock keys, such as CapsLock, are logical keys that toggle their
|
|
||||||
/// respective boolean states on key down events. Such flags are usually used
|
|
||||||
/// as modifier to other keys or events.
|
|
||||||
///
|
|
||||||
/// If called from a key event handler, the result will already include the effect
|
|
||||||
/// of the event.
|
|
||||||
Set<KeyboardLockMode> get lockModesEnabled => _lockModes;
|
|
||||||
|
|
||||||
void _assertEventIsRegular(KeyEvent event) {
|
|
||||||
assert(() {
|
|
||||||
const String common = 'If this occurs in real application, please report this '
|
|
||||||
'bug to Flutter. If this occurs in unit tests, please ensure that '
|
|
||||||
"simulated events follow Flutter's event model as documented in "
|
|
||||||
'`HardwareKeyboard`. This was the event: ';
|
|
||||||
if (event is KeyDownEvent) {
|
|
||||||
assert(!_pressedKeys.containsKey(event.physicalKey),
|
|
||||||
'A ${event.runtimeType} is dispatched, but the state shows that the physical '
|
|
||||||
'key is already pressed. $common$event');
|
|
||||||
} else if (event is KeyRepeatEvent || event is KeyUpEvent) {
|
|
||||||
assert(_pressedKeys.containsKey(event.physicalKey),
|
|
||||||
'A ${event.runtimeType} is dispatched, but the state shows that the physical '
|
|
||||||
'key is not pressed. $common$event');
|
|
||||||
assert(_pressedKeys[event.physicalKey] == event.logicalKey,
|
|
||||||
'A ${event.runtimeType} is dispatched, but the state shows that the physical '
|
|
||||||
'key is pressed on a different logical key. $common$event '
|
|
||||||
'and the recorded logical key ${_pressedKeys[event.physicalKey]}');
|
|
||||||
} else {
|
|
||||||
assert(false, 'Unexpected key event class ${event.runtimeType}');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<KeyEventCallback> _handlers = <KeyEventCallback>[];
|
|
||||||
bool _duringDispatch = false;
|
|
||||||
List<KeyEventCallback>? _modifiedHandlers;
|
|
||||||
|
|
||||||
/// Register a listener that is called every time a hardware key event
|
|
||||||
/// occurs.
|
|
||||||
///
|
|
||||||
/// All registered handlers will be invoked in order regardless of
|
|
||||||
/// their return value. The return value indicates whether Flutter
|
|
||||||
/// "handles" the event. If any handler returns true, the event
|
|
||||||
/// will not be propagated to other native components in the add-to-app
|
|
||||||
/// scenario.
|
|
||||||
///
|
|
||||||
/// If an object added a handler, it must remove the handler before it is
|
|
||||||
/// disposed.
|
|
||||||
///
|
|
||||||
/// If used during event dispatching, the addition will not take effect
|
|
||||||
/// until after the dispatching.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [removeHandler], which removes the handler.
|
|
||||||
void addHandler(KeyEventCallback handler) {
|
|
||||||
if (_duringDispatch) {
|
|
||||||
_modifiedHandlers ??= <KeyEventCallback>[..._handlers];
|
|
||||||
_modifiedHandlers!.add(handler);
|
|
||||||
} else {
|
|
||||||
_handlers.add(handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stop calling the given listener every time a hardware key event
|
|
||||||
/// occurs.
|
|
||||||
///
|
|
||||||
/// The `handler` argument must be [identical] to the one used in
|
|
||||||
/// [addHandler]. If multiple exist, the first one will be removed.
|
|
||||||
/// If none is found, then this method is a no-op.
|
|
||||||
///
|
|
||||||
/// If used during event dispatching, the removal will not take effect
|
|
||||||
/// until after the event has been dispatched.
|
|
||||||
void removeHandler(KeyEventCallback handler) {
|
|
||||||
if (_duringDispatch) {
|
|
||||||
_modifiedHandlers ??= <KeyEventCallback>[..._handlers];
|
|
||||||
_modifiedHandlers!.remove(handler);
|
|
||||||
} else {
|
|
||||||
_handlers.remove(handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _dispatchKeyEvent(KeyEvent event) {
|
|
||||||
// This dispatching could have used the same algorithm as [ChangeNotifier],
|
|
||||||
// but since 1) it shouldn't be necessary to support reentrantly
|
|
||||||
// dispatching, 2) there shouldn't be many handlers (most apps should use
|
|
||||||
// only 1, this function just uses a simpler algorithm.
|
|
||||||
assert(!_duringDispatch, 'Nested keyboard dispatching is not supported');
|
|
||||||
_duringDispatch = true;
|
|
||||||
bool handled = false;
|
|
||||||
for (final KeyEventCallback handler in _handlers) {
|
|
||||||
try {
|
|
||||||
final bool thisResult = handler(event);
|
|
||||||
handled = handled || thisResult;
|
|
||||||
} catch (exception, stack) {
|
|
||||||
FlutterError.reportError(FlutterErrorDetails(
|
|
||||||
exception: exception,
|
|
||||||
stack: stack,
|
|
||||||
library: 'services library',
|
|
||||||
context: ErrorDescription('while dispatching notifications for $runtimeType'),
|
|
||||||
informationCollector: () sync* {
|
|
||||||
yield DiagnosticsProperty<HardwareKeyboard>(
|
|
||||||
'The $runtimeType sending notification was',
|
|
||||||
this,
|
|
||||||
style: DiagnosticsTreeStyle.errorProperty,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_duringDispatch = false;
|
|
||||||
if (_modifiedHandlers != null) {
|
|
||||||
_handlers = _modifiedHandlers!;
|
|
||||||
_modifiedHandlers = null;
|
|
||||||
}
|
|
||||||
return handled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Process a new [KeyEvent] by recording the state changes and dispatching
|
|
||||||
/// to handlers.
|
|
||||||
bool handleKeyEvent(KeyEvent event) {
|
|
||||||
_assertEventIsRegular(event);
|
|
||||||
final PhysicalKeyboardKey physicalKey = event.physicalKey;
|
|
||||||
final LogicalKeyboardKey logicalKey = event.logicalKey;
|
|
||||||
if (event is KeyDownEvent) {
|
|
||||||
_pressedKeys[physicalKey] = logicalKey;
|
|
||||||
final KeyboardLockMode? lockMode = KeyboardLockMode.findLockByLogicalKey(event.logicalKey);
|
|
||||||
if (lockMode != null) {
|
|
||||||
if (_lockModes.contains(lockMode)) {
|
|
||||||
_lockModes.remove(lockMode);
|
|
||||||
} else {
|
|
||||||
_lockModes.add(lockMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (event is KeyUpEvent) {
|
|
||||||
_pressedKeys.remove(physicalKey);
|
|
||||||
} else if (event is KeyRepeatEvent) {
|
|
||||||
// Empty
|
|
||||||
}
|
|
||||||
|
|
||||||
return _dispatchKeyEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear all keyboard states and additional handlers.
|
|
||||||
///
|
|
||||||
/// All handlers are removed except for the first one, which is added by
|
|
||||||
/// [ServicesBinding].
|
|
||||||
///
|
|
||||||
/// This is used by the testing framework to make sure that tests are hermetic.
|
|
||||||
@visibleForTesting
|
|
||||||
void clearState() {
|
|
||||||
_pressedKeys.clear();
|
|
||||||
_lockModes.clear();
|
|
||||||
_handlers.clear();
|
|
||||||
assert(_modifiedHandlers == null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The mode in which information of key messages is delivered.
|
|
||||||
///
|
|
||||||
/// Different platforms use different methods, classes, and models to inform the
|
|
||||||
/// framework of native key events, which is called "transit mode".
|
|
||||||
///
|
|
||||||
/// The framework must determine which transit mode the current platform
|
|
||||||
/// implements and behave accordingly (such as transforming and synthesizing
|
|
||||||
/// events if necessary). Unit tests related to keyboard might also want to
|
|
||||||
/// simulate platform of each transit mode.
|
|
||||||
///
|
|
||||||
/// The transit mode of the current platform is inferred by [KeyEventManager] at
|
|
||||||
/// the start of the application.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [KeyEventManager], which infers the transit mode of the current platform
|
|
||||||
/// and guides how key messages are dispatched.
|
|
||||||
/// * [debugKeyEventSimulatorTransitModeOverride], overrides the transit mode
|
|
||||||
/// used to simulate key events.
|
|
||||||
/// * [KeySimulatorTransitModeVariant], an easier way to set
|
|
||||||
/// [debugKeyEventSimulatorTransitModeOverride] in widget tests.
|
|
||||||
enum KeyDataTransitMode {
|
|
||||||
/// Key event information is delivered as raw key data.
|
|
||||||
///
|
|
||||||
/// Raw key data is platform's native key event information sent in JSON
|
|
||||||
/// through a method channel, which is then interpreted as a platform subclass
|
|
||||||
/// of [RawKeyEventData].
|
|
||||||
///
|
|
||||||
/// If the current transit mode is [rawKeyData], the raw key data is converted
|
|
||||||
/// to both [KeyMessage.events] and [KeyMessage.rawEvent].
|
|
||||||
rawKeyData,
|
|
||||||
|
|
||||||
/// Key event information is delivered as converted key data, followed
|
|
||||||
/// by raw key data.
|
|
||||||
///
|
|
||||||
/// Key data ([ui.KeyData]) is a standardized event stream converted from
|
|
||||||
/// platform's native key event information, sent through the embedder
|
|
||||||
/// API. Its event model is described in [HardwareKeyboard].
|
|
||||||
///
|
|
||||||
/// Raw key data is platform's native key event information sent in JSON
|
|
||||||
/// through a method channel. It is interpreted by subclasses of
|
|
||||||
/// [RawKeyEventData].
|
|
||||||
///
|
|
||||||
/// If the current transit mode is [rawKeyData], the key data is converted to
|
|
||||||
/// [KeyMessage.events], and the raw key data is converted to
|
|
||||||
/// [KeyMessage.rawEvent].
|
|
||||||
keyDataThenRawKeyData,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The assumbled information corresponding to a native key message.
|
|
||||||
///
|
|
||||||
/// While Flutter's [KeyEvent]s are created from key messages from the native
|
|
||||||
/// platform, every native message might result in multiple [KeyEvent]s. For
|
|
||||||
/// example, this might happen in order to synthesize missed modifier key
|
|
||||||
/// presses or releases.
|
|
||||||
/// A [KeyMessage] bundles all information related to a native key message
|
|
||||||
/// together for the convenience of propagation on the [FocusNode] tree.
|
|
||||||
///
|
|
||||||
/// When dispatched to handlers or listeners, or propagated through the
|
|
||||||
/// [FocusNode] tree, all handlers or listeners belonging to a node are
|
|
||||||
/// executed regardless of their [KeyEventResult], and all results are combined
|
|
||||||
/// into the result of the node using [combineKeyEventResults].
|
|
||||||
///
|
|
||||||
/// In very rare cases, a native key message might not result in a [KeyMessage].
|
|
||||||
/// For example, key messages for Fn key are ignored on macOS for the
|
|
||||||
/// convenience of cross-platform code.
|
|
||||||
@immutable
|
|
||||||
class KeyMessage {
|
|
||||||
/// Create a [KeyMessage] by providing all information.
|
|
||||||
///
|
|
||||||
/// The [events] might be empty.
|
|
||||||
const KeyMessage(this.events, this.rawEvent);
|
|
||||||
|
|
||||||
/// The list of [KeyEvent]s converted from the native key message.
|
|
||||||
///
|
|
||||||
/// A native key message is converted into multiple [KeyEvent]s in a regular
|
|
||||||
/// event model. The [events] might contain zero or any number of
|
|
||||||
/// [KeyEvent]s.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [HardwareKeyboard], which describes the regular event model.
|
|
||||||
/// * [HardwareKeyboard.addHandler], [KeyboardListener], [Focus.onKeyEvent],
|
|
||||||
/// where [KeyEvent]s are commonly used.
|
|
||||||
final List<KeyEvent> events;
|
|
||||||
|
|
||||||
/// The native key message in the form of a raw key event.
|
|
||||||
///
|
|
||||||
/// A native key message is sent to the framework in JSON and represented
|
|
||||||
/// in a platform-specific form as [RawKeyEventData] and a platform-neutral
|
|
||||||
/// form as [RawKeyEvent]. Their stream is not as regular as [KeyEvent]'s,
|
|
||||||
/// but keeps as much native information and structure as possible.
|
|
||||||
///
|
|
||||||
/// The [rawEvent] will be deprecated in the future.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [RawKeyboard.addListener], [RawKeyboardListener], [Focus.onKey],
|
|
||||||
/// where [RawKeyEvent]s are commonly used.
|
|
||||||
final RawKeyEvent rawEvent;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'KeyMessage($events)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The signature for [KeyEventManager.keyMessageHandler].
|
|
||||||
///
|
|
||||||
/// A [KeyMessageHandler] processes a [KeyMessage] and returns whether
|
|
||||||
/// the message is considered handled. Handled messages should not be
|
|
||||||
/// propagated to other native components.
|
|
||||||
typedef KeyMessageHandler = bool Function(KeyMessage message);
|
|
||||||
|
|
||||||
/// A singleton class that processes key messages from the platform and
|
|
||||||
/// dispatches converted messages accordingly.
|
|
||||||
///
|
|
||||||
/// [KeyEventManager] receives platform key messages by [handleKeyData]
|
|
||||||
/// and [handleRawKeyMessage], sends converted events to [HardwareKeyboard]
|
|
||||||
/// and [RawKeyboard] for recording keeping, and then dispatches the [KeyMessage]
|
|
||||||
/// to [keyMessageHandler], the global message handler.
|
|
||||||
///
|
|
||||||
/// [KeyEventManager] also resolves cross-platform compatibility of keyboard
|
|
||||||
/// implementations. Legacy platforms might have not implemented the new key
|
|
||||||
/// data API and only send raw key data on each key message. [KeyEventManager]
|
|
||||||
/// recognize platform types as [KeyDataTransitMode] and dispatches events in
|
|
||||||
/// different ways accordingly.
|
|
||||||
///
|
|
||||||
/// [KeyEventManager] is typically created, owned, and invoked by
|
|
||||||
/// [ServicesBinding].
|
|
||||||
class KeyEventManager {
|
|
||||||
/// Create an instance.
|
|
||||||
///
|
|
||||||
/// This is typically only called by [ServicesBinding].
|
|
||||||
KeyEventManager(this._hardwareKeyboard, this._rawKeyboard);
|
|
||||||
|
|
||||||
/// The global handler for all hardware key messages of Flutter.
|
|
||||||
///
|
|
||||||
/// Key messages received from the platform are first sent to [RawKeyboard]'s
|
|
||||||
/// listeners and [HardwareKeyboard]'s handlers, then sent to
|
|
||||||
/// [keyMessageHandler], regardless of the results of [HardwareKeyboard]'s
|
|
||||||
/// handlers. The result from the handlers and [keyMessageHandler] are
|
|
||||||
/// combined and returned to the platform. The handler result is explained below.
|
|
||||||
///
|
|
||||||
/// This handler is normally set by the [FocusManager] so that it can control
|
|
||||||
/// the key event propagation to focused widgets. Applications that use the
|
|
||||||
/// focus system (see [Focus] and [FocusManager]) to receive key events
|
|
||||||
/// do not need to set this field.
|
|
||||||
///
|
|
||||||
/// ## Handler result
|
|
||||||
///
|
|
||||||
/// Key messages on the platform are given to Flutter to be handled by the
|
|
||||||
/// engine. If they are not handled, then the platform will continue to
|
|
||||||
/// distribute the keys (i.e. propagate them) to other (possibly non-Flutter)
|
|
||||||
/// components in the application. The return value from this handler tells
|
|
||||||
/// the platform to either stop propagation (by returning true: "event
|
|
||||||
/// handled"), or pass the event on to other controls (false: "event not
|
|
||||||
/// handled"). Some platforms might trigger special alerts if the event
|
|
||||||
/// is not handled by other controls either (such as the "bonk" noise on
|
|
||||||
/// macOS).
|
|
||||||
///
|
|
||||||
/// If you are not using the [FocusManager] to manage focus, set this
|
|
||||||
/// attribute to a [KeyMessageHandler] that returns true if the propagation
|
|
||||||
/// on the platform should not be continued. Otherwise, key events will be
|
|
||||||
/// assumed to not have been handled by Flutter, and will also be sent to
|
|
||||||
/// other (possibly non-Flutter) controls in the application.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [Focus.onKeyEvent], a [Focus] callback attribute that will be given
|
|
||||||
/// key events distributed by the [FocusManager] based on the current
|
|
||||||
/// primary focus.
|
|
||||||
/// * [HardwareKeyboard.addHandler], which accepts multiple handlers to
|
|
||||||
/// control the handler result but only accepts [KeyEvent] instead of
|
|
||||||
/// [KeyMessage].
|
|
||||||
KeyMessageHandler? keyMessageHandler;
|
|
||||||
|
|
||||||
final HardwareKeyboard _hardwareKeyboard;
|
|
||||||
final RawKeyboard _rawKeyboard;
|
|
||||||
|
|
||||||
// The [KeyDataTransitMode] of the current platform.
|
|
||||||
//
|
|
||||||
// The `_transitMode` is guaranteed to be non-null during key event callbacks.
|
|
||||||
//
|
|
||||||
// The `_transitMode` is null before the first event, after which it is inferred
|
|
||||||
// and will not change throughout the lifecycle of the application.
|
|
||||||
//
|
|
||||||
// The `_transitMode` can be reset to null in tests using [clearState].
|
|
||||||
KeyDataTransitMode? _transitMode;
|
|
||||||
|
|
||||||
// The accumulated [KeyEvent]s since the last time the message is dispatched.
|
|
||||||
//
|
|
||||||
// If _transitMode is [KeyDataTransitMode.keyDataThenRawKeyData], then [KeyEvent]s
|
|
||||||
// are directly received from [handleKeyData]. If _transitMode is
|
|
||||||
// [KeyDataTransitMode.rawKeyData], then [KeyEvent]s are converted from the raw
|
|
||||||
// key data of [handleRawKeyMessage]. Either way, the accumulated key
|
|
||||||
// events are only dispatched along with the next [KeyMessage] when a
|
|
||||||
// dispatchable [RawKeyEvent] is available.
|
|
||||||
final List<KeyEvent> _keyEventsSinceLastMessage = <KeyEvent>[];
|
|
||||||
|
|
||||||
/// Dispatch a key data to global and leaf listeners.
|
|
||||||
///
|
|
||||||
/// This method is the handler to the global `onKeyData` API.
|
|
||||||
bool handleKeyData(ui.KeyData data) {
|
|
||||||
_transitMode ??= KeyDataTransitMode.keyDataThenRawKeyData;
|
|
||||||
switch (_transitMode!) {
|
|
||||||
case KeyDataTransitMode.rawKeyData:
|
|
||||||
assert(false, 'Should never encounter KeyData when transitMode is rawKeyData.');
|
|
||||||
return false;
|
|
||||||
case KeyDataTransitMode.keyDataThenRawKeyData:
|
|
||||||
// Postpone key event dispatching until the handleRawKeyMessage.
|
|
||||||
_keyEventsSinceLastMessage.add(_eventFromData(data));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles a raw key message.
|
|
||||||
///
|
|
||||||
/// This method is the handler to [SystemChannels.keyEvent], processing
|
|
||||||
/// the JSON form of the native key message and returns the responds for the
|
|
||||||
/// channel.
|
|
||||||
Future<Map<String, dynamic>> handleRawKeyMessage(dynamic message) async {
|
|
||||||
if (_transitMode == null) {
|
|
||||||
_transitMode = KeyDataTransitMode.rawKeyData;
|
|
||||||
// Convert raw events using a listener so that conversion only occurs if
|
|
||||||
// the raw event should be dispatched.
|
|
||||||
_rawKeyboard.addListener(_convertRawEventAndStore);
|
|
||||||
}
|
|
||||||
final RawKeyEvent rawEvent = RawKeyEvent.fromMessage(message as Map<String, dynamic>);
|
|
||||||
// The following `handleRawKeyEvent` will call `_convertRawEventAndStore`
|
|
||||||
// unless the event is not dispatched.
|
|
||||||
bool handled = _rawKeyboard.handleRawKeyEvent(rawEvent);
|
|
||||||
|
|
||||||
for (final KeyEvent event in _keyEventsSinceLastMessage) {
|
|
||||||
handled = _hardwareKeyboard.handleKeyEvent(event) || handled;
|
|
||||||
}
|
|
||||||
if (_transitMode == KeyDataTransitMode.rawKeyData) {
|
|
||||||
assert(setEquals(_rawKeyboard.physicalKeysPressed, _hardwareKeyboard.physicalKeysPressed),
|
|
||||||
'RawKeyboard reported ${_rawKeyboard.physicalKeysPressed}, '
|
|
||||||
'while HardwareKeyboard reported ${_hardwareKeyboard.physicalKeysPressed}');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyMessageHandler != null) {
|
|
||||||
handled = keyMessageHandler!(KeyMessage(_keyEventsSinceLastMessage, rawEvent)) || handled;
|
|
||||||
}
|
|
||||||
_keyEventsSinceLastMessage.clear();
|
|
||||||
|
|
||||||
return <String, dynamic>{ 'handled': handled };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the raw event to key events, including synthesizing events for
|
|
||||||
// modifiers, and store the key events in `_keyEventsSinceLastMessage`.
|
|
||||||
//
|
|
||||||
// This is only called when `_transitMode` is `rawKeyEvent` and if the raw
|
|
||||||
// event should be dispatched.
|
|
||||||
void _convertRawEventAndStore(RawKeyEvent rawEvent) {
|
|
||||||
final PhysicalKeyboardKey physicalKey = rawEvent.physicalKey;
|
|
||||||
final LogicalKeyboardKey logicalKey = rawEvent.logicalKey;
|
|
||||||
final Set<PhysicalKeyboardKey> physicalKeysPressed = _hardwareKeyboard.physicalKeysPressed;
|
|
||||||
final KeyEvent? mainEvent;
|
|
||||||
final LogicalKeyboardKey? recordedLogicalMain = _hardwareKeyboard.lookUpLayout(physicalKey);
|
|
||||||
final Duration timeStamp = ServicesBinding.instance!.currentSystemFrameTimeStamp;
|
|
||||||
final String? character = rawEvent.character == '' ? null : rawEvent.character;
|
|
||||||
if (rawEvent is RawKeyDownEvent) {
|
|
||||||
if (recordedLogicalMain == null) {
|
|
||||||
mainEvent = KeyDownEvent(
|
|
||||||
physicalKey: physicalKey,
|
|
||||||
logicalKey: logicalKey,
|
|
||||||
character: character,
|
|
||||||
timeStamp: timeStamp,
|
|
||||||
);
|
|
||||||
physicalKeysPressed.add(physicalKey);
|
|
||||||
} else {
|
|
||||||
assert(physicalKeysPressed.contains(physicalKey));
|
|
||||||
mainEvent = KeyRepeatEvent(
|
|
||||||
physicalKey: physicalKey,
|
|
||||||
logicalKey: recordedLogicalMain,
|
|
||||||
character: character,
|
|
||||||
timeStamp: timeStamp,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
assert(rawEvent is RawKeyUpEvent, 'Unexpected subclass of RawKeyEvent: ${rawEvent.runtimeType}');
|
|
||||||
if (recordedLogicalMain == null) {
|
|
||||||
mainEvent = null;
|
|
||||||
} else {
|
|
||||||
mainEvent = KeyUpEvent(
|
|
||||||
logicalKey: recordedLogicalMain,
|
|
||||||
physicalKey: physicalKey,
|
|
||||||
timeStamp: timeStamp,
|
|
||||||
);
|
|
||||||
physicalKeysPressed.remove(physicalKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (final PhysicalKeyboardKey key in physicalKeysPressed.difference(_rawKeyboard.physicalKeysPressed)) {
|
|
||||||
_keyEventsSinceLastMessage.add(KeyUpEvent(
|
|
||||||
physicalKey: key,
|
|
||||||
logicalKey: _hardwareKeyboard.lookUpLayout(physicalKey)!,
|
|
||||||
timeStamp: timeStamp,
|
|
||||||
synthesized: true,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
for (final PhysicalKeyboardKey key in _rawKeyboard.physicalKeysPressed.difference(physicalKeysPressed)) {
|
|
||||||
_keyEventsSinceLastMessage.add(KeyDownEvent(
|
|
||||||
physicalKey: key,
|
|
||||||
logicalKey: _rawKeyboard.lookUpLayout(physicalKey)!,
|
|
||||||
timeStamp: timeStamp,
|
|
||||||
synthesized: true,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if (mainEvent != null)
|
|
||||||
_keyEventsSinceLastMessage.add(mainEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reset the inferred platform transit mode and related states.
|
|
||||||
///
|
|
||||||
/// This method is only used in unit tests. In release mode, this is a no-op.
|
|
||||||
@visibleForTesting
|
|
||||||
void clearState() {
|
|
||||||
assert(() {
|
|
||||||
_transitMode = null;
|
|
||||||
_rawKeyboard.removeListener(_convertRawEventAndStore);
|
|
||||||
_keyEventsSinceLastMessage.clear();
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
}
|
|
||||||
|
|
||||||
static KeyEvent _eventFromData(ui.KeyData keyData) {
|
|
||||||
final PhysicalKeyboardKey physicalKey =
|
|
||||||
PhysicalKeyboardKey.findKeyByCode(keyData.physical) ??
|
|
||||||
PhysicalKeyboardKey(keyData.physical);
|
|
||||||
final LogicalKeyboardKey logicalKey =
|
|
||||||
LogicalKeyboardKey.findKeyByKeyId(keyData.logical) ??
|
|
||||||
LogicalKeyboardKey(keyData.logical);
|
|
||||||
final Duration timeStamp = keyData.timeStamp;
|
|
||||||
switch (keyData.type) {
|
|
||||||
case ui.KeyEventType.down:
|
|
||||||
return KeyDownEvent(
|
|
||||||
physicalKey: physicalKey,
|
|
||||||
logicalKey: logicalKey,
|
|
||||||
timeStamp: timeStamp,
|
|
||||||
character: keyData.character,
|
|
||||||
synthesized: keyData.synthesized,
|
|
||||||
);
|
|
||||||
case ui.KeyEventType.up:
|
|
||||||
assert(keyData.character == null);
|
|
||||||
return KeyUpEvent(
|
|
||||||
physicalKey: physicalKey,
|
|
||||||
logicalKey: logicalKey,
|
|
||||||
timeStamp: timeStamp,
|
|
||||||
synthesized: keyData.synthesized,
|
|
||||||
);
|
|
||||||
case ui.KeyEventType.repeat:
|
|
||||||
return KeyRepeatEvent(
|
|
||||||
physicalKey: physicalKey,
|
|
||||||
logicalKey: logicalKey,
|
|
||||||
timeStamp: timeStamp,
|
|
||||||
character: keyData.character,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -2624,9 +2624,6 @@ class LogicalKeyboardKey extends KeyboardKey {
|
|||||||
/// See the function [RawKeyEvent.logicalKey] for more information.
|
/// See the function [RawKeyEvent.logicalKey] for more information.
|
||||||
static const LogicalKeyboardKey gameButtonZ = LogicalKeyboardKey(0x0020000031f);
|
static const LogicalKeyboardKey gameButtonZ = LogicalKeyboardKey(0x0020000031f);
|
||||||
|
|
||||||
/// A list of all predefined constant [LogicalKeyboardKey]s.
|
|
||||||
static Iterable<LogicalKeyboardKey> get knownLogicalKeys => _knownLogicalKeys.values;
|
|
||||||
|
|
||||||
// A list of all predefined constant LogicalKeyboardKeys so they can be
|
// A list of all predefined constant LogicalKeyboardKeys so they can be
|
||||||
// searched.
|
// searched.
|
||||||
static const Map<int, LogicalKeyboardKey> _knownLogicalKeys = <int, LogicalKeyboardKey>{
|
static const Map<int, LogicalKeyboardKey> _knownLogicalKeys = <int, LogicalKeyboardKey>{
|
||||||
@ -5139,9 +5136,6 @@ class PhysicalKeyboardKey extends KeyboardKey {
|
|||||||
/// See the function [RawKeyEvent.physicalKey] for more information.
|
/// See the function [RawKeyEvent.physicalKey] for more information.
|
||||||
static const PhysicalKeyboardKey showAllWindows = PhysicalKeyboardKey(0x000c029f);
|
static const PhysicalKeyboardKey showAllWindows = PhysicalKeyboardKey(0x000c029f);
|
||||||
|
|
||||||
/// A list of all predefined constant [PhysicalKeyboardKey]s.
|
|
||||||
static Iterable<PhysicalKeyboardKey> get knownPhysicalKeys => _knownPhysicalKeys.values;
|
|
||||||
|
|
||||||
// A list of all the predefined constant PhysicalKeyboardKeys so that they
|
// A list of all the predefined constant PhysicalKeyboardKeys so that they
|
||||||
// can be searched.
|
// can be searched.
|
||||||
static const Map<int, PhysicalKeyboardKey> _knownPhysicalKeys = <int, PhysicalKeyboardKey>{
|
static const Map<int, PhysicalKeyboardKey> _knownPhysicalKeys = <int, PhysicalKeyboardKey>{
|
||||||
|
@ -2953,16 +2953,6 @@ const Map<int, LogicalKeyboardKey> kWindowsToLogicalKey = <int, LogicalKeyboardK
|
|||||||
45: LogicalKeyboardKey.insert,
|
45: LogicalKeyboardKey.insert,
|
||||||
46: LogicalKeyboardKey.delete,
|
46: LogicalKeyboardKey.delete,
|
||||||
47: LogicalKeyboardKey.help,
|
47: LogicalKeyboardKey.help,
|
||||||
48: LogicalKeyboardKey.digit0,
|
|
||||||
49: LogicalKeyboardKey.digit1,
|
|
||||||
50: LogicalKeyboardKey.digit2,
|
|
||||||
51: LogicalKeyboardKey.digit3,
|
|
||||||
52: LogicalKeyboardKey.digit4,
|
|
||||||
53: LogicalKeyboardKey.digit5,
|
|
||||||
54: LogicalKeyboardKey.digit6,
|
|
||||||
55: LogicalKeyboardKey.digit7,
|
|
||||||
56: LogicalKeyboardKey.digit8,
|
|
||||||
57: LogicalKeyboardKey.digit9,
|
|
||||||
65: LogicalKeyboardKey.keyA,
|
65: LogicalKeyboardKey.keyA,
|
||||||
66: LogicalKeyboardKey.keyB,
|
66: LogicalKeyboardKey.keyB,
|
||||||
67: LogicalKeyboardKey.keyC,
|
67: LogicalKeyboardKey.keyC,
|
||||||
|
@ -3,12 +3,10 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'binding.dart';
|
|
||||||
import 'hardware_keyboard.dart';
|
|
||||||
import 'keyboard_key.dart';
|
import 'keyboard_key.dart';
|
||||||
import 'raw_keyboard_android.dart';
|
import 'raw_keyboard_android.dart';
|
||||||
import 'raw_keyboard_fuchsia.dart';
|
import 'raw_keyboard_fuchsia.dart';
|
||||||
@ -288,20 +286,17 @@ abstract class RawKeyEvent with Diagnosticable {
|
|||||||
final RawKeyEventData data;
|
final RawKeyEventData data;
|
||||||
String? character;
|
String? character;
|
||||||
|
|
||||||
RawKeyEventData _dataFromWeb() {
|
if (kIsWeb) {
|
||||||
final String? key = message['key'] as String?;
|
final String? key = message['key'] as String?;
|
||||||
if (key != null && key.isNotEmpty) {
|
data = RawKeyEventDataWeb(
|
||||||
character = key;
|
|
||||||
}
|
|
||||||
return RawKeyEventDataWeb(
|
|
||||||
code: message['code'] as String? ?? '',
|
code: message['code'] as String? ?? '',
|
||||||
key: key ?? '',
|
key: key ?? '',
|
||||||
location: message['location'] as int? ?? 0,
|
location: message['location'] as int? ?? 0,
|
||||||
metaState: message['metaState'] as int? ?? 0,
|
metaState: message['metaState'] as int? ?? 0,
|
||||||
);
|
);
|
||||||
}
|
if (key != null && key.isNotEmpty) {
|
||||||
if (kIsWeb) {
|
character = key;
|
||||||
data = _dataFromWeb();
|
}
|
||||||
} else {
|
} else {
|
||||||
final String keymap = message['keymap'] as String;
|
final String keymap = message['keymap'] as String;
|
||||||
switch (keymap) {
|
switch (keymap) {
|
||||||
@ -378,7 +373,16 @@ abstract class RawKeyEvent with Diagnosticable {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'web':
|
case 'web':
|
||||||
data = _dataFromWeb();
|
final String? key = message['key'] as String?;
|
||||||
|
data = RawKeyEventDataWeb(
|
||||||
|
code: message['code'] as String? ?? '',
|
||||||
|
key: key ?? '',
|
||||||
|
location: message['location'] as int? ?? 0,
|
||||||
|
metaState: message['metaState'] as int? ?? 0,
|
||||||
|
);
|
||||||
|
if (key != null && key.isNotEmpty) {
|
||||||
|
character = key;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/// This exception would only be hit on platforms that haven't yet
|
/// This exception would only be hit on platforms that haven't yet
|
||||||
@ -569,7 +573,9 @@ typedef RawKeyEventHandler = bool Function(RawKeyEvent event);
|
|||||||
/// * [SystemChannels.keyEvent], the low-level channel used for receiving
|
/// * [SystemChannels.keyEvent], the low-level channel used for receiving
|
||||||
/// events from the system.
|
/// events from the system.
|
||||||
class RawKeyboard {
|
class RawKeyboard {
|
||||||
RawKeyboard._();
|
RawKeyboard._() {
|
||||||
|
SystemChannels.keyEvent.setMessageHandler(_handleKeyEvent);
|
||||||
|
}
|
||||||
|
|
||||||
/// The shared instance of [RawKeyboard].
|
/// The shared instance of [RawKeyboard].
|
||||||
static final RawKeyboard instance = RawKeyboard._();
|
static final RawKeyboard instance = RawKeyboard._();
|
||||||
@ -600,45 +606,39 @@ class RawKeyboard {
|
|||||||
_listeners.remove(listener);
|
_listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A handler for raw hardware keyboard events that will stop propagation if
|
/// A handler for hardware keyboard events that will stop propagation if the
|
||||||
/// the handler returns true.
|
/// handler returns true.
|
||||||
///
|
///
|
||||||
/// This property is only a wrapper over [KeyEventManager.keyMessageHandler],
|
/// Key events on the platform are given to Flutter to be handled by the
|
||||||
/// and is kept only for backward compatibility. New code should use
|
/// engine. If they are not handled, then the platform will continue to
|
||||||
/// [KeyEventManager.keyMessageHandler] to set custom global key event
|
/// distribute the keys (i.e. propagate them) to other (possibly non-Flutter)
|
||||||
/// handler. Setting [keyEventHandler] will cause
|
/// components in the application. The return value from this handler tells
|
||||||
/// [KeyEventManager.keyMessageHandler] to be set with a converted handler.
|
/// the platform to either stop propagation (by returning true: "event
|
||||||
/// If [KeyEventManager.keyMessageHandler] is set by [FocusManager] (the most
|
/// handled"), or pass the event on to other controls (false: "event not
|
||||||
/// common situation), then the exact value of [keyEventHandler] is a dummy
|
/// handled").
|
||||||
/// callback and must not be invoked.
|
///
|
||||||
RawKeyEventHandler? get keyEventHandler {
|
/// This handler is normally set by the [FocusManager] so that it can control
|
||||||
if (ServicesBinding.instance!.keyEventManager.keyMessageHandler != _cachedKeyMessageHandler) {
|
/// the key event propagation to focused widgets.
|
||||||
_cachedKeyMessageHandler = ServicesBinding.instance!.keyEventManager.keyMessageHandler;
|
///
|
||||||
_cachedKeyEventHandler = _cachedKeyMessageHandler == null ?
|
/// Most applications can use the focus system (see [Focus] and
|
||||||
null :
|
/// [FocusManager]) to receive key events. If you are not using the
|
||||||
(RawKeyEvent event) {
|
/// [FocusManager] to manage focus, then to be able to stop propagation of the
|
||||||
assert(false,
|
/// event by indicating that the event was handled, set this attribute to a
|
||||||
'The RawKeyboard.instance.keyEventHandler assigned by Flutter is a dummy '
|
/// [RawKeyEventHandler]. Otherwise, key events will be assumed to not have
|
||||||
'callback kept for compatibility and should not be directly called. Use '
|
/// been handled by Flutter, and will also be sent to other (possibly
|
||||||
'ServicesBinding.instance!.keyMessageHandler instead.');
|
/// non-Flutter) controls in the application.
|
||||||
return true;
|
///
|
||||||
};
|
/// See also:
|
||||||
}
|
///
|
||||||
return _cachedKeyEventHandler;
|
/// * [Focus.onKey], a [Focus] callback attribute that will be given key
|
||||||
}
|
/// events distributed by the [FocusManager] based on the current primary
|
||||||
RawKeyEventHandler? _cachedKeyEventHandler;
|
/// focus.
|
||||||
KeyMessageHandler? _cachedKeyMessageHandler;
|
/// * [addListener], to add passive key event listeners that do not stop event
|
||||||
set keyEventHandler(RawKeyEventHandler? handler) {
|
/// propagation.
|
||||||
_cachedKeyEventHandler = handler;
|
RawKeyEventHandler? keyEventHandler;
|
||||||
_cachedKeyMessageHandler = handler == null ?
|
|
||||||
null :
|
|
||||||
(KeyMessage message) => handler(message.rawEvent);
|
|
||||||
ServicesBinding.instance!.keyEventManager.keyMessageHandler = _cachedKeyMessageHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Process a new [RawKeyEvent] by recording the state changes and
|
Future<dynamic> _handleKeyEvent(dynamic message) async {
|
||||||
/// dispatching to listeners.
|
final RawKeyEvent event = RawKeyEvent.fromMessage(message as Map<String, dynamic>);
|
||||||
bool handleRawKeyEvent(RawKeyEvent event) {
|
|
||||||
bool shouldDispatch = true;
|
bool shouldDispatch = true;
|
||||||
if (event is RawKeyDownEvent) {
|
if (event is RawKeyDownEvent) {
|
||||||
if (event.data.shouldDispatchEvent()) {
|
if (event.data.shouldDispatchEvent()) {
|
||||||
@ -659,7 +659,7 @@ class RawKeyboard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!shouldDispatch) {
|
if (!shouldDispatch) {
|
||||||
return true;
|
return <String, dynamic>{ 'handled': true };
|
||||||
}
|
}
|
||||||
// Make sure that the modifiers reflect reality, in case a modifier key was
|
// Make sure that the modifiers reflect reality, in case a modifier key was
|
||||||
// pressed/released while the app didn't have focus.
|
// pressed/released while the app didn't have focus.
|
||||||
@ -678,7 +678,12 @@ class RawKeyboard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
// Send the key event to the keyEventHandler, then send the appropriate
|
||||||
|
// response to the platform so that it can resolve the event's handling.
|
||||||
|
// Defaults to false if keyEventHandler is null.
|
||||||
|
final bool handled = keyEventHandler != null && keyEventHandler!(event);
|
||||||
|
assert(handled != null, 'keyEventHandler returned null, which is not allowed');
|
||||||
|
return <String, dynamic>{ 'handled': handled };
|
||||||
}
|
}
|
||||||
|
|
||||||
static final Map<_ModifierSidePair, Set<PhysicalKeyboardKey>> _modifierKeyMap = <_ModifierSidePair, Set<PhysicalKeyboardKey>>{
|
static final Map<_ModifierSidePair, Set<PhysicalKeyboardKey>> _modifierKeyMap = <_ModifierSidePair, Set<PhysicalKeyboardKey>>{
|
||||||
@ -803,11 +808,6 @@ class RawKeyboard {
|
|||||||
/// Returns the set of physical keys currently pressed.
|
/// Returns the set of physical keys currently pressed.
|
||||||
Set<PhysicalKeyboardKey> get physicalKeysPressed => _keysPressed.keys.toSet();
|
Set<PhysicalKeyboardKey> get physicalKeysPressed => _keysPressed.keys.toSet();
|
||||||
|
|
||||||
/// Returns the logical key that corresponds to the given pressed physical key.
|
|
||||||
///
|
|
||||||
/// Returns null if the physical key is not currently pressed.
|
|
||||||
LogicalKeyboardKey? lookUpLayout(PhysicalKeyboardKey physicalKey) => _keysPressed[physicalKey];
|
|
||||||
|
|
||||||
/// Clears the list of keys returned from [keysPressed].
|
/// Clears the list of keys returned from [keysPressed].
|
||||||
///
|
///
|
||||||
/// This is used by the testing framework to make sure tests are hermetic.
|
/// This is used by the testing framework to make sure tests are hermetic.
|
||||||
@ -832,5 +832,5 @@ class _ModifierSidePair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => ui.hashValues(modifier, side);
|
int get hashCode => hashValues(modifier, side);
|
||||||
}
|
}
|
||||||
|
@ -83,11 +83,13 @@ class RawKeyEventDataWeb extends RawKeyEventData {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
LogicalKeyboardKey get logicalKey {
|
LogicalKeyboardKey get logicalKey {
|
||||||
// Look to see if the keyCode is a key based on location. Typically they are
|
// Look to see if the keyCode is a printable number pad key, so that a
|
||||||
// numpad keys (versus main area keys) and left/right modifiers.
|
// difference between regular keys (e.g. ".") and the number pad version
|
||||||
final LogicalKeyboardKey? maybeLocationKey = kWebLocationMap[key]?[location];
|
// (e.g. the "." on the number pad) can be determined.
|
||||||
if (maybeLocationKey != null)
|
final LogicalKeyboardKey? numPadKey = kWebNumPadMap[code];
|
||||||
return maybeLocationKey;
|
if (numPadKey != null) {
|
||||||
|
return numPadKey;
|
||||||
|
}
|
||||||
|
|
||||||
// Look to see if the [code] is one we know about and have a mapping for.
|
// Look to see if the [code] is one we know about and have a mapping for.
|
||||||
final LogicalKeyboardKey? newKey = kWebToLogicalKey[code];
|
final LogicalKeyboardKey? newKey = kWebToLogicalKey[code];
|
||||||
|
@ -36,7 +36,7 @@ bool _focusDebug(String message, [Iterable<String>? details]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An enum that describes how to handle a key event handled by a
|
/// An enum that describes how to handle a key event handled by a
|
||||||
/// [FocusOnKeyCallback] or [FocusOnKeyEventCallback].
|
/// [FocusOnKeyCallback].
|
||||||
enum KeyEventResult {
|
enum KeyEventResult {
|
||||||
/// The key event has been handled, and the event should not be propagated to
|
/// The key event has been handled, and the event should not be propagated to
|
||||||
/// other key event handlers.
|
/// other key event handlers.
|
||||||
@ -52,32 +52,6 @@ enum KeyEventResult {
|
|||||||
skipRemainingHandlers,
|
skipRemainingHandlers,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Combine the results returned by multiple [FocusOnKeyCallback]s or
|
|
||||||
/// [FocusOnKeyEventCallback]s.
|
|
||||||
///
|
|
||||||
/// If any callback returns [KeyEventResult.handled], the node considers the
|
|
||||||
/// message handled; otherwise, if any callback returns
|
|
||||||
/// [KeyEventResult.skipRemainingHandlers], the node skips the remaining
|
|
||||||
/// handlers without preventing the platform to handle; otherwise the node is
|
|
||||||
/// ignored.
|
|
||||||
KeyEventResult combineKeyEventResults(Iterable<KeyEventResult> results) {
|
|
||||||
bool hasSkipRemainingHandlers = false;
|
|
||||||
for (final KeyEventResult result in results) {
|
|
||||||
switch (result) {
|
|
||||||
case KeyEventResult.handled:
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
case KeyEventResult.skipRemainingHandlers:
|
|
||||||
hasSkipRemainingHandlers = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hasSkipRemainingHandlers ?
|
|
||||||
KeyEventResult.skipRemainingHandlers :
|
|
||||||
KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Signature of a callback used by [Focus.onKey] and [FocusScope.onKey]
|
/// Signature of a callback used by [Focus.onKey] and [FocusScope.onKey]
|
||||||
/// to receive key events.
|
/// to receive key events.
|
||||||
///
|
///
|
||||||
@ -87,15 +61,6 @@ KeyEventResult combineKeyEventResults(Iterable<KeyEventResult> results) {
|
|||||||
/// was handled.
|
/// was handled.
|
||||||
typedef FocusOnKeyCallback = KeyEventResult Function(FocusNode node, RawKeyEvent event);
|
typedef FocusOnKeyCallback = KeyEventResult Function(FocusNode node, RawKeyEvent event);
|
||||||
|
|
||||||
/// Signature of a callback used by [Focus.onKeyEvent] and [FocusScope.onKeyEvent]
|
|
||||||
/// to receive key events.
|
|
||||||
///
|
|
||||||
/// The [node] is the node that received the event.
|
|
||||||
///
|
|
||||||
/// Returns a [KeyEventResult] that describes how, and whether, the key event
|
|
||||||
/// was handled.
|
|
||||||
typedef FocusOnKeyEventCallback = KeyEventResult Function(FocusNode node, KeyEvent event);
|
|
||||||
|
|
||||||
/// An attachment point for a [FocusNode].
|
/// An attachment point for a [FocusNode].
|
||||||
///
|
///
|
||||||
/// Using a [FocusAttachment] is rarely needed, unless you are building
|
/// Using a [FocusAttachment] is rarely needed, unless you are building
|
||||||
@ -306,13 +271,12 @@ enum UnfocusDisposition {
|
|||||||
/// {@template flutter.widgets.FocusNode.keyEvents}
|
/// {@template flutter.widgets.FocusNode.keyEvents}
|
||||||
/// ## Key Event Propagation
|
/// ## Key Event Propagation
|
||||||
///
|
///
|
||||||
/// The [FocusManager] receives key events from [RawKeyboard] and
|
/// The [FocusManager] receives key events from [RawKeyboard] and will pass them
|
||||||
/// [HardwareKeyboard] and will pass them to the focused nodes. It starts with
|
/// to the focused nodes. It starts with the node with the primary focus, and
|
||||||
/// the node with the primary focus, and will call the [onKey] or [onKeyEvent]
|
/// will call the [onKey] callback for that node. If the callback returns false,
|
||||||
/// callback for that node. If the callback returns false, indicating that it did
|
/// indicating that it did not handle the event, the [FocusManager] will move to
|
||||||
/// not handle the event, the [FocusManager] will move to the parent of that node
|
/// the parent of that node and call its [onKey]. If that [onKey] returns true,
|
||||||
/// and call its [onKey] or [onKeyEvent]. If that [onKey] or [onKeyEvent] returns
|
/// then it will stop propagating the event. If it reaches the root
|
||||||
/// true, then it will stop propagating the event. If it reaches the root
|
|
||||||
/// [FocusScopeNode], [FocusManager.rootScope], the event is discarded.
|
/// [FocusScopeNode], [FocusManager.rootScope], the event is discarded.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
///
|
///
|
||||||
@ -469,14 +433,9 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
|
|||||||
///
|
///
|
||||||
/// The [skipTraversal], [descendantsAreFocusable], and [canRequestFocus]
|
/// The [skipTraversal], [descendantsAreFocusable], and [canRequestFocus]
|
||||||
/// arguments must not be null.
|
/// arguments must not be null.
|
||||||
///
|
|
||||||
/// To receive key events that focuses on this node, pass a listener to `onKeyEvent`.
|
|
||||||
/// The `onKey` is a legacy API based on [RawKeyEvent] and will be deprecated
|
|
||||||
/// in the future.
|
|
||||||
FocusNode({
|
FocusNode({
|
||||||
String? debugLabel,
|
String? debugLabel,
|
||||||
this.onKey,
|
this.onKey,
|
||||||
this.onKeyEvent,
|
|
||||||
bool skipTraversal = false,
|
bool skipTraversal = false,
|
||||||
bool canRequestFocus = true,
|
bool canRequestFocus = true,
|
||||||
bool descendantsAreFocusable = true,
|
bool descendantsAreFocusable = true,
|
||||||
@ -615,18 +574,9 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
|
|||||||
/// Called if this focus node receives a key event while focused (i.e. when
|
/// Called if this focus node receives a key event while focused (i.e. when
|
||||||
/// [hasFocus] returns true).
|
/// [hasFocus] returns true).
|
||||||
///
|
///
|
||||||
/// This is a legacy API based on [RawKeyEvent] and will be deprecated in the
|
|
||||||
/// future. Prefer [onKeyEvent] instead.
|
|
||||||
///
|
|
||||||
/// {@macro flutter.widgets.FocusNode.keyEvents}
|
/// {@macro flutter.widgets.FocusNode.keyEvents}
|
||||||
FocusOnKeyCallback? onKey;
|
FocusOnKeyCallback? onKey;
|
||||||
|
|
||||||
/// Called if this focus node receives a key event while focused (i.e. when
|
|
||||||
/// [hasFocus] returns true).
|
|
||||||
///
|
|
||||||
/// {@macro flutter.widgets.FocusNode.keyEvents}
|
|
||||||
FocusOnKeyEventCallback? onKeyEvent;
|
|
||||||
|
|
||||||
FocusManager? _manager;
|
FocusManager? _manager;
|
||||||
List<FocusNode>? _ancestors;
|
List<FocusNode>? _ancestors;
|
||||||
List<FocusNode>? _descendants;
|
List<FocusNode>? _descendants;
|
||||||
@ -1078,19 +1028,10 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
|
|||||||
/// need to be attached. [FocusAttachment.detach] should be called on the old
|
/// need to be attached. [FocusAttachment.detach] should be called on the old
|
||||||
/// node, and then [attach] called on the new node. This typically happens in
|
/// node, and then [attach] called on the new node. This typically happens in
|
||||||
/// the [State.didUpdateWidget] method.
|
/// the [State.didUpdateWidget] method.
|
||||||
///
|
|
||||||
/// To receive key events that focuses on this node, pass a listener to `onKeyEvent`.
|
|
||||||
/// The `onKey` is a legacy API based on [RawKeyEvent] and will be deprecated
|
|
||||||
/// in the future.
|
|
||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
FocusAttachment attach(
|
FocusAttachment attach(BuildContext? context, {FocusOnKeyCallback? onKey}) {
|
||||||
BuildContext? context, {
|
|
||||||
FocusOnKeyEventCallback? onKeyEvent,
|
|
||||||
FocusOnKeyCallback? onKey,
|
|
||||||
}) {
|
|
||||||
_context = context;
|
_context = context;
|
||||||
this.onKey = onKey ?? this.onKey;
|
this.onKey = onKey ?? this.onKey;
|
||||||
this.onKeyEvent = onKeyEvent ?? this.onKeyEvent;
|
|
||||||
_attachment = FocusAttachment._(this);
|
_attachment = FocusAttachment._(this);
|
||||||
return _attachment!;
|
return _attachment!;
|
||||||
}
|
}
|
||||||
@ -1284,7 +1225,6 @@ class FocusScopeNode extends FocusNode {
|
|||||||
/// All parameters are optional.
|
/// All parameters are optional.
|
||||||
FocusScopeNode({
|
FocusScopeNode({
|
||||||
String? debugLabel,
|
String? debugLabel,
|
||||||
FocusOnKeyEventCallback? onKeyEvent,
|
|
||||||
FocusOnKeyCallback? onKey,
|
FocusOnKeyCallback? onKey,
|
||||||
bool skipTraversal = false,
|
bool skipTraversal = false,
|
||||||
bool canRequestFocus = true,
|
bool canRequestFocus = true,
|
||||||
@ -1292,7 +1232,6 @@ class FocusScopeNode extends FocusNode {
|
|||||||
assert(canRequestFocus != null),
|
assert(canRequestFocus != null),
|
||||||
super(
|
super(
|
||||||
debugLabel: debugLabel,
|
debugLabel: debugLabel,
|
||||||
onKeyEvent: onKeyEvent,
|
|
||||||
onKey: onKey,
|
onKey: onKey,
|
||||||
canRequestFocus: canRequestFocus,
|
canRequestFocus: canRequestFocus,
|
||||||
descendantsAreFocusable: true,
|
descendantsAreFocusable: true,
|
||||||
@ -1524,15 +1463,15 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
|
|||||||
/// When this focus manager is no longer needed, calling [dispose] on it will
|
/// When this focus manager is no longer needed, calling [dispose] on it will
|
||||||
/// unregister these handlers.
|
/// unregister these handlers.
|
||||||
void registerGlobalHandlers() {
|
void registerGlobalHandlers() {
|
||||||
assert(ServicesBinding.instance!.keyEventManager.keyMessageHandler == null);
|
assert(RawKeyboard.instance.keyEventHandler == null);
|
||||||
ServicesBinding.instance!.keyEventManager.keyMessageHandler = _handleKeyMessage;
|
RawKeyboard.instance.keyEventHandler = _handleRawKeyEvent;
|
||||||
GestureBinding.instance!.pointerRouter.addGlobalRoute(_handlePointerEvent);
|
GestureBinding.instance!.pointerRouter.addGlobalRoute(_handlePointerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
if (ServicesBinding.instance!.keyEventManager.keyMessageHandler == _handleKeyMessage) {
|
if (RawKeyboard.instance.keyEventHandler == _handleRawKeyEvent) {
|
||||||
ServicesBinding.instance!.keyEventManager.keyMessageHandler = null;
|
RawKeyboard.instance.keyEventHandler = null;
|
||||||
GestureBinding.instance!.pointerRouter.removeGlobalRoute(_handlePointerEvent);
|
GestureBinding.instance!.pointerRouter.removeGlobalRoute(_handlePointerEvent);
|
||||||
}
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@ -1721,15 +1660,15 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _handleKeyMessage(KeyMessage message) {
|
bool _handleRawKeyEvent(RawKeyEvent event) {
|
||||||
// Update highlightMode first, since things responding to the keys might
|
// Update highlightMode first, since things responding to the keys might
|
||||||
// look at the highlight mode, and it should be accurate.
|
// look at the highlight mode, and it should be accurate.
|
||||||
_lastInteractionWasTouch = false;
|
_lastInteractionWasTouch = false;
|
||||||
_updateHighlightMode();
|
_updateHighlightMode();
|
||||||
|
|
||||||
assert(_focusDebug('Received key event $message'));
|
assert(_focusDebug('Received key event ${event.logicalKey}'));
|
||||||
if (_primaryFocus == null) {
|
if (_primaryFocus == null) {
|
||||||
assert(_focusDebug('No primary focus for key event, ignored: $message'));
|
assert(_focusDebug('No primary focus for key event, ignored: $event'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1738,35 +1677,25 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
|
|||||||
// stop propagation, stop.
|
// stop propagation, stop.
|
||||||
bool handled = false;
|
bool handled = false;
|
||||||
for (final FocusNode node in <FocusNode>[_primaryFocus!, ..._primaryFocus!.ancestors]) {
|
for (final FocusNode node in <FocusNode>[_primaryFocus!, ..._primaryFocus!.ancestors]) {
|
||||||
final List<KeyEventResult> results = <KeyEventResult>[];
|
|
||||||
if (node.onKeyEvent != null) {
|
|
||||||
for (final KeyEvent event in message.events) {
|
|
||||||
results.add(node.onKeyEvent!(node, event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (node.onKey != null) {
|
if (node.onKey != null) {
|
||||||
results.add(node.onKey!(node, message.rawEvent));
|
final KeyEventResult result = node.onKey!(node, event);
|
||||||
|
switch (result) {
|
||||||
|
case KeyEventResult.handled:
|
||||||
|
assert(_focusDebug('Node $node handled key event $event.'));
|
||||||
|
handled = true;
|
||||||
|
break;
|
||||||
|
case KeyEventResult.skipRemainingHandlers:
|
||||||
|
assert(_focusDebug('Node $node stopped key event propagation: $event.'));
|
||||||
|
handled = false;
|
||||||
|
break;
|
||||||
|
case KeyEventResult.ignored:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
final KeyEventResult result = combineKeyEventResults(results);
|
|
||||||
switch (result) {
|
|
||||||
case KeyEventResult.ignored:
|
|
||||||
continue;
|
|
||||||
case KeyEventResult.handled:
|
|
||||||
assert(_focusDebug('Node $node handled key event $message.'));
|
|
||||||
handled = true;
|
|
||||||
break;
|
|
||||||
case KeyEventResult.skipRemainingHandlers:
|
|
||||||
assert(_focusDebug('Node $node stopped key event propagation: $message.'));
|
|
||||||
handled = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Only KeyEventResult.ignored will continue the for loop. All other
|
|
||||||
// options will stop the event propagation.
|
|
||||||
assert(result != KeyEventResult.ignored);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
assert(_focusDebug('Key event not handled by anyone: $message.'));
|
assert(_focusDebug('Key event not handled by anyone: $event.'));
|
||||||
}
|
}
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
@ -283,7 +283,6 @@ class Focus extends StatefulWidget {
|
|||||||
this.autofocus = false,
|
this.autofocus = false,
|
||||||
this.onFocusChange,
|
this.onFocusChange,
|
||||||
this.onKey,
|
this.onKey,
|
||||||
this.onKeyEvent,
|
|
||||||
this.debugLabel,
|
this.debugLabel,
|
||||||
this.canRequestFocus,
|
this.canRequestFocus,
|
||||||
this.descendantsAreFocusable = true,
|
this.descendantsAreFocusable = true,
|
||||||
@ -316,24 +315,6 @@ class Focus extends StatefulWidget {
|
|||||||
/// focus.
|
/// focus.
|
||||||
///
|
///
|
||||||
/// Key events are first given to the [FocusNode] that has primary focus, and
|
/// Key events are first given to the [FocusNode] that has primary focus, and
|
||||||
/// if its [onKeyEvent] method return false, then they are given to each
|
|
||||||
/// ancestor node up the focus hierarchy in turn. If an event reaches the root
|
|
||||||
/// of the hierarchy, it is discarded.
|
|
||||||
///
|
|
||||||
/// This is not the way to get text input in the manner of a text field: it
|
|
||||||
/// leaves out support for input method editors, and doesn't support soft
|
|
||||||
/// keyboards in general. For text input, consider [TextField],
|
|
||||||
/// [EditableText], or [CupertinoTextField] instead, which do support these
|
|
||||||
/// things.
|
|
||||||
final FocusOnKeyEventCallback? onKeyEvent;
|
|
||||||
|
|
||||||
/// Handler for keys pressed when this object or one of its children has
|
|
||||||
/// focus.
|
|
||||||
///
|
|
||||||
/// This is a legacy API based on [RawKeyEvent] and will be deprecated in the
|
|
||||||
/// future. Prefer [onKeyEvent] instead.
|
|
||||||
///
|
|
||||||
/// Key events are first given to the [FocusNode] that has primary focus, and
|
|
||||||
/// if its [onKey] method return false, then they are given to each ancestor
|
/// if its [onKey] method return false, then they are given to each ancestor
|
||||||
/// node up the focus hierarchy in turn. If an event reaches the root of the
|
/// node up the focus hierarchy in turn. If an event reaches the root of the
|
||||||
/// hierarchy, it is discarded.
|
/// hierarchy, it is discarded.
|
||||||
@ -559,7 +540,7 @@ class Focus extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<Focus> createState() => _FocusState();
|
State<Focus> createState() => _FocusState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FocusState extends State<Focus> {
|
class _FocusState extends State<Focus> {
|
||||||
@ -594,7 +575,7 @@ class _FocusState extends State<Focus> {
|
|||||||
_canRequestFocus = focusNode.canRequestFocus;
|
_canRequestFocus = focusNode.canRequestFocus;
|
||||||
_descendantsAreFocusable = focusNode.descendantsAreFocusable;
|
_descendantsAreFocusable = focusNode.descendantsAreFocusable;
|
||||||
_hasPrimaryFocus = focusNode.hasPrimaryFocus;
|
_hasPrimaryFocus = focusNode.hasPrimaryFocus;
|
||||||
_focusAttachment = focusNode.attach(context, onKeyEvent: widget.onKeyEvent, onKey: widget.onKey);
|
_focusAttachment = focusNode.attach(context, onKey: widget.onKey);
|
||||||
|
|
||||||
// Add listener even if the _internalNode existed before, since it should
|
// Add listener even if the _internalNode existed before, since it should
|
||||||
// not be listening now if we're re-using a previous one because it should
|
// not be listening now if we're re-using a previous one because it should
|
||||||
@ -933,7 +914,6 @@ class FocusScope extends Focus {
|
|||||||
ValueChanged<bool>? onFocusChange,
|
ValueChanged<bool>? onFocusChange,
|
||||||
bool? canRequestFocus,
|
bool? canRequestFocus,
|
||||||
bool? skipTraversal,
|
bool? skipTraversal,
|
||||||
FocusOnKeyEventCallback? onKeyEvent,
|
|
||||||
FocusOnKeyCallback? onKey,
|
FocusOnKeyCallback? onKey,
|
||||||
String? debugLabel,
|
String? debugLabel,
|
||||||
}) : assert(child != null),
|
}) : assert(child != null),
|
||||||
@ -946,7 +926,6 @@ class FocusScope extends Focus {
|
|||||||
onFocusChange: onFocusChange,
|
onFocusChange: onFocusChange,
|
||||||
canRequestFocus: canRequestFocus,
|
canRequestFocus: canRequestFocus,
|
||||||
skipTraversal: skipTraversal,
|
skipTraversal: skipTraversal,
|
||||||
onKeyEvent: onKeyEvent,
|
|
||||||
onKey: onKey,
|
onKey: onKey,
|
||||||
debugLabel: debugLabel,
|
debugLabel: debugLabel,
|
||||||
);
|
);
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
// Copyright 2014 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.
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import 'focus_manager.dart';
|
|
||||||
import 'focus_scope.dart';
|
|
||||||
import 'framework.dart';
|
|
||||||
|
|
||||||
export 'package:flutter/services.dart' show KeyEvent;
|
|
||||||
|
|
||||||
/// A widget that calls a callback whenever the user presses or releases a key
|
|
||||||
/// on a keyboard.
|
|
||||||
///
|
|
||||||
/// A [KeyboardListener] is useful for listening to key events and
|
|
||||||
/// hardware buttons that are represented as keys. Typically used by games and
|
|
||||||
/// other apps that use keyboards for purposes other than text entry.
|
|
||||||
///
|
|
||||||
/// For text entry, consider using a [EditableText], which integrates with
|
|
||||||
/// on-screen keyboards and input method editors (IMEs).
|
|
||||||
///
|
|
||||||
/// The [KeyboardListener] is different from [RawKeyboardListener] in that
|
|
||||||
/// [KeyboardListener] uses the newer [HardwareKeyboard] API, which is
|
|
||||||
/// preferrable.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [EditableText], which should be used instead of this widget for text
|
|
||||||
/// entry.
|
|
||||||
/// * [RawKeyboardListener], a similar widget based on the old [RawKeyboard]
|
|
||||||
/// API.
|
|
||||||
class KeyboardListener extends StatelessWidget {
|
|
||||||
/// Creates a widget that receives keyboard events.
|
|
||||||
///
|
|
||||||
/// For text entry, consider using a [EditableText], which integrates with
|
|
||||||
/// on-screen keyboards and input method editors (IMEs).
|
|
||||||
///
|
|
||||||
/// The [focusNode] and [child] arguments are required and must not be null.
|
|
||||||
///
|
|
||||||
/// The [autofocus] argument must not be null.
|
|
||||||
///
|
|
||||||
/// The `key` is an identifier for widgets, and is unrelated to keyboards.
|
|
||||||
/// See [Widget.key].
|
|
||||||
const KeyboardListener({
|
|
||||||
Key? key,
|
|
||||||
required this.focusNode,
|
|
||||||
this.autofocus = false,
|
|
||||||
this.includeSemantics = true,
|
|
||||||
this.onKeyEvent,
|
|
||||||
required this.child,
|
|
||||||
}) : assert(focusNode != null),
|
|
||||||
assert(autofocus != null),
|
|
||||||
assert(includeSemantics != null),
|
|
||||||
assert(child != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
/// Controls whether this widget has keyboard focus.
|
|
||||||
final FocusNode focusNode;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.Focus.autofocus}
|
|
||||||
final bool autofocus;
|
|
||||||
|
|
||||||
/// {@macro flutter.widgets.Focus.includeSemantics}
|
|
||||||
final bool includeSemantics;
|
|
||||||
|
|
||||||
/// Called whenever this widget receives a keyboard event.
|
|
||||||
final ValueChanged<KeyEvent>? onKeyEvent;
|
|
||||||
|
|
||||||
/// The widget below this widget in the tree.
|
|
||||||
///
|
|
||||||
/// {@macro flutter.widgets.ProxyWidget.child}
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Focus(
|
|
||||||
focusNode: focusNode,
|
|
||||||
autofocus: autofocus,
|
|
||||||
includeSemantics: includeSemantics,
|
|
||||||
onKeyEvent: (FocusNode node, KeyEvent event) {
|
|
||||||
onKeyEvent?.call(event);
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
},
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
||||||
super.debugFillProperties(properties);
|
|
||||||
properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode));
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,16 +21,10 @@ export 'package:flutter/services.dart' show RawKeyEvent;
|
|||||||
/// For text entry, consider using a [EditableText], which integrates with
|
/// For text entry, consider using a [EditableText], which integrates with
|
||||||
/// on-screen keyboards and input method editors (IMEs).
|
/// on-screen keyboards and input method editors (IMEs).
|
||||||
///
|
///
|
||||||
/// The [RawKeyboardListener] is different from [KeyboardListener] in that
|
|
||||||
/// [RawKeyboardListener] uses the legacy [RawKeyboard] API. Use
|
|
||||||
/// [KeyboardListener] if possible.
|
|
||||||
///
|
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [EditableText], which should be used instead of this widget for text
|
/// * [EditableText], which should be used instead of this widget for text
|
||||||
/// entry.
|
/// entry.
|
||||||
/// * [KeyboardListener], a similar widget based on the newer
|
|
||||||
/// [HardwareKeyboard] API.
|
|
||||||
class RawKeyboardListener extends StatefulWidget {
|
class RawKeyboardListener extends StatefulWidget {
|
||||||
/// Creates a widget that receives raw keyboard events.
|
/// Creates a widget that receives raw keyboard events.
|
||||||
///
|
///
|
||||||
|
@ -194,13 +194,13 @@ abstract class ShortcutActivator {
|
|||||||
/// event.
|
/// event.
|
||||||
///
|
///
|
||||||
/// For example, for `Ctrl-A`, it has to check if the event is a
|
/// For example, for `Ctrl-A`, it has to check if the event is a
|
||||||
/// [KeyDownEvent], if either side of the Ctrl key is pressed, and none of
|
/// [RawKeyDownEvent], if either side of the Ctrl key is pressed, and none of
|
||||||
/// the Shift keys, Alt keys, or Meta keys are pressed; it doesn't have to
|
/// the Shift keys, Alt keys, or Meta keys are pressed; it doesn't have to
|
||||||
/// check if KeyA is pressed, since it's already guaranteed.
|
/// check if KeyA is pressed, since it's already guaranteed.
|
||||||
///
|
///
|
||||||
/// This method must not cause any side effects for the `state`. Typically
|
/// This method must not cause any side effects for the `state`. Typically
|
||||||
/// this is only used to query whether [HardwareKeyboard.logicalKeysPressed]
|
/// this is only used to query whether [RawKeyboard.keysPressed] contains
|
||||||
/// contains a key.
|
/// a key.
|
||||||
///
|
///
|
||||||
/// Since [ShortcutActivator] accepts all event types, subclasses might want
|
/// Since [ShortcutActivator] accepts all event types, subclasses might want
|
||||||
/// to check the event type in [accepts].
|
/// to check the event type in [accepts].
|
||||||
@ -314,13 +314,11 @@ class LogicalKeySet extends KeySet<LogicalKeyboardKey> with Diagnosticable
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool accepts(RawKeyEvent event, RawKeyboard state) {
|
bool accepts(RawKeyEvent event, RawKeyboard state) {
|
||||||
if (event is! RawKeyDownEvent)
|
|
||||||
return false;
|
|
||||||
final Set<LogicalKeyboardKey> collapsedRequired = LogicalKeyboardKey.collapseSynonyms(keys);
|
final Set<LogicalKeyboardKey> collapsedRequired = LogicalKeyboardKey.collapseSynonyms(keys);
|
||||||
final Set<LogicalKeyboardKey> collapsedPressed = LogicalKeyboardKey.collapseSynonyms(state.keysPressed);
|
final Set<LogicalKeyboardKey> collapsedPressed = LogicalKeyboardKey.collapseSynonyms(state.keysPressed);
|
||||||
final bool keysEqual = collapsedRequired.difference(collapsedPressed).isEmpty
|
final bool keysEqual = collapsedRequired.difference(collapsedPressed).isEmpty
|
||||||
&& collapsedRequired.length == collapsedPressed.length;
|
&& collapsedRequired.length == collapsedPressed.length;
|
||||||
return keysEqual;
|
return event is RawKeyDownEvent && keysEqual;
|
||||||
}
|
}
|
||||||
|
|
||||||
static final Set<LogicalKeyboardKey> _modifiers = <LogicalKeyboardKey>{
|
static final Set<LogicalKeyboardKey> _modifiers = <LogicalKeyboardKey>{
|
||||||
@ -427,8 +425,7 @@ class ShortcutMapProperty extends DiagnosticsProperty<Map<ShortcutActivator, Int
|
|||||||
/// * [CharacterActivator], an activator that represents key combinations
|
/// * [CharacterActivator], an activator that represents key combinations
|
||||||
/// that result in the specified character, such as question mark.
|
/// that result in the specified character, such as question mark.
|
||||||
class SingleActivator with Diagnosticable implements ShortcutActivator {
|
class SingleActivator with Diagnosticable implements ShortcutActivator {
|
||||||
/// Triggered when the [trigger] key is pressed or repeated when the
|
/// Create an activator of a trigger key and modifiers.
|
||||||
/// modifiers are held.
|
|
||||||
///
|
///
|
||||||
/// The `trigger` should be the non-modifier key that is pressed after all the
|
/// The `trigger` should be the non-modifier key that is pressed after all the
|
||||||
/// modifiers, such as [LogicalKeyboardKey.keyC] as in `Ctrl+C`. It must not be
|
/// modifiers, such as [LogicalKeyboardKey.keyC] as in `Ctrl+C`. It must not be
|
||||||
@ -437,9 +434,6 @@ class SingleActivator with Diagnosticable implements ShortcutActivator {
|
|||||||
/// The `control`, `shift`, `alt`, and `meta` flags represent whether
|
/// The `control`, `shift`, `alt`, and `meta` flags represent whether
|
||||||
/// the respect modifier keys should be held (true) or released (false)
|
/// the respect modifier keys should be held (true) or released (false)
|
||||||
///
|
///
|
||||||
/// On each [RawKeyDownEvent] of the [trigger] key, this activator checks
|
|
||||||
/// whether the specified modifier conditions are met.
|
|
||||||
///
|
|
||||||
/// {@tool dartpad --template=stateful_widget_scaffold_center}
|
/// {@tool dartpad --template=stateful_widget_scaffold_center}
|
||||||
/// In the following example, the shortcut `Control + C` increases the counter:
|
/// In the following example, the shortcut `Control + C` increases the counter:
|
||||||
///
|
///
|
||||||
@ -817,7 +811,17 @@ class ShortcutManager extends ChangeNotifier with Diagnosticable {
|
|||||||
/// must be mapped to an [Action], and the [Action] must be enabled.
|
/// must be mapped to an [Action], and the [Action] must be enabled.
|
||||||
@protected
|
@protected
|
||||||
KeyEventResult handleKeypress(BuildContext context, RawKeyEvent event) {
|
KeyEventResult handleKeypress(BuildContext context, RawKeyEvent event) {
|
||||||
|
if (event is! RawKeyDownEvent) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
assert(context != null);
|
assert(context != null);
|
||||||
|
assert(
|
||||||
|
RawKeyboard.instance.keysPressed.isNotEmpty,
|
||||||
|
'Received a key down event when no keys are in keysPressed. '
|
||||||
|
"This state can occur if the key event being sent doesn't properly "
|
||||||
|
'set its modifier flags. This was the event: $event and its data: '
|
||||||
|
'${event.data}',
|
||||||
|
);
|
||||||
final Intent? matchedIntent = _find(event, RawKeyboard.instance);
|
final Intent? matchedIntent = _find(event, RawKeyboard.instance);
|
||||||
if (matchedIntent != null) {
|
if (matchedIntent != null) {
|
||||||
final BuildContext? primaryContext = primaryFocus?.context;
|
final BuildContext? primaryContext = primaryFocus?.context;
|
||||||
@ -1281,4 +1285,4 @@ class CallbackShortcuts extends StatelessWidget {
|
|||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -63,7 +63,6 @@ export 'src/widgets/inherited_model.dart';
|
|||||||
export 'src/widgets/inherited_notifier.dart';
|
export 'src/widgets/inherited_notifier.dart';
|
||||||
export 'src/widgets/inherited_theme.dart';
|
export 'src/widgets/inherited_theme.dart';
|
||||||
export 'src/widgets/interactive_viewer.dart';
|
export 'src/widgets/interactive_viewer.dart';
|
||||||
export 'src/widgets/keyboard_listener.dart';
|
|
||||||
export 'src/widgets/layout_builder.dart';
|
export 'src/widgets/layout_builder.dart';
|
||||||
export 'src/widgets/list_wheel_scroll_view.dart';
|
export 'src/widgets/list_wheel_scroll_view.dart';
|
||||||
export 'src/widgets/localizations.dart';
|
export 'src/widgets/localizations.dart';
|
||||||
|
@ -4348,7 +4348,7 @@ void main() {
|
|||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(focusNode3.hasPrimaryFocus, isTrue);
|
expect(focusNode3.hasPrimaryFocus, isTrue);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Scrolling shortcuts are disabled in text fields', (WidgetTester tester) async {
|
testWidgets('Scrolling shortcuts are disabled in text fields', (WidgetTester tester) async {
|
||||||
bool scrollInvoked = false;
|
bool scrollInvoked = false;
|
||||||
@ -4381,7 +4381,7 @@ void main() {
|
|||||||
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
||||||
expect(scrollInvoked, isFalse);
|
expect(scrollInvoked, isFalse);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Cupertino text field semantics', (WidgetTester tester) async {
|
testWidgets('Cupertino text field semantics', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
|
@ -4731,7 +4731,7 @@ void main() {
|
|||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
||||||
expect(controller.selection.extentOffset - controller.selection.baseOffset, -1);
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, -1);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Shift test 2', (WidgetTester tester) async {
|
testWidgets('Shift test 2', (WidgetTester tester) async {
|
||||||
await setupWidget(tester);
|
await setupWidget(tester);
|
||||||
@ -4749,7 +4749,7 @@ void main() {
|
|||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowRight);
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowRight);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(controller.selection.extentOffset - controller.selection.baseOffset, 1);
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 1);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Control Shift test', (WidgetTester tester) async {
|
testWidgets('Control Shift test', (WidgetTester tester) async {
|
||||||
await setupWidget(tester);
|
await setupWidget(tester);
|
||||||
@ -4766,7 +4766,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(controller.selection.extentOffset - controller.selection.baseOffset, 5);
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 5);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Down and up test', (WidgetTester tester) async {
|
testWidgets('Down and up test', (WidgetTester tester) async {
|
||||||
await setupWidget(tester);
|
await setupWidget(tester);
|
||||||
@ -4793,7 +4793,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(controller.selection.extentOffset - controller.selection.baseOffset, 0);
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 0);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Down and up test 2', (WidgetTester tester) async {
|
testWidgets('Down and up test 2', (WidgetTester tester) async {
|
||||||
await setupWidget(tester);
|
await setupWidget(tester);
|
||||||
@ -4849,7 +4849,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(controller.selection.extentOffset - controller.selection.baseOffset, -5);
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, -5);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Read only keyboard selection test', (WidgetTester tester) async {
|
testWidgets('Read only keyboard selection test', (WidgetTester tester) async {
|
||||||
final TextEditingController controller = TextEditingController(text: 'readonly');
|
final TextEditingController controller = TextEditingController(text: 'readonly');
|
||||||
@ -4869,7 +4869,7 @@ void main() {
|
|||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft);
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft);
|
||||||
expect(controller.selection.extentOffset - controller.selection.baseOffset, -1);
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, -1);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
}, skip: areKeyEventsHandledByPlatform);
|
}, skip: areKeyEventsHandledByPlatform);
|
||||||
|
|
||||||
testWidgets('Copy paste test', (WidgetTester tester) async {
|
testWidgets('Copy paste test', (WidgetTester tester) async {
|
||||||
@ -4944,7 +4944,7 @@ void main() {
|
|||||||
|
|
||||||
const String expected = 'a biga big house\njumped over a mouse';
|
const String expected = 'a biga big house\njumped over a mouse';
|
||||||
expect(find.text(expected), findsOneWidget, reason: 'Because text contains ${controller.text}');
|
expect(find.text(expected), findsOneWidget, reason: 'Because text contains ${controller.text}');
|
||||||
}, skip: areKeyEventsHandledByPlatform, variant: KeySimulatorTransitModeVariant.all());
|
}, skip: areKeyEventsHandledByPlatform);
|
||||||
|
|
||||||
testWidgets('Copy paste obscured text test', (WidgetTester tester) async {
|
testWidgets('Copy paste obscured text test', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
@ -5018,7 +5018,7 @@ void main() {
|
|||||||
|
|
||||||
const String expected = 'a biga big house jumped over a mouse';
|
const String expected = 'a biga big house jumped over a mouse';
|
||||||
expect(find.text(expected), findsOneWidget, reason: 'Because text contains ${controller.text}');
|
expect(find.text(expected), findsOneWidget, reason: 'Because text contains ${controller.text}');
|
||||||
}, skip: areKeyEventsHandledByPlatform, variant: KeySimulatorTransitModeVariant.all());
|
}, skip: areKeyEventsHandledByPlatform);
|
||||||
|
|
||||||
// Regressing test for https://github.com/flutter/flutter/issues/78219
|
// Regressing test for https://github.com/flutter/flutter/issues/78219
|
||||||
testWidgets('Paste does not crash when the section is inValid', (WidgetTester tester) async {
|
testWidgets('Paste does not crash when the section is inValid', (WidgetTester tester) async {
|
||||||
@ -5069,7 +5069,7 @@ void main() {
|
|||||||
// Do nothing.
|
// Do nothing.
|
||||||
expect(find.text(clipboardContent), findsNothing);
|
expect(find.text(clipboardContent), findsNothing);
|
||||||
expect(controller.selection, const TextSelection.collapsed(offset: -1));
|
expect(controller.selection, const TextSelection.collapsed(offset: -1));
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Cut test', (WidgetTester tester) async {
|
testWidgets('Cut test', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
@ -5145,7 +5145,7 @@ void main() {
|
|||||||
|
|
||||||
const String expected = ' housa bige\njumped over a mouse';
|
const String expected = ' housa bige\njumped over a mouse';
|
||||||
expect(find.text(expected), findsOneWidget);
|
expect(find.text(expected), findsOneWidget);
|
||||||
}, skip: areKeyEventsHandledByPlatform, variant: KeySimulatorTransitModeVariant.all());
|
}, skip: areKeyEventsHandledByPlatform);
|
||||||
|
|
||||||
testWidgets('Cut obscured text test', (WidgetTester tester) async {
|
testWidgets('Cut obscured text test', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
@ -5220,7 +5220,7 @@ void main() {
|
|||||||
|
|
||||||
const String expected = ' housa bige jumped over a mouse';
|
const String expected = ' housa bige jumped over a mouse';
|
||||||
expect(find.text(expected), findsOneWidget);
|
expect(find.text(expected), findsOneWidget);
|
||||||
}, skip: areKeyEventsHandledByPlatform, variant: KeySimulatorTransitModeVariant.all());
|
}, skip: areKeyEventsHandledByPlatform);
|
||||||
|
|
||||||
testWidgets('Select all test', (WidgetTester tester) async {
|
testWidgets('Select all test', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
@ -5269,7 +5269,7 @@ void main() {
|
|||||||
|
|
||||||
const String expected = '';
|
const String expected = '';
|
||||||
expect(find.text(expected), findsOneWidget);
|
expect(find.text(expected), findsOneWidget);
|
||||||
}, skip: areKeyEventsHandledByPlatform, variant: KeySimulatorTransitModeVariant.all());
|
}, skip: areKeyEventsHandledByPlatform);
|
||||||
|
|
||||||
testWidgets('Delete test', (WidgetTester tester) async {
|
testWidgets('Delete test', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
@ -5321,7 +5321,7 @@ void main() {
|
|||||||
|
|
||||||
const String expected2 = '';
|
const String expected2 = '';
|
||||||
expect(find.text(expected2), findsOneWidget);
|
expect(find.text(expected2), findsOneWidget);
|
||||||
}, skip: areKeyEventsHandledByPlatform, variant: KeySimulatorTransitModeVariant.all());
|
}, skip: areKeyEventsHandledByPlatform);
|
||||||
|
|
||||||
testWidgets('Changing positions of text fields', (WidgetTester tester) async {
|
testWidgets('Changing positions of text fields', (WidgetTester tester) async {
|
||||||
|
|
||||||
@ -5413,7 +5413,7 @@ void main() {
|
|||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
|
||||||
|
|
||||||
expect(c1.selection.extentOffset - c1.selection.baseOffset, -10);
|
expect(c1.selection.extentOffset - c1.selection.baseOffset, -10);
|
||||||
}, skip: areKeyEventsHandledByPlatform, variant: KeySimulatorTransitModeVariant.all());
|
}, skip: areKeyEventsHandledByPlatform);
|
||||||
|
|
||||||
|
|
||||||
testWidgets('Changing focus test', (WidgetTester tester) async {
|
testWidgets('Changing focus test', (WidgetTester tester) async {
|
||||||
@ -5488,7 +5488,7 @@ void main() {
|
|||||||
|
|
||||||
expect(c1.selection.extentOffset - c1.selection.baseOffset, 0);
|
expect(c1.selection.extentOffset - c1.selection.baseOffset, 0);
|
||||||
expect(c2.selection.extentOffset - c2.selection.baseOffset, -5);
|
expect(c2.selection.extentOffset - c2.selection.baseOffset, -5);
|
||||||
}, skip: areKeyEventsHandledByPlatform, variant: KeySimulatorTransitModeVariant.all());
|
}, skip: areKeyEventsHandledByPlatform);
|
||||||
|
|
||||||
testWidgets('Caret works when maxLines is null', (WidgetTester tester) async {
|
testWidgets('Caret works when maxLines is null', (WidgetTester tester) async {
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController();
|
||||||
|
@ -16,11 +16,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
|
|
||||||
// The test_api package is not for general use... it's literally for our use.
|
|
||||||
// ignore: deprecated_member_use
|
|
||||||
import 'package:test_api/test_api.dart' as test_package;
|
|
||||||
|
|
||||||
import '../rendering/mock_canvas.dart';
|
import '../rendering/mock_canvas.dart';
|
||||||
import '../rendering/recording_canvas.dart';
|
import '../rendering/recording_canvas.dart';
|
||||||
@ -40,38 +35,6 @@ class FakeEditableTextState with TextSelectionDelegate {
|
|||||||
void bringIntoView(TextPosition position) { }
|
void bringIntoView(TextPosition position) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
@isTest
|
|
||||||
void testVariants(
|
|
||||||
String description,
|
|
||||||
AsyncValueGetter<void> callback, {
|
|
||||||
bool? skip,
|
|
||||||
test_package.Timeout? timeout,
|
|
||||||
TestVariant<Object?> variant = const DefaultTestVariant(),
|
|
||||||
dynamic tags,
|
|
||||||
}) {
|
|
||||||
assert(variant != null);
|
|
||||||
assert(variant.values.isNotEmpty, 'There must be at least one value to test in the testing variant.');
|
|
||||||
for (final dynamic value in variant.values) {
|
|
||||||
final String variationDescription = variant.describeValue(value);
|
|
||||||
final String combinedDescription = variationDescription.isNotEmpty ? '$description ($variationDescription)' : description;
|
|
||||||
test(
|
|
||||||
combinedDescription,
|
|
||||||
() async {
|
|
||||||
Object? memento;
|
|
||||||
try {
|
|
||||||
memento = await variant.setUp(value);
|
|
||||||
await callback();
|
|
||||||
} finally {
|
|
||||||
await variant.tearDown(value, memento);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
skip: skip,
|
|
||||||
timeout: timeout,
|
|
||||||
tags: tags,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('RenderEditable respects clipBehavior', () {
|
test('RenderEditable respects clipBehavior', () {
|
||||||
const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
|
const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
|
||||||
@ -1380,7 +1343,7 @@ void main() {
|
|||||||
expect(currentSelection.extentOffset, 1);
|
expect(currentSelection.extentOffset, 1);
|
||||||
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/58068
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/58068
|
||||||
|
|
||||||
testVariants('respects enableInteractiveSelection', () async {
|
test('respects enableInteractiveSelection', () async {
|
||||||
const String text = '012345';
|
const String text = '012345';
|
||||||
final TextSelectionDelegate delegate = FakeEditableTextState()
|
final TextSelectionDelegate delegate = FakeEditableTextState()
|
||||||
..textEditingValue = const TextEditingValue(text: text);
|
..textEditingValue = const TextEditingValue(text: text);
|
||||||
@ -1440,7 +1403,7 @@ void main() {
|
|||||||
|
|
||||||
await simulateKeyUpEvent(wordModifier);
|
await simulateKeyUpEvent(wordModifier);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.shift);
|
await simulateKeyUpEvent(LogicalKeyboardKey.shift);
|
||||||
}, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/58068
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/58068
|
||||||
|
|
||||||
group('delete', () {
|
group('delete', () {
|
||||||
test('when as a non-collapsed selection, it should delete a selection', () async {
|
test('when as a non-collapsed selection, it should delete a selection', () async {
|
||||||
|
@ -1,165 +0,0 @@
|
|||||||
// Copyright 2014 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.
|
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('HardwareKeyboard records pressed keys and enabled locks', (WidgetTester tester) async {
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.numLock, platform: 'windows');
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed,
|
|
||||||
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed,
|
|
||||||
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled,
|
|
||||||
equals(<KeyboardLockMode>{KeyboardLockMode.numLock}));
|
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.numpad1, platform: 'windows');
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed,
|
|
||||||
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock, PhysicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed,
|
|
||||||
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock, LogicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled,
|
|
||||||
equals(<KeyboardLockMode>{KeyboardLockMode.numLock}));
|
|
||||||
|
|
||||||
await simulateKeyRepeatEvent(LogicalKeyboardKey.numpad1, platform: 'windows');
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed,
|
|
||||||
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock, PhysicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed,
|
|
||||||
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock, LogicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled,
|
|
||||||
equals(<KeyboardLockMode>{KeyboardLockMode.numLock}));
|
|
||||||
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.numLock);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed,
|
|
||||||
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed,
|
|
||||||
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled,
|
|
||||||
equals(<KeyboardLockMode>{KeyboardLockMode.numLock}));
|
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.numLock, platform: 'windows');
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed,
|
|
||||||
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock, PhysicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed,
|
|
||||||
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock, LogicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled,
|
|
||||||
equals(<KeyboardLockMode>{}));
|
|
||||||
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.numpad1, platform: 'windows');
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed,
|
|
||||||
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed,
|
|
||||||
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled,
|
|
||||||
equals(<KeyboardLockMode>{}));
|
|
||||||
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.numLock, platform: 'windows');
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed,
|
|
||||||
equals(<PhysicalKeyboardKey>{}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed,
|
|
||||||
equals(<LogicalKeyboardKey>{}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled,
|
|
||||||
equals(<KeyboardLockMode>{}));
|
|
||||||
}, variant: KeySimulatorTransitModeVariant.keyDataThenRawKeyData());
|
|
||||||
|
|
||||||
testWidgets('Dispatch events to all handlers', (WidgetTester tester) async {
|
|
||||||
final FocusNode focusNode = FocusNode();
|
|
||||||
final List<int> logs = <int>[];
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
KeyboardListener(
|
|
||||||
autofocus: true,
|
|
||||||
focusNode: focusNode,
|
|
||||||
child: Container(),
|
|
||||||
onKeyEvent: (KeyEvent event) {
|
|
||||||
logs.add(1);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Only the Service binding handler.
|
|
||||||
|
|
||||||
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA),
|
|
||||||
false);
|
|
||||||
expect(logs, <int>[1]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
// Add a handler.
|
|
||||||
|
|
||||||
bool handler2Result = false;
|
|
||||||
bool handler2(KeyEvent event) {
|
|
||||||
logs.add(2);
|
|
||||||
return handler2Result;
|
|
||||||
}
|
|
||||||
HardwareKeyboard.instance.addHandler(handler2);
|
|
||||||
|
|
||||||
expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA),
|
|
||||||
false);
|
|
||||||
expect(logs, <int>[2, 1]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
handler2Result = true;
|
|
||||||
|
|
||||||
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA),
|
|
||||||
true);
|
|
||||||
expect(logs, <int>[2, 1]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
// Add another handler.
|
|
||||||
|
|
||||||
handler2Result = false;
|
|
||||||
bool handler3Result = false;
|
|
||||||
bool handler3(KeyEvent event) {
|
|
||||||
logs.add(3);
|
|
||||||
return handler3Result;
|
|
||||||
}
|
|
||||||
HardwareKeyboard.instance.addHandler(handler3);
|
|
||||||
|
|
||||||
expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA),
|
|
||||||
false);
|
|
||||||
expect(logs, <int>[2, 3, 1]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
handler2Result = true;
|
|
||||||
|
|
||||||
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA),
|
|
||||||
true);
|
|
||||||
expect(logs, <int>[2, 3, 1]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
handler3Result = true;
|
|
||||||
|
|
||||||
expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA),
|
|
||||||
true);
|
|
||||||
expect(logs, <int>[2, 3, 1]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
// Add handler2 again.
|
|
||||||
|
|
||||||
HardwareKeyboard.instance.addHandler(handler2);
|
|
||||||
|
|
||||||
handler3Result = false;
|
|
||||||
handler2Result = false;
|
|
||||||
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA),
|
|
||||||
false);
|
|
||||||
expect(logs, <int>[2, 3, 2, 1]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
handler2Result = true;
|
|
||||||
expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA),
|
|
||||||
true);
|
|
||||||
expect(logs, <int>[2, 3, 2, 1]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
// Remove handler2 once.
|
|
||||||
|
|
||||||
HardwareKeyboard.instance.removeHandler(handler2);
|
|
||||||
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA),
|
|
||||||
true);
|
|
||||||
expect(logs, <int>[3, 2, 1]);
|
|
||||||
logs.clear();
|
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
|
||||||
}
|
|
@ -201,7 +201,7 @@ void main() {
|
|||||||
testWidgets('keysPressed modifiers are synchronized with key events on macOS', (WidgetTester tester) async {
|
testWidgets('keysPressed modifiers are synchronized with key events on macOS', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'macos',
|
platform: 'macos',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -226,7 +226,7 @@ void main() {
|
|||||||
testWidgets('keysPressed modifiers are synchronized with key events on iOS', (WidgetTester tester) async {
|
testWidgets('keysPressed modifiers are synchronized with key events on iOS', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'ios',
|
platform: 'ios',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -251,7 +251,7 @@ void main() {
|
|||||||
testWidgets('keysPressed modifiers are synchronized with key events on Windows', (WidgetTester tester) async {
|
testWidgets('keysPressed modifiers are synchronized with key events on Windows', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'windows',
|
platform: 'windows',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -276,7 +276,7 @@ void main() {
|
|||||||
testWidgets('keysPressed modifiers are synchronized with key events on android', (WidgetTester tester) async {
|
testWidgets('keysPressed modifiers are synchronized with key events on android', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'android',
|
platform: 'android',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -301,7 +301,7 @@ void main() {
|
|||||||
testWidgets('keysPressed modifiers are synchronized with key events on fuchsia', (WidgetTester tester) async {
|
testWidgets('keysPressed modifiers are synchronized with key events on fuchsia', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'fuchsia',
|
platform: 'fuchsia',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -326,7 +326,7 @@ void main() {
|
|||||||
testWidgets('keysPressed modifiers are synchronized with key events on Linux GLFW', (WidgetTester tester) async {
|
testWidgets('keysPressed modifiers are synchronized with key events on Linux GLFW', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'linux',
|
platform: 'linux',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -359,7 +359,7 @@ void main() {
|
|||||||
// Generate the data for a regular key down event. Change the modifiers so
|
// Generate the data for a regular key down event. Change the modifiers so
|
||||||
// that they show the shift key as already down when this event is
|
// that they show the shift key as already down when this event is
|
||||||
// received, but it's not in keysPressed yet.
|
// received, but it's not in keysPressed yet.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'web',
|
platform: 'web',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -384,7 +384,7 @@ void main() {
|
|||||||
// Generate the data for a regular key up event. Don't set the modifiers
|
// Generate the data for a regular key up event. Don't set the modifiers
|
||||||
// for shift so that they show the shift key as already up when this event
|
// for shift so that they show the shift key as already up when this event
|
||||||
// is received, and it's in keysPressed.
|
// is received, and it's in keysPressed.
|
||||||
final Map<String, dynamic> data2 = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data2 = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'web',
|
platform: 'web',
|
||||||
isDown: false,
|
isDown: false,
|
||||||
@ -403,7 +403,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Press right modifier key
|
// Press right modifier key
|
||||||
final Map<String, dynamic> data3 = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data3 = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.shiftRight,
|
LogicalKeyboardKey.shiftRight,
|
||||||
platform: 'web',
|
platform: 'web',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -425,7 +425,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Release the key
|
// Release the key
|
||||||
final Map<String, dynamic> data4 = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data4 = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.shiftRight,
|
LogicalKeyboardKey.shiftRight,
|
||||||
platform: 'web',
|
platform: 'web',
|
||||||
isDown: false,
|
isDown: false,
|
||||||
@ -447,7 +447,7 @@ void main() {
|
|||||||
testWidgets('sided modifiers without a side set return all sides on Android', (WidgetTester tester) async {
|
testWidgets('sided modifiers without a side set return all sides on Android', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'android',
|
platform: 'android',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -485,7 +485,7 @@ void main() {
|
|||||||
testWidgets('sided modifiers without a side set return all sides on macOS', (WidgetTester tester) async {
|
testWidgets('sided modifiers without a side set return all sides on macOS', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'macos',
|
platform: 'macos',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -523,7 +523,7 @@ void main() {
|
|||||||
testWidgets('sided modifiers without a side set return all sides on iOS', (WidgetTester tester) async {
|
testWidgets('sided modifiers without a side set return all sides on iOS', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'ios',
|
platform: 'ios',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -561,7 +561,7 @@ void main() {
|
|||||||
testWidgets('sided modifiers without a side set return all sides on Windows', (WidgetTester tester) async {
|
testWidgets('sided modifiers without a side set return all sides on Windows', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'windows',
|
platform: 'windows',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -597,7 +597,7 @@ void main() {
|
|||||||
testWidgets('sided modifiers without a side set return all sides on Linux GLFW', (WidgetTester tester) async {
|
testWidgets('sided modifiers without a side set return all sides on Linux GLFW', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'linux',
|
platform: 'linux',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -636,7 +636,7 @@ void main() {
|
|||||||
testWidgets('sided modifiers without a side set return left sides on web', (WidgetTester tester) async {
|
testWidgets('sided modifiers without a side set return left sides on web', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'web',
|
platform: 'web',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -703,70 +703,6 @@ void main() {
|
|||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Dispatch events to all handlers', (WidgetTester tester) async {
|
|
||||||
final FocusNode focusNode = FocusNode();
|
|
||||||
final List<int> logs = <int>[];
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
RawKeyboardListener(
|
|
||||||
autofocus: true,
|
|
||||||
focusNode: focusNode,
|
|
||||||
child: Container(),
|
|
||||||
onKey: (RawKeyEvent event) {
|
|
||||||
logs.add(1);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Only the Service binding handler.
|
|
||||||
|
|
||||||
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA),
|
|
||||||
false);
|
|
||||||
expect(logs, <int>[1]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
// Add a handler.
|
|
||||||
|
|
||||||
void handler2(RawKeyEvent event) {
|
|
||||||
logs.add(2);
|
|
||||||
}
|
|
||||||
RawKeyboard.instance.addListener(handler2);
|
|
||||||
|
|
||||||
expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA),
|
|
||||||
false);
|
|
||||||
expect(logs, <int>[1, 2]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
// Add another handler.
|
|
||||||
|
|
||||||
void handler3(RawKeyEvent event) {
|
|
||||||
logs.add(3);
|
|
||||||
}
|
|
||||||
RawKeyboard.instance.addListener(handler3);
|
|
||||||
|
|
||||||
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA),
|
|
||||||
false);
|
|
||||||
expect(logs, <int>[1, 2, 3]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
// Add handler2 again.
|
|
||||||
|
|
||||||
RawKeyboard.instance.addListener(handler2);
|
|
||||||
|
|
||||||
expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA),
|
|
||||||
false);
|
|
||||||
expect(logs, <int>[1, 2, 3, 2]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
// Remove handler2 once.
|
|
||||||
|
|
||||||
RawKeyboard.instance.removeListener(handler2);
|
|
||||||
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA),
|
|
||||||
false);
|
|
||||||
expect(logs, <int>[1, 3, 2]);
|
|
||||||
logs.clear();
|
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group('RawKeyEventDataAndroid', () {
|
group('RawKeyEventDataAndroid', () {
|
||||||
@ -1005,7 +941,7 @@ void main() {
|
|||||||
testWidgets('Key events are responded to correctly.', (WidgetTester tester) async {
|
testWidgets('Key events are responded to correctly.', (WidgetTester tester) async {
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
// Generate the data for a regular key down event.
|
// Generate the data for a regular key down event.
|
||||||
final Map<String, dynamic> data = KeyEventSimulator.getRawKeyData(
|
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||||
LogicalKeyboardKey.keyA,
|
LogicalKeyboardKey.keyA,
|
||||||
platform: 'android',
|
platform: 'android',
|
||||||
isDown: true,
|
isDown: true,
|
||||||
@ -1019,7 +955,6 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
expect(message, equals(<String, dynamic>{ 'handled': false }));
|
expect(message, equals(<String, dynamic>{ 'handled': false }));
|
||||||
message = null;
|
|
||||||
|
|
||||||
// Set up a widget that will receive focused text events.
|
// Set up a widget that will receive focused text events.
|
||||||
final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
|
final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
|
||||||
|
@ -143,7 +143,7 @@ void main() {
|
|||||||
await tester.sendKeyEvent(LogicalKeyboardKey.gameButtonA);
|
await tester.sendKeyEvent(LogicalKeyboardKey.gameButtonA);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(checked, isTrue);
|
expect(checked, isTrue);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
group('error control test', () {
|
group('error control test', () {
|
||||||
Future<void> expectFlutterError({
|
Future<void> expectFlutterError({
|
||||||
|
@ -114,5 +114,5 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(leftCalled, isFalse);
|
expect(leftCalled, isFalse);
|
||||||
expect(rightCalled, isTrue);
|
expect(rightCalled, isTrue);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
}
|
}
|
||||||
|
@ -328,8 +328,6 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Cursor animation restarts when it is moved using keys on desktop', (WidgetTester tester) async {
|
testWidgets('Cursor animation restarts when it is moved using keys on desktop', (WidgetTester tester) async {
|
||||||
debugDefaultTargetPlatformOverride = TargetPlatform.macOS;
|
|
||||||
|
|
||||||
const String testText = 'Some text long enough to move the cursor around';
|
const String testText = 'Some text long enough to move the cursor around';
|
||||||
final TextEditingController controller = TextEditingController(text: testText);
|
final TextEditingController controller = TextEditingController(text: testText);
|
||||||
final Widget widget = MaterialApp(
|
final Widget widget = MaterialApp(
|
||||||
@ -402,9 +400,7 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 1));
|
await tester.pump(const Duration(milliseconds: 1));
|
||||||
expect(renderEditable.cursorColor!.alpha, 0);
|
expect(renderEditable.cursorColor!.alpha, 0);
|
||||||
expect(renderEditable, paintsExactlyCountTimes(#drawRect, 0));
|
expect(renderEditable, paintsExactlyCountTimes(#drawRect, 0));
|
||||||
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.macOS }));
|
||||||
debugDefaultTargetPlatformOverride = null;
|
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
|
||||||
|
|
||||||
testWidgets('Cursor does not show when showCursor set to false', (WidgetTester tester) async {
|
testWidgets('Cursor does not show when showCursor set to false', (WidgetTester tester) async {
|
||||||
const Widget widget = MaterialApp(
|
const Widget widget = MaterialApp(
|
||||||
|
@ -4864,23 +4864,8 @@ void main() {
|
|||||||
expect(controller.text, isEmpty, reason: 'on $platform');
|
expect(controller.text, isEmpty, reason: 'on $platform');
|
||||||
}
|
}
|
||||||
|
|
||||||
testWidgets('keyboard text selection works (RawKeyEvent)', (WidgetTester tester) async {
|
testWidgets('keyboard text selection works', (WidgetTester tester) async {
|
||||||
debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.rawKeyData;
|
|
||||||
|
|
||||||
await testTextEditing(tester, targetPlatform: defaultTargetPlatform);
|
await testTextEditing(tester, targetPlatform: defaultTargetPlatform);
|
||||||
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = null;
|
|
||||||
|
|
||||||
// On web, using keyboard for selection is handled by the browser.
|
|
||||||
}, skip: kIsWeb, variant: TargetPlatformVariant.all());
|
|
||||||
|
|
||||||
testWidgets('keyboard text selection works (ui.KeyData then RawKeyEvent)', (WidgetTester tester) async {
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData;
|
|
||||||
|
|
||||||
await testTextEditing(tester, targetPlatform: defaultTargetPlatform);
|
|
||||||
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = null;
|
|
||||||
|
|
||||||
// On web, using keyboard for selection is handled by the browser.
|
// On web, using keyboard for selection is handled by the browser.
|
||||||
}, skip: kIsWeb, variant: TargetPlatformVariant.all());
|
}, skip: kIsWeb, variant: TargetPlatformVariant.all());
|
||||||
|
|
||||||
@ -7690,7 +7675,7 @@ void main() {
|
|||||||
expect(controller.selection.isCollapsed, true);
|
expect(controller.selection.isCollapsed, true);
|
||||||
expect(controller.selection.baseOffset, 1);
|
expect(controller.selection.baseOffset, 1);
|
||||||
}
|
}
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('the toolbar is disposed when selection changes and there is no selectionControls', (WidgetTester tester) async {
|
testWidgets('the toolbar is disposed when selection changes and there is no selectionControls', (WidgetTester tester) async {
|
||||||
late StateSetter setState;
|
late StateSetter setState;
|
||||||
|
@ -165,101 +165,7 @@ void main() {
|
|||||||
'hasPrimaryFocus: false',
|
'hasPrimaryFocus: false',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('onKeyEvent and onKey correctly cooperate', (WidgetTester tester) async {
|
|
||||||
final FocusNode focusNode = FocusNode(debugLabel: 'Test Node 3');
|
|
||||||
List<List<KeyEventResult>> results = <List<KeyEventResult>>[
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
];
|
|
||||||
final List<int> logs = <int>[];
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
focusNode: FocusNode(debugLabel: 'Test Node 1'),
|
|
||||||
onKeyEvent: (_, KeyEvent event) {
|
|
||||||
logs.add(0);
|
|
||||||
return results[0][0];
|
|
||||||
},
|
|
||||||
onKey: (_, RawKeyEvent event) {
|
|
||||||
logs.add(1);
|
|
||||||
return results[0][1];
|
|
||||||
},
|
|
||||||
child: Focus(
|
|
||||||
focusNode: FocusNode(debugLabel: 'Test Node 2'),
|
|
||||||
onKeyEvent: (_, KeyEvent event) {
|
|
||||||
logs.add(10);
|
|
||||||
return results[1][0];
|
|
||||||
},
|
|
||||||
onKey: (_, RawKeyEvent event) {
|
|
||||||
logs.add(11);
|
|
||||||
return results[1][1];
|
|
||||||
},
|
|
||||||
child: Focus(
|
|
||||||
focusNode: focusNode,
|
|
||||||
onKeyEvent: (_, KeyEvent event) {
|
|
||||||
logs.add(20);
|
|
||||||
return results[2][0];
|
|
||||||
},
|
|
||||||
onKey: (_, RawKeyEvent event) {
|
|
||||||
logs.add(21);
|
|
||||||
return results[2][1];
|
|
||||||
},
|
|
||||||
child: const SizedBox(width: 200, height: 100),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
focusNode.requestFocus();
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// All ignored.
|
|
||||||
results = <List<KeyEventResult>>[
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
];
|
|
||||||
expect(await simulateKeyDownEvent(LogicalKeyboardKey.digit1),
|
|
||||||
false);
|
|
||||||
expect(logs, <int>[20, 21, 10, 11, 0, 1]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
// The onKeyEvent should be able to stop propagation.
|
|
||||||
results = <List<KeyEventResult>>[
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
<KeyEventResult>[KeyEventResult.handled, KeyEventResult.ignored],
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
];
|
|
||||||
expect(await simulateKeyUpEvent(LogicalKeyboardKey.digit1),
|
|
||||||
true);
|
|
||||||
expect(logs, <int>[20, 21, 10, 11]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
// The onKey should be able to stop propagation.
|
|
||||||
results = <List<KeyEventResult>>[
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.handled],
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
];
|
|
||||||
expect(await simulateKeyDownEvent(LogicalKeyboardKey.digit1),
|
|
||||||
true);
|
|
||||||
expect(logs, <int>[20, 21, 10, 11]);
|
|
||||||
logs.clear();
|
|
||||||
|
|
||||||
// KeyEventResult.skipRemainingHandlers works.
|
|
||||||
results = <List<KeyEventResult>>[
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
<KeyEventResult>[KeyEventResult.skipRemainingHandlers, KeyEventResult.ignored],
|
|
||||||
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
|
|
||||||
];
|
|
||||||
expect(await simulateKeyUpEvent(LogicalKeyboardKey.digit1),
|
|
||||||
false);
|
|
||||||
expect(logs, <int>[20, 21, 10, 11]);
|
|
||||||
logs.clear();
|
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group(FocusScopeNode, () {
|
group(FocusScopeNode, () {
|
||||||
|
|
||||||
testWidgets('Can setFirstFocus on a scope with no manager.', (WidgetTester tester) async {
|
testWidgets('Can setFirstFocus on a scope with no manager.', (WidgetTester tester) async {
|
||||||
@ -1029,7 +935,7 @@ void main() {
|
|||||||
// Since none of the focused nodes handle this event, nothing should
|
// Since none of the focused nodes handle this event, nothing should
|
||||||
// receive it.
|
// receive it.
|
||||||
expect(receivedAnEvent, isEmpty);
|
expect(receivedAnEvent, isEmpty);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Initial highlight mode guesses correctly.', (WidgetTester tester) async {
|
testWidgets('Initial highlight mode guesses correctly.', (WidgetTester tester) async {
|
||||||
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.automatic;
|
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.automatic;
|
||||||
|
@ -1678,7 +1678,7 @@ void main() {
|
|||||||
expect(Focus.of(lowerLeftKey.currentContext!).hasPrimaryFocus, isTrue);
|
expect(Focus.of(lowerLeftKey.currentContext!).hasPrimaryFocus, isTrue);
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||||
expect(Focus.of(upperLeftKey.currentContext!).hasPrimaryFocus, isTrue);
|
expect(Focus.of(upperLeftKey.currentContext!).hasPrimaryFocus, isTrue);
|
||||||
}, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/35347
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
|
||||||
|
|
||||||
testWidgets('Focus traversal inside a vertical scrollable scrolls to stay visible.', (WidgetTester tester) async {
|
testWidgets('Focus traversal inside a vertical scrollable scrolls to stay visible.', (WidgetTester tester) async {
|
||||||
final List<int> items = List<int>.generate(11, (int index) => index).toList();
|
final List<int> items = List<int>.generate(11, (int index) => index).toList();
|
||||||
@ -1776,7 +1776,7 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(topNode.hasPrimaryFocus, isTrue);
|
expect(topNode.hasPrimaryFocus, isTrue);
|
||||||
expect(controller.offset, equals(0.0));
|
expect(controller.offset, equals(0.0));
|
||||||
}, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/35347
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
|
||||||
|
|
||||||
testWidgets('Focus traversal inside a horizontal scrollable scrolls to stay visible.', (WidgetTester tester) async {
|
testWidgets('Focus traversal inside a horizontal scrollable scrolls to stay visible.', (WidgetTester tester) async {
|
||||||
final List<int> items = List<int>.generate(11, (int index) => index).toList();
|
final List<int> items = List<int>.generate(11, (int index) => index).toList();
|
||||||
@ -1874,7 +1874,7 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(leftNode.hasPrimaryFocus, isTrue);
|
expect(leftNode.hasPrimaryFocus, isTrue);
|
||||||
expect(controller.offset, equals(0.0));
|
expect(controller.offset, equals(0.0));
|
||||||
}, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/35347
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
|
||||||
|
|
||||||
testWidgets('Arrow focus traversal actions can be re-enabled for text fields.', (WidgetTester tester) async {
|
testWidgets('Arrow focus traversal actions can be re-enabled for text fields.', (WidgetTester tester) async {
|
||||||
final GlobalKey upperLeftKey = GlobalKey(debugLabel: 'upperLeftKey');
|
final GlobalKey upperLeftKey = GlobalKey(debugLabel: 'upperLeftKey');
|
||||||
@ -1997,10 +1997,10 @@ void main() {
|
|||||||
expect(focusNodeUpperLeft.hasPrimaryFocus, isTrue);
|
expect(focusNodeUpperLeft.hasPrimaryFocus, isTrue);
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
||||||
expect(focusNodeUpperLeft.hasPrimaryFocus, isTrue);
|
expect(focusNodeUpperLeft.hasPrimaryFocus, isTrue);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Focus traversal does not break when no focusable is available on a MaterialApp', (WidgetTester tester) async {
|
testWidgets('Focus traversal does not break when no focusable is available on a MaterialApp', (WidgetTester tester) async {
|
||||||
final List<Object> events = <Object>[];
|
final List<RawKeyEvent> events = <RawKeyEvent>[];
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(home: Container()));
|
await tester.pumpWidget(MaterialApp(home: Container()));
|
||||||
|
|
||||||
@ -2013,7 +2013,7 @@ void main() {
|
|||||||
await tester.idle();
|
await tester.idle();
|
||||||
|
|
||||||
expect(events.length, 2);
|
expect(events.length, 2);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Focus traversal does not throw when no focusable is available in a group', (WidgetTester tester) async {
|
testWidgets('Focus traversal does not throw when no focusable is available in a group', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(const MaterialApp(home: Scaffold(body: ListTile(title: Text('title')))));
|
await tester.pumpWidget(const MaterialApp(home: Scaffold(body: ListTile(title: Text('title')))));
|
||||||
@ -2047,7 +2047,7 @@ void main() {
|
|||||||
await tester.idle();
|
await tester.idle();
|
||||||
|
|
||||||
expect(events.length, 2);
|
expect(events.length, 2);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
});
|
});
|
||||||
group(FocusTraversalGroup, () {
|
group(FocusTraversalGroup, () {
|
||||||
testWidgets("Focus traversal group doesn't introduce a Semantics node", (WidgetTester tester) async {
|
testWidgets("Focus traversal group doesn't introduce a Semantics node", (WidgetTester tester) async {
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
// Copyright 2014 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.
|
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Can dispose without keyboard', (WidgetTester tester) async {
|
|
||||||
final FocusNode focusNode = FocusNode();
|
|
||||||
await tester.pumpWidget(KeyboardListener(focusNode: focusNode, onKeyEvent: null, child: Container()));
|
|
||||||
await tester.pumpWidget(KeyboardListener(focusNode: focusNode, onKeyEvent: null, child: Container()));
|
|
||||||
await tester.pumpWidget(Container());
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Fuchsia key event', (WidgetTester tester) async {
|
|
||||||
final List<KeyEvent> events = <KeyEvent>[];
|
|
||||||
|
|
||||||
final FocusNode focusNode = FocusNode();
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
KeyboardListener(
|
|
||||||
focusNode: focusNode,
|
|
||||||
onKeyEvent: events.add,
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
focusNode.requestFocus();
|
|
||||||
await tester.idle();
|
|
||||||
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia');
|
|
||||||
await tester.idle();
|
|
||||||
|
|
||||||
expect(events.length, 2);
|
|
||||||
expect(events[0], isA<KeyDownEvent>());
|
|
||||||
expect(events[0].physicalKey, PhysicalKeyboardKey.metaLeft);
|
|
||||||
expect(events[0].logicalKey, LogicalKeyboardKey.metaLeft);
|
|
||||||
|
|
||||||
await tester.pumpWidget(Container());
|
|
||||||
focusNode.dispose();
|
|
||||||
}, skip: isBrowser); // This is a Fuchsia-specific test.
|
|
||||||
|
|
||||||
testWidgets('Web key event', (WidgetTester tester) async {
|
|
||||||
final List<KeyEvent> events = <KeyEvent>[];
|
|
||||||
|
|
||||||
final FocusNode focusNode = FocusNode();
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
KeyboardListener(
|
|
||||||
focusNode: focusNode,
|
|
||||||
onKeyEvent: events.add,
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
focusNode.requestFocus();
|
|
||||||
await tester.idle();
|
|
||||||
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'web');
|
|
||||||
await tester.idle();
|
|
||||||
|
|
||||||
expect(events.length, 2);
|
|
||||||
expect(events[0], isA<KeyDownEvent>());
|
|
||||||
expect(events[0].physicalKey, PhysicalKeyboardKey.metaLeft);
|
|
||||||
expect(events[0].logicalKey, LogicalKeyboardKey.metaLeft);
|
|
||||||
|
|
||||||
await tester.pumpWidget(Container());
|
|
||||||
focusNode.dispose();
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Defunct listeners do not receive events', (WidgetTester tester) async {
|
|
||||||
final List<KeyEvent> events = <KeyEvent>[];
|
|
||||||
|
|
||||||
final FocusNode focusNode = FocusNode();
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
KeyboardListener(
|
|
||||||
focusNode: focusNode,
|
|
||||||
onKeyEvent: events.add,
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
focusNode.requestFocus();
|
|
||||||
await tester.idle();
|
|
||||||
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia');
|
|
||||||
await tester.idle();
|
|
||||||
|
|
||||||
expect(events.length, 2);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.pumpWidget(Container());
|
|
||||||
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia');
|
|
||||||
|
|
||||||
await tester.idle();
|
|
||||||
|
|
||||||
expect(events.length, 0);
|
|
||||||
|
|
||||||
await tester.pumpWidget(Container());
|
|
||||||
focusNode.dispose();
|
|
||||||
});
|
|
||||||
}
|
|
@ -520,7 +520,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(controller.position.pixels, equals(0.0));
|
expect(controller.position.pixels, equals(0.0));
|
||||||
expect(tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)));
|
expect(tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)));
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Vertical scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async {
|
testWidgets('Vertical scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async {
|
||||||
final ScrollController controller = ScrollController();
|
final ScrollController controller = ScrollController();
|
||||||
@ -571,7 +571,7 @@ void main() {
|
|||||||
await tester.sendKeyEvent(LogicalKeyboardKey.pageUp);
|
await tester.sendKeyEvent(LogicalKeyboardKey.pageUp);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)));
|
expect(tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)));
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Horizontal scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async {
|
testWidgets('Horizontal scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async {
|
||||||
final ScrollController controller = ScrollController();
|
final ScrollController controller = ScrollController();
|
||||||
@ -617,7 +617,7 @@ void main() {
|
|||||||
await tester.sendKeyUpEvent(modifierKey);
|
await tester.sendKeyUpEvent(modifierKey);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 0.0, 50.0, 600.0)));
|
expect(tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 0.0, 50.0, 600.0)));
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Horizontal scrollables are scrolled the correct direction in RTL locales.', (WidgetTester tester) async {
|
testWidgets('Horizontal scrollables are scrolled the correct direction in RTL locales.', (WidgetTester tester) async {
|
||||||
final ScrollController controller = ScrollController();
|
final ScrollController controller = ScrollController();
|
||||||
@ -666,7 +666,7 @@ void main() {
|
|||||||
await tester.sendKeyUpEvent(modifierKey);
|
await tester.sendKeyUpEvent(modifierKey);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(750.0, 0.0, 800.0, 600.0)));
|
expect(tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(750.0, 0.0, 800.0, 600.0)));
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Reversed vertical scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async {
|
testWidgets('Reversed vertical scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async {
|
||||||
final ScrollController controller = ScrollController();
|
final ScrollController controller = ScrollController();
|
||||||
@ -720,7 +720,7 @@ void main() {
|
|||||||
await tester.sendKeyEvent(LogicalKeyboardKey.pageDown);
|
await tester.sendKeyEvent(LogicalKeyboardKey.pageDown);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 550.0, 800.0, 600.0)));
|
expect(tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 550.0, 800.0, 600.0)));
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Reversed horizontal scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async {
|
testWidgets('Reversed horizontal scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async {
|
||||||
final ScrollController controller = ScrollController();
|
final ScrollController controller = ScrollController();
|
||||||
@ -768,7 +768,7 @@ void main() {
|
|||||||
if (!kIsWeb)
|
if (!kIsWeb)
|
||||||
await tester.sendKeyUpEvent(modifierKey);
|
await tester.sendKeyUpEvent(modifierKey);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Custom scrollables with a center sliver are scrolled when activated via keyboard.', (WidgetTester tester) async {
|
testWidgets('Custom scrollables with a center sliver are scrolled when activated via keyboard.', (WidgetTester tester) async {
|
||||||
final ScrollController controller = ScrollController();
|
final ScrollController controller = ScrollController();
|
||||||
@ -828,7 +828,7 @@ void main() {
|
|||||||
// Goes up two past "center" where it started, so negative.
|
// Goes up two past "center" where it started, so negative.
|
||||||
expect(controller.position.pixels, equals(-100.0));
|
expect(controller.position.pixels, equals(-100.0));
|
||||||
expect(tester.getRect(find.byKey(const ValueKey<String>('Item 10'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 100.0, 800.0, 200.0)));
|
expect(tester.getRect(find.byKey(const ValueKey<String>('Item 10'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 100.0, 800.0, 200.0)));
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Can recommendDeferredLoadingForContext - animation', (WidgetTester tester) async {
|
testWidgets('Can recommendDeferredLoadingForContext - animation', (WidgetTester tester) async {
|
||||||
final List<String> widgetTracker = <String>[];
|
final List<String> widgetTracker = <String>[];
|
||||||
|
@ -1570,7 +1570,7 @@ void main() {
|
|||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft);
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft);
|
||||||
expect(controller.selection.extentOffset - controller.selection.baseOffset, -1);
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, -1);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Shift test 2', (WidgetTester tester) async {
|
testWidgets('Shift test 2', (WidgetTester tester) async {
|
||||||
await setupWidget(tester, 'abcdefghi');
|
await setupWidget(tester, 'abcdefghi');
|
||||||
@ -1582,7 +1582,7 @@ void main() {
|
|||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowRight);
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowRight);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(controller.selection.extentOffset - controller.selection.baseOffset, 1);
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 1);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Control Shift test', (WidgetTester tester) async {
|
testWidgets('Control Shift test', (WidgetTester tester) async {
|
||||||
await setupWidget(tester, 'their big house');
|
await setupWidget(tester, 'their big house');
|
||||||
@ -1594,7 +1594,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(controller.selection.extentOffset - controller.selection.baseOffset, -5);
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, -5);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Down and up test', (WidgetTester tester) async {
|
testWidgets('Down and up test', (WidgetTester tester) async {
|
||||||
await setupWidget(tester, 'a big house');
|
await setupWidget(tester, 'a big house');
|
||||||
@ -1612,7 +1612,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(controller.selection.extentOffset - controller.selection.baseOffset, 0);
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, 0);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Down and up test 2', (WidgetTester tester) async {
|
testWidgets('Down and up test 2', (WidgetTester tester) async {
|
||||||
await setupWidget(tester, 'a big house\njumped over a mouse\nOne more line yay');
|
await setupWidget(tester, 'a big house\njumped over a mouse\nOne more line yay');
|
||||||
@ -1663,7 +1663,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(controller.selection.extentOffset - controller.selection.baseOffset, -5);
|
expect(controller.selection.extentOffset - controller.selection.baseOffset, -5);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Copy test', (WidgetTester tester) async {
|
testWidgets('Copy test', (WidgetTester tester) async {
|
||||||
@ -1722,7 +1722,7 @@ void main() {
|
|||||||
|
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Select all test', (WidgetTester tester) async {
|
testWidgets('Select all test', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
@ -1757,7 +1757,7 @@ void main() {
|
|||||||
|
|
||||||
expect(controller.selection.baseOffset, 0);
|
expect(controller.selection.baseOffset, 0);
|
||||||
expect(controller.selection.extentOffset, 31);
|
expect(controller.selection.extentOffset, 31);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('keyboard selection should call onSelectionChanged', (WidgetTester tester) async {
|
testWidgets('keyboard selection should call onSelectionChanged', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
@ -1804,7 +1804,7 @@ void main() {
|
|||||||
expect(newSelection!.extentOffset, i + 1);
|
expect(newSelection!.extentOffset, i + 1);
|
||||||
newSelection = null;
|
newSelection = null;
|
||||||
}
|
}
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Changing positions of selectable text', (WidgetTester tester) async {
|
testWidgets('Changing positions of selectable text', (WidgetTester tester) async {
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
@ -1894,7 +1894,7 @@ void main() {
|
|||||||
c1 = editableTextWidget.controller;
|
c1 = editableTextWidget.controller;
|
||||||
|
|
||||||
expect(c1.selection.extentOffset - c1.selection.baseOffset, -6);
|
expect(c1.selection.extentOffset - c1.selection.baseOffset, -6);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
|
|
||||||
testWidgets('Changing focus test', (WidgetTester tester) async {
|
testWidgets('Changing focus test', (WidgetTester tester) async {
|
||||||
@ -1964,7 +1964,7 @@ void main() {
|
|||||||
|
|
||||||
expect(c1.selection.extentOffset - c1.selection.baseOffset, 0);
|
expect(c1.selection.extentOffset - c1.selection.baseOffset, 0);
|
||||||
expect(c2.selection.extentOffset - c2.selection.baseOffset, -5);
|
expect(c2.selection.extentOffset - c2.selection.baseOffset, -5);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('Caret works when maxLines is null', (WidgetTester tester) async {
|
testWidgets('Caret works when maxLines is null', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
|
@ -435,33 +435,7 @@ void main() {
|
|||||||
invoked = 0;
|
invoked = 0;
|
||||||
|
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('handles repeated events', (WidgetTester tester) async {
|
|
||||||
int invoked = 0;
|
|
||||||
await tester.pumpWidget(activatorTester(
|
|
||||||
const SingleActivator(
|
|
||||||
LogicalKeyboardKey.keyC,
|
|
||||||
control: true,
|
|
||||||
),
|
|
||||||
(Intent intent) { invoked += 1; },
|
|
||||||
));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// LCtrl -> KeyC: Accept
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
|
||||||
expect(invoked, 0);
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
|
|
||||||
expect(invoked, 1);
|
|
||||||
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyC);
|
|
||||||
expect(invoked, 2);
|
|
||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
|
|
||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
|
|
||||||
expect(invoked, 2);
|
|
||||||
invoked = 0;
|
|
||||||
|
|
||||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
|
||||||
|
|
||||||
testWidgets('handles Shift-Ctrl-C', (WidgetTester tester) async {
|
testWidgets('handles Shift-Ctrl-C', (WidgetTester tester) async {
|
||||||
int invoked = 0;
|
int invoked = 0;
|
||||||
@ -1101,27 +1075,7 @@ void main() {
|
|||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
|
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
|
||||||
expect(invoked, 1);
|
expect(invoked, 1);
|
||||||
invoked = 0;
|
invoked = 0;
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
});
|
||||||
|
|
||||||
testWidgets('handles repeated events', (WidgetTester tester) async {
|
|
||||||
int invoked = 0;
|
|
||||||
await tester.pumpWidget(activatorTester(
|
|
||||||
const CharacterActivator('?'),
|
|
||||||
(Intent intent) { invoked += 1; },
|
|
||||||
));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Press KeyC: Accepted by DumbLogicalActivator
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.slash, character: '?');
|
|
||||||
expect(invoked, 1);
|
|
||||||
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.slash, character: '?');
|
|
||||||
expect(invoked, 2);
|
|
||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.slash);
|
|
||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
|
|
||||||
expect(invoked, 2);
|
|
||||||
invoked = 0;
|
|
||||||
}, variant: KeySimulatorTransitModeVariant.all());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group('CallbackShortcuts', () {
|
group('CallbackShortcuts', () {
|
||||||
|
@ -863,9 +863,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
|||||||
assert(debugAssertAllSchedulerVarsUnset(
|
assert(debugAssertAllSchedulerVarsUnset(
|
||||||
'The value of a scheduler debug variable was changed by the test.',
|
'The value of a scheduler debug variable was changed by the test.',
|
||||||
));
|
));
|
||||||
assert(debugAssertAllServicesVarsUnset(
|
|
||||||
'The value of a services debug variable was changed by the test.',
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _verifyAutoUpdateGoldensUnset(bool valueBeforeTest) {
|
void _verifyAutoUpdateGoldensUnset(bool valueBeforeTest) {
|
||||||
@ -932,10 +929,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
|||||||
// tests.
|
// tests.
|
||||||
// ignore: invalid_use_of_visible_for_testing_member
|
// ignore: invalid_use_of_visible_for_testing_member
|
||||||
RawKeyboard.instance.clearKeysPressed();
|
RawKeyboard.instance.clearKeysPressed();
|
||||||
// ignore: invalid_use_of_visible_for_testing_member
|
|
||||||
HardwareKeyboard.instance.clearState();
|
|
||||||
// ignore: invalid_use_of_visible_for_testing_member
|
|
||||||
keyEventManager.clearState();
|
|
||||||
assert(!RendererBinding.instance!.mouseTracker.mouseIsConnected,
|
assert(!RendererBinding.instance!.mouseTracker.mouseIsConnected,
|
||||||
'The MouseTracker thinks that there is still a mouse connected, which indicates that a '
|
'The MouseTracker thinks that there is still a mouse connected, which indicates that a '
|
||||||
'test has not removed the mouse pointer which it added. Call removePointer on the '
|
'test has not removed the mouse pointer which it added. Call removePointer on the '
|
||||||
|
@ -973,7 +973,7 @@ abstract class WidgetController {
|
|||||||
return box.size;
|
return box.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simulates sending physical key down and up events.
|
/// Simulates sending physical key down and up events through the system channel.
|
||||||
///
|
///
|
||||||
/// This only simulates key events coming from a physical keyboard, not from a
|
/// This only simulates key events coming from a physical keyboard, not from a
|
||||||
/// soft keyboard.
|
/// soft keyboard.
|
||||||
@ -984,9 +984,6 @@ abstract class WidgetController {
|
|||||||
/// else. Must not be null. Some platforms (e.g. Windows, iOS) are not yet
|
/// else. Must not be null. Some platforms (e.g. Windows, iOS) are not yet
|
||||||
/// supported.
|
/// supported.
|
||||||
///
|
///
|
||||||
/// Whether the event is sent through [RawKeyEvent] or [KeyEvent] is
|
|
||||||
/// controlled by [debugKeyEventSimulatorTransitModeOverride].
|
|
||||||
///
|
|
||||||
/// Keys that are down when the test completes are cleared after each test.
|
/// Keys that are down when the test completes are cleared after each test.
|
||||||
///
|
///
|
||||||
/// This method sends both the key down and the key up events, to simulate a
|
/// This method sends both the key down and the key up events, to simulate a
|
||||||
@ -1007,7 +1004,7 @@ abstract class WidgetController {
|
|||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simulates sending a physical key down event.
|
/// Simulates sending a physical key down event through the system channel.
|
||||||
///
|
///
|
||||||
/// This only simulates key down events coming from a physical keyboard, not
|
/// This only simulates key down events coming from a physical keyboard, not
|
||||||
/// from a soft keyboard.
|
/// from a soft keyboard.
|
||||||
@ -1018,17 +1015,13 @@ abstract class WidgetController {
|
|||||||
/// else. Must not be null. Some platforms (e.g. Windows, iOS) are not yet
|
/// else. Must not be null. Some platforms (e.g. Windows, iOS) are not yet
|
||||||
/// supported.
|
/// supported.
|
||||||
///
|
///
|
||||||
/// Whether the event is sent through [RawKeyEvent] or [KeyEvent] is
|
|
||||||
/// controlled by [debugKeyEventSimulatorTransitModeOverride].
|
|
||||||
///
|
|
||||||
/// Keys that are down when the test completes are cleared after each test.
|
/// Keys that are down when the test completes are cleared after each test.
|
||||||
///
|
///
|
||||||
/// Returns true if the key event was handled by the framework.
|
/// Returns true if the key event was handled by the framework.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// - [sendKeyUpEvent] and [sendKeyRepeatEvent] to simulate the corresponding
|
/// - [sendKeyUpEvent] to simulate the corresponding key up event.
|
||||||
/// key up and repeat event.
|
|
||||||
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
|
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
|
||||||
Future<bool> sendKeyDownEvent(LogicalKeyboardKey key, { String? character, String platform = _defaultPlatform }) async {
|
Future<bool> sendKeyDownEvent(LogicalKeyboardKey key, { String? character, String platform = _defaultPlatform }) async {
|
||||||
assert(platform != null);
|
assert(platform != null);
|
||||||
@ -1046,15 +1039,11 @@ abstract class WidgetController {
|
|||||||
/// that type of system. Defaults to "web" on web, and "android" everywhere
|
/// that type of system. Defaults to "web" on web, and "android" everywhere
|
||||||
/// else. May not be null.
|
/// else. May not be null.
|
||||||
///
|
///
|
||||||
/// Whether the event is sent through [RawKeyEvent] or [KeyEvent] is
|
|
||||||
/// controlled by [debugKeyEventSimulatorTransitModeOverride].
|
|
||||||
///
|
|
||||||
/// Returns true if the key event was handled by the framework.
|
/// Returns true if the key event was handled by the framework.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// - [sendKeyDownEvent] and [sendKeyRepeatEvent] to simulate the
|
/// - [sendKeyDownEvent] to simulate the corresponding key down event.
|
||||||
/// corresponding key down and repeat event.
|
|
||||||
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
|
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
|
||||||
Future<bool> sendKeyUpEvent(LogicalKeyboardKey key, { String platform = _defaultPlatform }) async {
|
Future<bool> sendKeyUpEvent(LogicalKeyboardKey key, { String platform = _defaultPlatform }) async {
|
||||||
assert(platform != null);
|
assert(platform != null);
|
||||||
@ -1062,35 +1051,6 @@ abstract class WidgetController {
|
|||||||
return simulateKeyUpEvent(key, platform: platform);
|
return simulateKeyUpEvent(key, platform: platform);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simulates sending a physical key repeat event.
|
|
||||||
///
|
|
||||||
/// This only simulates key repeat events coming from a physical keyboard, not
|
|
||||||
/// from a soft keyboard.
|
|
||||||
///
|
|
||||||
/// Specify `platform` as one of the platforms allowed in
|
|
||||||
/// [platform.Platform.operatingSystem] to make the event appear to be from that type
|
|
||||||
/// of system. Defaults to "web" on web, and "android" everywhere else. Must not be
|
|
||||||
/// null. Some platforms (e.g. Windows, iOS) are not yet supported.
|
|
||||||
///
|
|
||||||
/// Whether the event is sent through [RawKeyEvent] or [KeyEvent] is
|
|
||||||
/// controlled by [debugKeyEventSimulatorTransitModeOverride]. If through [RawKeyEvent],
|
|
||||||
/// this method is equivalent to [sendKeyDownEvent].
|
|
||||||
///
|
|
||||||
/// Keys that are down when the test completes are cleared after each test.
|
|
||||||
///
|
|
||||||
/// Returns true if the key event was handled by the framework.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// - [sendKeyDownEvent] and [sendKeyUpEvent] to simulate the corresponding
|
|
||||||
/// key down and up event.
|
|
||||||
/// - [sendKeyEvent] to simulate both the key up and key down in the same call.
|
|
||||||
Future<bool> sendKeyRepeatEvent(LogicalKeyboardKey key, { String? character, String platform = _defaultPlatform }) async {
|
|
||||||
assert(platform != null);
|
|
||||||
// Internally wrapped in async guard.
|
|
||||||
return simulateKeyRepeatEvent(key, character: character, platform: platform);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the rect of the given widget. This is only valid once
|
/// Returns the rect of the given widget. This is only valid once
|
||||||
/// the widget's render object has been laid out at least once.
|
/// the widget's render object has been laid out at least once.
|
||||||
Rect getRect(Finder finder) => getTopLeft(finder) & getSize(finder);
|
Rect getRect(Finder finder) => getTopLeft(finder) & getSize(finder);
|
||||||
|
@ -4,12 +4,9 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import 'binding.dart';
|
import 'binding.dart';
|
||||||
import 'test_async_utils.dart';
|
import 'test_async_utils.dart';
|
||||||
@ -19,14 +16,13 @@ import 'test_async_utils.dart';
|
|||||||
// https://github.com/flutter/flutter/issues/33521
|
// https://github.com/flutter/flutter/issues/33521
|
||||||
// This code can only simulate keys which appear in the key maps.
|
// This code can only simulate keys which appear in the key maps.
|
||||||
|
|
||||||
String? _keyLabel(LogicalKeyboardKey key) {
|
String _keyLabel(LogicalKeyboardKey key) {
|
||||||
final String keyLabel = key.keyLabel;
|
final String keyLabel = key.keyLabel;
|
||||||
if (keyLabel.length == 1)
|
if (keyLabel.length == 1)
|
||||||
return keyLabel.toLowerCase();
|
return keyLabel.toLowerCase();
|
||||||
return null;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: avoid_classes_with_only_static_members
|
|
||||||
/// A class that serves as a namespace for a bunch of keyboard-key generation
|
/// A class that serves as a namespace for a bunch of keyboard-key generation
|
||||||
/// utilities.
|
/// utilities.
|
||||||
class KeyEventSimulator {
|
class KeyEventSimulator {
|
||||||
@ -157,7 +153,7 @@ class KeyEventSimulator {
|
|||||||
return result!;
|
return result!;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PhysicalKeyboardKey _findPhysicalKeyByPlatform(LogicalKeyboardKey key, String platform) {
|
static PhysicalKeyboardKey _findPhysicalKey(LogicalKeyboardKey key, String platform) {
|
||||||
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
|
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
|
||||||
late Map<dynamic, PhysicalKeyboardKey> map;
|
late Map<dynamic, PhysicalKeyboardKey> map;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
@ -200,7 +196,7 @@ class KeyEventSimulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a raw key data map given a [LogicalKeyboardKey] and a platform.
|
/// Get a raw key data map given a [LogicalKeyboardKey] and a platform.
|
||||||
static Map<String, dynamic> getRawKeyData(
|
static Map<String, dynamic> getKeyData(
|
||||||
LogicalKeyboardKey key, {
|
LogicalKeyboardKey key, {
|
||||||
required String platform,
|
required String platform,
|
||||||
bool isDown = true,
|
bool isDown = true,
|
||||||
@ -212,7 +208,7 @@ class KeyEventSimulator {
|
|||||||
key = _getKeySynonym(key);
|
key = _getKeySynonym(key);
|
||||||
|
|
||||||
// Find a suitable physical key if none was supplied.
|
// Find a suitable physical key if none was supplied.
|
||||||
physicalKey ??= _findPhysicalKeyByPlatform(key, platform);
|
physicalKey ??= _findPhysicalKey(key, platform);
|
||||||
|
|
||||||
assert(key.debugName != null);
|
assert(key.debugName != null);
|
||||||
final int keyCode = _getKeyCode(key, platform);
|
final int keyCode = _getKeyCode(key, platform);
|
||||||
@ -223,7 +219,7 @@ class KeyEventSimulator {
|
|||||||
'keymap': platform,
|
'keymap': platform,
|
||||||
};
|
};
|
||||||
|
|
||||||
final String resultCharacter = character ?? _keyLabel(key) ?? '';
|
final String resultCharacter = character ?? _keyLabel(key);
|
||||||
void assignWeb() {
|
void assignWeb() {
|
||||||
result['code'] = _getWebKeyCode(key);
|
result['code'] = _getWebKeyCode(key);
|
||||||
result['key'] = resultCharacter;
|
result['key'] = resultCharacter;
|
||||||
@ -629,64 +625,7 @@ class KeyEventSimulator {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<bool> _simulateKeyEventByRawEvent(ValueGetter<Map<String, dynamic>> getRawKeyData) async {
|
/// Simulates sending a hardware key down event through the system channel.
|
||||||
return TestAsyncUtils.guard<bool>(() async {
|
|
||||||
final Completer<bool> result = Completer<bool>();
|
|
||||||
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
|
||||||
SystemChannels.keyEvent.name,
|
|
||||||
SystemChannels.keyEvent.codec.encodeMessage(getRawKeyData()),
|
|
||||||
(ByteData? data) {
|
|
||||||
if (data == null) {
|
|
||||||
result.complete(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final Map<String, dynamic> decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
|
|
||||||
result.complete(decoded['handled'] as bool);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return result.future;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static late final Map<String, PhysicalKeyboardKey> _debugNameToPhysicalKey = (() {
|
|
||||||
final Map<String, PhysicalKeyboardKey> result = <String, PhysicalKeyboardKey>{};
|
|
||||||
for (final PhysicalKeyboardKey key in PhysicalKeyboardKey.knownPhysicalKeys) {
|
|
||||||
final String? debugName = key.debugName;
|
|
||||||
if (debugName != null)
|
|
||||||
result[debugName] = key;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
})();
|
|
||||||
static PhysicalKeyboardKey _findPhysicalKey(LogicalKeyboardKey key) {
|
|
||||||
final PhysicalKeyboardKey? result = _debugNameToPhysicalKey[key.debugName];
|
|
||||||
assert(result != null, 'Physical key for $key not found in known physical keys');
|
|
||||||
return result!;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const KeyDataTransitMode _defaultTransitMode = KeyDataTransitMode.rawKeyData;
|
|
||||||
|
|
||||||
// The simulation transit mode for [simulateKeyDownEvent], [simulateKeyUpEvent],
|
|
||||||
// and [simulateKeyRepeatEvent].
|
|
||||||
//
|
|
||||||
// Simulation transit mode is the mode that simulated key events are constructed
|
|
||||||
// and delivered. For detailed introduction, see [KeyDataTransitMode] and
|
|
||||||
// its values.
|
|
||||||
//
|
|
||||||
// The `_transitMode` defaults to [KeyDataTransitMode.rawKeyEvent], and can be
|
|
||||||
// overridden with [debugKeyEventSimulatorTransitModeOverride]. In widget tests, it
|
|
||||||
// is often set with [KeySimulationModeVariant].
|
|
||||||
static KeyDataTransitMode get _transitMode {
|
|
||||||
KeyDataTransitMode? result;
|
|
||||||
assert(() {
|
|
||||||
result = debugKeyEventSimulatorTransitModeOverride;
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
return result ?? _defaultTransitMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String get _defaultPlatform => kIsWeb ? 'web' : Platform.operatingSystem;
|
|
||||||
|
|
||||||
/// Simulates sending a hardware key down event.
|
|
||||||
///
|
///
|
||||||
/// This only simulates key presses coming from a physical keyboard, not from a
|
/// This only simulates key presses coming from a physical keyboard, not from a
|
||||||
/// soft keyboard.
|
/// soft keyboard.
|
||||||
@ -702,36 +641,29 @@ class KeyEventSimulator {
|
|||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [simulateKeyUpEvent] to simulate the corresponding key up event.
|
/// - [simulateKeyUpEvent] to simulate the corresponding key up event.
|
||||||
static Future<bool> simulateKeyDownEvent(
|
static Future<bool> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey, String? character}) async {
|
||||||
LogicalKeyboardKey key, {
|
return TestAsyncUtils.guard<bool>(() async {
|
||||||
String? platform,
|
platform ??= Platform.operatingSystem;
|
||||||
PhysicalKeyboardKey? physicalKey,
|
assert(_osIsSupported(platform!), 'Platform $platform not supported for key simulation');
|
||||||
String? character,
|
|
||||||
}) async {
|
|
||||||
Future<bool> _simulateByRawEvent() {
|
final Map<String, dynamic> data = getKeyData(key, platform: platform!, isDown: true, physicalKey: physicalKey, character: character);
|
||||||
return _simulateKeyEventByRawEvent(() {
|
final Completer<bool> result = Completer<bool>();
|
||||||
platform ??= _defaultPlatform;
|
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||||
return getRawKeyData(key, platform: platform!, isDown: true, physicalKey: physicalKey, character: character);
|
SystemChannels.keyEvent.name,
|
||||||
});
|
SystemChannels.keyEvent.codec.encodeMessage(data),
|
||||||
}
|
(ByteData? data) {
|
||||||
switch (_transitMode) {
|
if (data == null) {
|
||||||
case KeyDataTransitMode.rawKeyData:
|
result.complete(false);
|
||||||
return _simulateByRawEvent();
|
return;
|
||||||
case KeyDataTransitMode.keyDataThenRawKeyData:
|
}
|
||||||
final LogicalKeyboardKey logicalKey = _getKeySynonym(key);
|
final Map<String, dynamic> decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
|
||||||
final bool resultByKeyEvent = ServicesBinding.instance!.keyEventManager.handleKeyData(
|
result.complete(decoded['handled'] as bool);
|
||||||
ui.KeyData(
|
}
|
||||||
type: ui.KeyEventType.down,
|
);
|
||||||
physical: (physicalKey ?? _findPhysicalKey(logicalKey)).usbHidUsage,
|
return result.future;
|
||||||
logical: logicalKey.keyId,
|
});
|
||||||
timeStamp: Duration.zero,
|
|
||||||
character: character ?? _keyLabel(key),
|
|
||||||
synthesized: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return (await _simulateByRawEvent()) || resultByKeyEvent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simulates sending a hardware key up event through the system channel.
|
/// Simulates sending a hardware key up event through the system channel.
|
||||||
@ -748,81 +680,29 @@ class KeyEventSimulator {
|
|||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [simulateKeyDownEvent] to simulate the corresponding key down event.
|
/// - [simulateKeyDownEvent] to simulate the corresponding key down event.
|
||||||
static Future<bool> simulateKeyUpEvent(
|
static Future<bool> simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) async {
|
||||||
LogicalKeyboardKey key, {
|
return TestAsyncUtils.guard<bool>(() async {
|
||||||
String? platform,
|
platform ??= Platform.operatingSystem;
|
||||||
PhysicalKeyboardKey? physicalKey,
|
assert(_osIsSupported(platform!), 'Platform $platform not supported for key simulation');
|
||||||
}) async {
|
|
||||||
Future<bool> _simulateByRawEvent() {
|
|
||||||
return _simulateKeyEventByRawEvent(() {
|
|
||||||
platform ??= _defaultPlatform;
|
|
||||||
return getRawKeyData(key, platform: platform!, isDown: false, physicalKey: physicalKey);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
switch (_transitMode) {
|
|
||||||
case KeyDataTransitMode.rawKeyData:
|
|
||||||
return _simulateByRawEvent();
|
|
||||||
case KeyDataTransitMode.keyDataThenRawKeyData:
|
|
||||||
final LogicalKeyboardKey logicalKey = _getKeySynonym(key);
|
|
||||||
final bool resultByKeyEvent = ServicesBinding.instance!.keyEventManager.handleKeyData(
|
|
||||||
ui.KeyData(
|
|
||||||
type: ui.KeyEventType.up,
|
|
||||||
physical: (physicalKey ?? _findPhysicalKey(logicalKey)).usbHidUsage,
|
|
||||||
logical: logicalKey.keyId,
|
|
||||||
timeStamp: Duration.zero,
|
|
||||||
character: null,
|
|
||||||
synthesized: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return (await _simulateByRawEvent()) || resultByKeyEvent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simulates sending a hardware key repeat event through the system channel.
|
final Map<String, dynamic> data = getKeyData(key, platform: platform!, isDown: false, physicalKey: physicalKey);
|
||||||
///
|
bool result = false;
|
||||||
/// This only simulates key presses coming from a physical keyboard, not from a
|
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||||
/// soft keyboard.
|
SystemChannels.keyEvent.name,
|
||||||
///
|
SystemChannels.keyEvent.codec.encodeMessage(data),
|
||||||
/// Specify `platform` as one of the platforms allowed in
|
(ByteData? data) {
|
||||||
/// [Platform.operatingSystem] to make the event appear to be from that type of
|
if (data == null) {
|
||||||
/// system. Defaults to the operating system that the test is running on. Some
|
return;
|
||||||
/// platforms (e.g. Windows, iOS) are not yet supported.
|
}
|
||||||
///
|
final Map<String, dynamic> decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
|
||||||
/// Returns true if the key event was handled by the framework.
|
if (decoded['handled'] as bool) {
|
||||||
///
|
result = true;
|
||||||
/// See also:
|
}
|
||||||
///
|
}
|
||||||
/// * [simulateKeyDownEvent] to simulate the corresponding key down event.
|
);
|
||||||
static Future<bool> simulateKeyRepeatEvent(
|
return result;
|
||||||
LogicalKeyboardKey key, {
|
});
|
||||||
String? platform,
|
|
||||||
PhysicalKeyboardKey? physicalKey,
|
|
||||||
String? character,
|
|
||||||
}) async {
|
|
||||||
Future<bool> _simulateByRawEvent() {
|
|
||||||
return _simulateKeyEventByRawEvent(() {
|
|
||||||
platform ??= _defaultPlatform;
|
|
||||||
return getRawKeyData(key, platform: platform!, isDown: true, physicalKey: physicalKey, character: character);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
switch (_transitMode) {
|
|
||||||
case KeyDataTransitMode.rawKeyData:
|
|
||||||
return _simulateByRawEvent();
|
|
||||||
case KeyDataTransitMode.keyDataThenRawKeyData:
|
|
||||||
final LogicalKeyboardKey logicalKey = _getKeySynonym(key);
|
|
||||||
final bool resultByKeyEvent = ServicesBinding.instance!.keyEventManager.handleKeyData(
|
|
||||||
ui.KeyData(
|
|
||||||
type: ui.KeyEventType.repeat,
|
|
||||||
physical: (physicalKey ?? _findPhysicalKey(logicalKey)).usbHidUsage,
|
|
||||||
logical: logicalKey.keyId,
|
|
||||||
timeStamp: Duration.zero,
|
|
||||||
character: character ?? _keyLabel(key),
|
|
||||||
synthesized: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return (await _simulateByRawEvent()) || resultByKeyEvent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -845,14 +725,8 @@ class KeyEventSimulator {
|
|||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [simulateKeyUpEvent] and [simulateKeyRepeatEvent] to simulate the
|
/// - [simulateKeyUpEvent] to simulate the corresponding key up event.
|
||||||
/// corresponding key up and repeat event.
|
Future<bool> simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey, String? character}) {
|
||||||
Future<bool> simulateKeyDownEvent(
|
|
||||||
LogicalKeyboardKey key, {
|
|
||||||
String? platform,
|
|
||||||
PhysicalKeyboardKey? physicalKey,
|
|
||||||
String? character,
|
|
||||||
}) {
|
|
||||||
return KeyEventSimulator.simulateKeyDownEvent(key, platform: platform, physicalKey: physicalKey, character: character);
|
return KeyEventSimulator.simulateKeyDownEvent(key, platform: platform, physicalKey: physicalKey, character: character);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -873,85 +747,7 @@ Future<bool> simulateKeyDownEvent(
|
|||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [simulateKeyDownEvent] and [simulateKeyRepeatEvent] to simulate the
|
/// - [simulateKeyDownEvent] to simulate the corresponding key down event.
|
||||||
/// corresponding key down and repeat event.
|
Future<bool> simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) {
|
||||||
Future<bool> simulateKeyUpEvent(
|
|
||||||
LogicalKeyboardKey key, {
|
|
||||||
String? platform,
|
|
||||||
PhysicalKeyboardKey? physicalKey,
|
|
||||||
}) {
|
|
||||||
return KeyEventSimulator.simulateKeyUpEvent(key, platform: platform, physicalKey: physicalKey);
|
return KeyEventSimulator.simulateKeyUpEvent(key, platform: platform, physicalKey: physicalKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simulates sending a hardware key repeat event through the system channel.
|
|
||||||
///
|
|
||||||
/// This only simulates key presses coming from a physical keyboard, not from a
|
|
||||||
/// soft keyboard.
|
|
||||||
///
|
|
||||||
/// Specify `platform` as one of the platforms allowed in
|
|
||||||
/// [Platform.operatingSystem] to make the event appear to be from that type of
|
|
||||||
/// system. Defaults to the operating system that the test is running on. Some
|
|
||||||
/// platforms (e.g. Windows, iOS) are not yet supported.
|
|
||||||
///
|
|
||||||
/// Returns true if the key event was handled by the framework.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// - [simulateKeyDownEvent] and [simulateKeyUpEvent] to simulate the
|
|
||||||
/// corresponding key down and up event.
|
|
||||||
Future<bool> simulateKeyRepeatEvent(
|
|
||||||
LogicalKeyboardKey key, {
|
|
||||||
String? platform,
|
|
||||||
PhysicalKeyboardKey? physicalKey,
|
|
||||||
String? character,
|
|
||||||
}) {
|
|
||||||
return KeyEventSimulator.simulateKeyRepeatEvent(key, platform: platform, physicalKey: physicalKey, character: character);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A [TestVariant] that runs tests with transit modes set to different values
|
|
||||||
/// of [KeyDataTransitMode].
|
|
||||||
class KeySimulatorTransitModeVariant extends TestVariant<KeyDataTransitMode> {
|
|
||||||
/// Creates a [KeySimulatorTransitModeVariant] that tests the given [values].
|
|
||||||
const KeySimulatorTransitModeVariant(this.values);
|
|
||||||
|
|
||||||
/// Creates a [KeySimulatorTransitModeVariant] for each value option of
|
|
||||||
/// [KeyDataTransitMode].
|
|
||||||
KeySimulatorTransitModeVariant.all()
|
|
||||||
: this(KeyDataTransitMode.values.toSet());
|
|
||||||
|
|
||||||
/// Creates a [KeySimulatorTransitModeVariant] that only contains
|
|
||||||
/// [KeyDataTransitMode.keyDataThenRawKeyData].
|
|
||||||
KeySimulatorTransitModeVariant.keyDataThenRawKeyData()
|
|
||||||
: this(<KeyDataTransitMode>{KeyDataTransitMode.keyDataThenRawKeyData});
|
|
||||||
|
|
||||||
@override
|
|
||||||
final Set<KeyDataTransitMode> values;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String describeValue(KeyDataTransitMode value) {
|
|
||||||
switch (value) {
|
|
||||||
case KeyDataTransitMode.rawKeyData:
|
|
||||||
return 'RawKeyEvent';
|
|
||||||
case KeyDataTransitMode.keyDataThenRawKeyData:
|
|
||||||
return 'ui.KeyData then RawKeyEvent';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<KeyDataTransitMode?> setUp(KeyDataTransitMode value) async {
|
|
||||||
final KeyDataTransitMode? previousSetting = debugKeyEventSimulatorTransitModeOverride;
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = value;
|
|
||||||
return previousSetting;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> tearDown(KeyDataTransitMode value, KeyDataTransitMode? memento) async {
|
|
||||||
// ignore: invalid_use_of_visible_for_testing_member
|
|
||||||
RawKeyboard.instance.clearKeysPressed();
|
|
||||||
// ignore: invalid_use_of_visible_for_testing_member
|
|
||||||
HardwareKeyboard.instance.clearState();
|
|
||||||
// ignore: invalid_use_of_visible_for_testing_member
|
|
||||||
ServicesBinding.instance!.keyEventManager.clearState();
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = memento;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -230,7 +230,6 @@ class TestPointer {
|
|||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: _device,
|
device: _device,
|
||||||
pointer: pointer,
|
|
||||||
position: _location ?? Offset.zero,
|
position: _location ?? Offset.zero,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -256,7 +255,6 @@ class TestPointer {
|
|||||||
timeStamp: timeStamp,
|
timeStamp: timeStamp,
|
||||||
kind: kind,
|
kind: kind,
|
||||||
device: _device,
|
device: _device,
|
||||||
pointer: pointer,
|
|
||||||
position: newLocation,
|
position: newLocation,
|
||||||
delta: delta,
|
delta: delta,
|
||||||
);
|
);
|
||||||
|
@ -525,7 +525,7 @@ Future<void> expectLater(
|
|||||||
/// Class that programmatically interacts with widgets and the test environment.
|
/// Class that programmatically interacts with widgets and the test environment.
|
||||||
///
|
///
|
||||||
/// For convenience, instances of this class (such as the one provided by
|
/// For convenience, instances of this class (such as the one provided by
|
||||||
/// `testWidgets`) can be used as the `vsync` for `AnimationController` objects.
|
/// `testWidget`) can be used as the `vsync` for `AnimationController` objects.
|
||||||
class WidgetTester extends WidgetController implements HitTestDispatcher, TickerProvider {
|
class WidgetTester extends WidgetController implements HitTestDispatcher, TickerProvider {
|
||||||
WidgetTester._(TestWidgetsFlutterBinding binding) : super(binding) {
|
WidgetTester._(TestWidgetsFlutterBinding binding) : super(binding) {
|
||||||
if (binding is LiveTestWidgetsFlutterBinding)
|
if (binding is LiveTestWidgetsFlutterBinding)
|
||||||
|
@ -2,44 +2,14 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
const List<String> platforms = <String>['linux', 'macos', 'android', 'fuchsia'];
|
const List<String> platforms = <String>['linux', 'macos', 'android', 'fuchsia'];
|
||||||
|
|
||||||
void _verifyKeyEvent<T extends KeyEvent>(KeyEvent event, PhysicalKeyboardKey physical, LogicalKeyboardKey logical, String? character) {
|
|
||||||
expect(event, isA<T>());
|
|
||||||
expect(event.physicalKey, physical);
|
|
||||||
expect(event.logicalKey, logical);
|
|
||||||
expect(event.character, character);
|
|
||||||
expect(event.synthesized, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _verifyRawKeyEvent<T extends RawKeyEvent>(RawKeyEvent event, PhysicalKeyboardKey physical, LogicalKeyboardKey logical, String? character) {
|
|
||||||
expect(event, isA<T>());
|
|
||||||
expect(event.physicalKey, physical);
|
|
||||||
expect(event.logicalKey, logical);
|
|
||||||
expect(event.character, character);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _shouldThrow<T extends Error>(AsyncValueGetter<void> func) async {
|
|
||||||
bool hasError = false;
|
|
||||||
try {
|
|
||||||
await func();
|
|
||||||
} catch (e) {
|
|
||||||
expect(e, isA<T>());
|
|
||||||
hasError = true;
|
|
||||||
} finally {
|
|
||||||
expect(hasError, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('simulates keyboard events (RawEvent)', (WidgetTester tester) async {
|
testWidgets('simulates keyboard events', (WidgetTester tester) async {
|
||||||
debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.rawKeyData;
|
|
||||||
|
|
||||||
final List<RawKeyEvent> events = <RawKeyEvent>[];
|
final List<RawKeyEvent> events = <RawKeyEvent>[];
|
||||||
|
|
||||||
final FocusNode focusNode = FocusNode();
|
final FocusNode focusNode = FocusNode();
|
||||||
@ -81,247 +51,5 @@ void main() {
|
|||||||
|
|
||||||
await tester.pumpWidget(Container());
|
await tester.pumpWidget(Container());
|
||||||
focusNode.dispose();
|
focusNode.dispose();
|
||||||
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('simulates keyboard events (KeyData then RawKeyEvent)', (WidgetTester tester) async {
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData;
|
|
||||||
|
|
||||||
final List<KeyEvent> events = <KeyEvent>[];
|
|
||||||
|
|
||||||
final FocusNode focusNode = FocusNode();
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
KeyboardListener(
|
|
||||||
focusNode: focusNode,
|
|
||||||
onKeyEvent: events.add,
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
focusNode.requestFocus();
|
|
||||||
await tester.idle();
|
|
||||||
|
|
||||||
// Key press shiftLeft
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
|
|
||||||
expect(events.length, 1);
|
|
||||||
_verifyKeyEvent<KeyDownEvent>(events[0], PhysicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftLeft, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftLeft}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.shiftLeft);
|
|
||||||
expect(events.length, 1);
|
|
||||||
_verifyKeyEvent<KeyRepeatEvent>(events[0], PhysicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftLeft, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftLeft}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
|
|
||||||
expect(events.length, 1);
|
|
||||||
_verifyKeyEvent<KeyUpEvent>(events[0], PhysicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftLeft, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty);
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty);
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
// Key press keyA
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
|
|
||||||
expect(events.length, 1);
|
|
||||||
_verifyKeyEvent<KeyDownEvent>(events[0], PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a');
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.keyA}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.keyA}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
|
|
||||||
_verifyKeyEvent<KeyRepeatEvent>(events[0], PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a');
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.keyA}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.keyA}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
|
|
||||||
_verifyKeyEvent<KeyUpEvent>(events[0], PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty);
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty);
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
// Key press numpad1
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad1);
|
|
||||||
_verifyKeyEvent<KeyDownEvent>(events[0], PhysicalKeyboardKey.numpad1, LogicalKeyboardKey.numpad1, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.numpad1);
|
|
||||||
_verifyKeyEvent<KeyRepeatEvent>(events[0], PhysicalKeyboardKey.numpad1, LogicalKeyboardKey.numpad1, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numpad1}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad1);
|
|
||||||
_verifyKeyEvent<KeyUpEvent>(events[0], PhysicalKeyboardKey.numpad1, LogicalKeyboardKey.numpad1, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty);
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty);
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
// Key press numLock (1st time)
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.numLock);
|
|
||||||
_verifyKeyEvent<KeyDownEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, equals(<KeyboardLockMode>{KeyboardLockMode.numLock}));
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.numLock);
|
|
||||||
_verifyKeyEvent<KeyRepeatEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, equals(<KeyboardLockMode>{KeyboardLockMode.numLock}));
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.numLock);
|
|
||||||
_verifyKeyEvent<KeyUpEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty);
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty);
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, equals(<KeyboardLockMode>{KeyboardLockMode.numLock}));
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
// Key press numLock (2nd time)
|
|
||||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.numLock);
|
|
||||||
_verifyKeyEvent<KeyDownEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.numLock);
|
|
||||||
_verifyKeyEvent<KeyRepeatEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock}));
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.numLock);
|
|
||||||
_verifyKeyEvent<KeyUpEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null);
|
|
||||||
expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty);
|
|
||||||
expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty);
|
|
||||||
expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
await tester.idle();
|
|
||||||
|
|
||||||
await tester.pumpWidget(Container());
|
|
||||||
focusNode.dispose();
|
|
||||||
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('simulates using the correct transit mode: rawKeyData', (WidgetTester tester) async {
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.rawKeyData;
|
|
||||||
|
|
||||||
final List<Object> events = <Object>[];
|
|
||||||
|
|
||||||
final FocusNode focusNode = FocusNode();
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
focusNode: focusNode,
|
|
||||||
onKey: (FocusNode node, RawKeyEvent event) {
|
|
||||||
events.add(event);
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
},
|
|
||||||
onKeyEvent: (FocusNode node, KeyEvent event) {
|
|
||||||
events.add(event);
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
},
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
focusNode.requestFocus();
|
|
||||||
await tester.idle();
|
|
||||||
|
|
||||||
// A (physical keyA, logical keyA) is pressed.
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.keyA);
|
|
||||||
expect(events.length, 2);
|
|
||||||
expect(events[0], isA<KeyEvent>());
|
|
||||||
_verifyKeyEvent<KeyDownEvent>(events[0] as KeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a');
|
|
||||||
expect(events[1], isA<RawKeyEvent>());
|
|
||||||
_verifyRawKeyEvent<RawKeyDownEvent>(events[1] as RawKeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a');
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
// A (physical keyA, logical keyB) is released.
|
|
||||||
//
|
|
||||||
// Since this event was synthesized and regularized before being sent to
|
|
||||||
// HardwareKeyboard, this event will be accepted.
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.keyB, physicalKey: PhysicalKeyboardKey.keyA);
|
|
||||||
expect(events.length, 2);
|
|
||||||
expect(events[0], isA<KeyEvent>());
|
|
||||||
_verifyKeyEvent<KeyUpEvent>(events[0] as KeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, null);
|
|
||||||
expect(events[1], isA<RawKeyEvent>());
|
|
||||||
_verifyRawKeyEvent<RawKeyUpEvent>(events[1] as RawKeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyB, null);
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
// Manually switch the transit mode to `keyDataThenRawKeyData`. This will
|
|
||||||
// never happen in real applications so the assertion error can verify that
|
|
||||||
// the transit mode is correctly applied.
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData;
|
|
||||||
|
|
||||||
await _shouldThrow<AssertionError>(() =>
|
|
||||||
simulateKeyUpEvent(LogicalKeyboardKey.keyB, physicalKey: PhysicalKeyboardKey.keyA));
|
|
||||||
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('simulates using the correct transit mode: keyDataThenRawKeyData', (WidgetTester tester) async {
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData;
|
|
||||||
|
|
||||||
final List<Object> events = <Object>[];
|
|
||||||
|
|
||||||
final FocusNode focusNode = FocusNode();
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Focus(
|
|
||||||
focusNode: focusNode,
|
|
||||||
onKey: (FocusNode node, RawKeyEvent event) {
|
|
||||||
events.add(event);
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
},
|
|
||||||
onKeyEvent: (FocusNode node, KeyEvent event) {
|
|
||||||
events.add(event);
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
},
|
|
||||||
child: Container(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
focusNode.requestFocus();
|
|
||||||
await tester.idle();
|
|
||||||
|
|
||||||
// A (physical keyA, logical keyA) is pressed.
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.keyA);
|
|
||||||
expect(events.length, 2);
|
|
||||||
expect(events[0], isA<KeyEvent>());
|
|
||||||
_verifyKeyEvent<KeyDownEvent>(events[0] as KeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a');
|
|
||||||
expect(events[1], isA<RawKeyEvent>());
|
|
||||||
_verifyRawKeyEvent<RawKeyDownEvent>(events[1] as RawKeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a');
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
// A (physical keyA, logical keyB) is released.
|
|
||||||
//
|
|
||||||
// Since this event is transmitted to HardwareKeyboard as-is, it will be rejected due to
|
|
||||||
// inconsistent logical key. This does not indicate behaviral difference,
|
|
||||||
// since KeyData is will never send malformed data sequence in real applications.
|
|
||||||
await _shouldThrow<AssertionError>(() =>
|
|
||||||
simulateKeyUpEvent(LogicalKeyboardKey.keyB, physicalKey: PhysicalKeyboardKey.keyA));
|
|
||||||
|
|
||||||
debugKeyEventSimulatorTransitModeOverride = null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user