mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

- - When I added notification of key events before processing them as text, it made it so that shortcut key bindings like the spacebar would prevent spaces from being inserted into text fields, which is obviously not desirable (and so that change was reverted). At the same time, we do want to make it possible to override key events so that they can do things like intercept a tab key or arrow keys that change the focus. This PR changes the behavior of the Shortcuts widget so that if it has a shortcut defined, but no action is bound to the intent, then instead of responding that the key is "handled", it responds as if nothing handled it. This allows the engine to continue to process the key as text entry. This PR includes: - Modification of the callback type for key handlers to return a KeyEventResult instead of a bool, so that we can return more information (i.e. the extra state of "stop propagation"). - Modification of the ActionDispatcher.invokeAction contract to require that Action.isEnabled return true before calling it. It will now assert if the action isn't enabled when invokeAction is called. This is to allow optimization of the number of calls to isEnabled, since the shortcuts widget now wants to know if the action was enabled before deciding to either handle the key or to return ignored. - Modification to ShortcutManager.handleKeypress to return KeyEventResult.ignored for keys which don't have an enabled action associated with them. - Adds an attribute to DoNothingAction that allows it to mark a key as not handled, even though it does have an action associated with it. This will allow disabling of a shortcut for a subtree.
154 lines
6.5 KiB
Dart
154 lines
6.5 KiB
Dart
// 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/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
void main() {
|
|
runApp(MaterialApp(
|
|
title: 'Hardware Key Demo',
|
|
home: Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Hardware Key Demo'),
|
|
),
|
|
body: const Center(
|
|
child: RawKeyboardDemo(),
|
|
),
|
|
),
|
|
));
|
|
}
|
|
|
|
class RawKeyboardDemo extends StatefulWidget {
|
|
const RawKeyboardDemo({Key key}) : super(key: key);
|
|
|
|
@override
|
|
_HardwareKeyDemoState createState() => _HardwareKeyDemoState();
|
|
}
|
|
|
|
class _HardwareKeyDemoState extends State<RawKeyboardDemo> {
|
|
final FocusNode _focusNode = FocusNode();
|
|
RawKeyEvent _event;
|
|
|
|
@override
|
|
void dispose() {
|
|
_focusNode.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
KeyEventResult _handleKeyEvent(FocusNode node, RawKeyEvent event) {
|
|
setState(() {
|
|
_event = event;
|
|
});
|
|
return KeyEventResult.ignored;
|
|
}
|
|
|
|
String _asHex(int value) => value != null ? '0x${value.toRadixString(16)}' : 'null';
|
|
|
|
String _getEnumName(dynamic enumItem) {
|
|
final String name = '$enumItem';
|
|
final int index = name.indexOf('.');
|
|
return index == -1 ? name : name.substring(index + 1);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final TextTheme textTheme = Theme.of(context).textTheme;
|
|
return Focus(
|
|
focusNode: _focusNode,
|
|
onKey: _handleKeyEvent,
|
|
autofocus: true,
|
|
child: AnimatedBuilder(
|
|
animation: _focusNode,
|
|
builder: (BuildContext context, Widget child) {
|
|
if (!_focusNode.hasFocus) {
|
|
return GestureDetector(
|
|
onTap: () {
|
|
_focusNode.requestFocus();
|
|
},
|
|
child: Text('Tap to focus', style: textTheme.headline4),
|
|
);
|
|
}
|
|
|
|
if (_event == null) {
|
|
return Text('Press a key', style: textTheme.headline4);
|
|
}
|
|
|
|
final RawKeyEventData data = _event.data;
|
|
final String modifierList = data.modifiersPressed.keys.map<String>(_getEnumName).join(', ').replaceAll('Modifier', '');
|
|
final List<Widget> dataText = <Widget>[
|
|
Text('${_event.runtimeType}'),
|
|
if (_event.character?.isNotEmpty ?? false) Text('character produced: "${_event.character}"'),
|
|
Text('modifiers set: $modifierList'),
|
|
];
|
|
if (data is RawKeyEventDataAndroid) {
|
|
const int combiningCharacterMask = 0x7fffffff;
|
|
final String codePointChar = String.fromCharCode(combiningCharacterMask & data.codePoint);
|
|
dataText.add(Text('codePoint: ${data.codePoint} (${_asHex(data.codePoint)}: $codePointChar)'));
|
|
final String plainCodePointChar = String.fromCharCode(combiningCharacterMask & data.plainCodePoint);
|
|
dataText.add(Text('plainCodePoint: ${data.plainCodePoint} (${_asHex(data.plainCodePoint)}: $plainCodePointChar)'));
|
|
dataText.add(Text('keyCode: ${data.keyCode} (${_asHex(data.keyCode)})'));
|
|
dataText.add(Text('scanCode: ${data.scanCode} (${_asHex(data.scanCode)})'));
|
|
dataText.add(Text('metaState: ${data.metaState} (${_asHex(data.metaState)})'));
|
|
dataText.add(Text('source: ${data.eventSource} (${_asHex(data.eventSource)})'));
|
|
dataText.add(Text('vendorId: ${data.vendorId} (${_asHex(data.vendorId)})'));
|
|
dataText.add(Text('productId: ${data.productId} (${_asHex(data.productId)})'));
|
|
dataText.add(Text('flags: ${data.flags} (${_asHex(data.flags)})'));
|
|
dataText.add(Text('repeatCount: ${data.repeatCount} (${_asHex(data.repeatCount)})'));
|
|
} else if (data is RawKeyEventDataFuchsia) {
|
|
dataText.add(Text('codePoint: ${data.codePoint} (${_asHex(data.codePoint)})'));
|
|
dataText.add(Text('hidUsage: ${data.hidUsage} (${_asHex(data.hidUsage)})'));
|
|
dataText.add(Text('modifiers: ${data.modifiers} (${_asHex(data.modifiers)})'));
|
|
} else if (data is RawKeyEventDataMacOs) {
|
|
dataText.add(Text('keyCode: ${data.keyCode} (${_asHex(data.keyCode)})'));
|
|
dataText.add(Text('characters: ${data.characters}'));
|
|
dataText.add(Text('charactersIgnoringModifiers: ${data.charactersIgnoringModifiers}'));
|
|
dataText.add(Text('modifiers: ${data.modifiers} (${_asHex(data.modifiers)})'));
|
|
} else if (data is RawKeyEventDataLinux) {
|
|
dataText.add(Text('keyCode: ${data.keyCode} (${_asHex(data.keyCode)})'));
|
|
dataText.add(Text('scanCode: ${data.scanCode}'));
|
|
dataText.add(Text('unicodeScalarValues: ${data.unicodeScalarValues}'));
|
|
dataText.add(Text('modifiers: ${data.modifiers} (${_asHex(data.modifiers)})'));
|
|
} else if (data is RawKeyEventDataWindows) {
|
|
dataText.add(Text('keyCode: ${data.keyCode} (${_asHex(data.keyCode)})'));
|
|
dataText.add(Text('scanCode: ${data.scanCode}'));
|
|
dataText.add(Text('characterCodePoint: ${data.characterCodePoint}'));
|
|
dataText.add(Text('modifiers: ${data.modifiers} (${_asHex(data.modifiers)})'));
|
|
} else if (data is RawKeyEventDataWeb) {
|
|
dataText.add(Text('key: ${data.key}'));
|
|
dataText.add(Text('code: ${data.code}'));
|
|
dataText.add(Text('metaState: ${data.metaState} (${_asHex(data.metaState)})'));
|
|
}
|
|
dataText.add(Text('logical: ${_event.logicalKey}'));
|
|
dataText.add(Text('physical: ${_event.physicalKey}'));
|
|
if (_event.character != null) {
|
|
dataText.add(Text('character: ${_event.character}'));
|
|
}
|
|
for (final ModifierKey modifier in data.modifiersPressed.keys) {
|
|
for (final KeyboardSide side in KeyboardSide.values) {
|
|
if (data.isModifierPressed(modifier, side: side)) {
|
|
dataText.add(
|
|
Text('${_getEnumName(side)} ${_getEnumName(modifier).replaceAll('Modifier', '')} pressed'),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
final List<String> pressed = <String>['Pressed:'];
|
|
for (final LogicalKeyboardKey key in RawKeyboard.instance.keysPressed) {
|
|
pressed.add(key.debugName);
|
|
}
|
|
dataText.add(Text(pressed.join(' ')));
|
|
return DefaultTextStyle(
|
|
style: textTheme.subtitle1,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: dataText,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|