Unverified Commit 590cc27b authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Revert "Implement focus traversal for desktop platforms, shoehorn edition. (#30040)" (#31461)

This reverts commit 4218c0bc.
parent 096439b4
......@@ -62,7 +62,7 @@ class _HardwareKeyDemoState extends State<RawKeyboardDemo> {
if (!_focusNode.hasFocus) {
return GestureDetector(
onTap: () {
_focusNode.requestFocus();
FocusScope.of(context).requestFocus(_focusNode);
},
child: Text('Tap to focus', style: textTheme.display1),
);
......
......@@ -17,7 +17,7 @@ class MyApp extends StatelessWidget {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
appBar: AppBar(title: Text(_title)),
body: MyStatefulWidget(),
),
);
......
......@@ -17,7 +17,7 @@ class MyApp extends StatelessWidget {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
appBar: AppBar(title: Text(_title)),
body: MyStatelessWidget(),
),
);
......
......@@ -90,7 +90,7 @@ import 'package:flutter/foundation.dart';
/// onTap: () {
/// FocusScope.of(context).requestFocus(_focusNode);
/// },
/// child: const Text('Tap to focus'),
/// child: Text('Tap to focus'),
/// );
/// }
/// return Text(_message ?? 'Press a key');
......
......@@ -304,7 +304,7 @@ class _TabSwitchingViewState extends State<_TabSwitchingView> {
tabs = List<Widget>(widget.tabNumber);
tabFocusNodes = List<FocusScopeNode>.generate(
widget.tabNumber,
(int index) => FocusScopeNode(debugLabel: 'Tab Focus Scope $index'),
(int index) => FocusScopeNode(),
);
}
......@@ -327,7 +327,7 @@ class _TabSwitchingViewState extends State<_TabSwitchingView> {
@override
void dispose() {
for (FocusScopeNode focusScopeNode in tabFocusNodes) {
focusScopeNode.dispose();
focusScopeNode.detach();
}
super.dispose();
}
......
......@@ -191,7 +191,7 @@ abstract class SearchDelegate<T> {
///
/// * [showSuggestions] to show the search suggestions again.
void showResults(BuildContext context) {
_focusNode?.unfocus();
_focusNode.unfocus();
_currentBody = _SearchBody.results;
}
......@@ -208,8 +208,7 @@ abstract class SearchDelegate<T> {
///
/// * [showResults] to show the search results.
void showSuggestions(BuildContext context) {
assert(_focusNode != null, '_focusNode must be set by route before showSuggestions is called.');
_focusNode.requestFocus();
FocusScope.of(context).requestFocus(_focusNode);
_currentBody = _SearchBody.suggestions;
}
......@@ -219,7 +218,7 @@ abstract class SearchDelegate<T> {
/// to [showSearch] that launched the search initially.
void close(BuildContext context, T result) {
_currentBody = null;
_focusNode?.unfocus();
_focusNode.unfocus();
Navigator.of(context)
..popUntil((Route<dynamic> route) => route == _route)
..pop(result);
......@@ -233,9 +232,7 @@ abstract class SearchDelegate<T> {
/// page.
Animation<double> get transitionAnimation => _proxyAnimation;
// The focus node to use for manipulating focus on the search page. This is
// managed, owned, and set by the _SearchPageRoute using this delegate.
FocusNode _focusNode;
final FocusNode _focusNode = FocusNode();
final TextEditingController _queryTextController = TextEditingController();
......@@ -249,6 +246,7 @@ abstract class SearchDelegate<T> {
}
_SearchPageRoute<T> _route;
}
/// Describes the body that is currently shown under the [AppBar] in the
......@@ -348,18 +346,13 @@ class _SearchPage<T> extends StatefulWidget {
}
class _SearchPageState<T> extends State<_SearchPage<T>> {
// This node is owned, but not hosted by, the search page. Hosting is done by
// the text field.
FocusNode focusNode = FocusNode();
@override
void initState() {
super.initState();
queryTextController.addListener(_onQueryChanged);
widget.animation.addStatusListener(_onAnimationStatusChanged);
widget.delegate._currentBodyNotifier.addListener(_onSearchBodyChanged);
focusNode.addListener(_onFocusChanged);
widget.delegate._focusNode = focusNode;
widget.delegate._focusNode.addListener(_onFocusChanged);
}
@override
......@@ -368,8 +361,7 @@ class _SearchPageState<T> extends State<_SearchPage<T>> {
queryTextController.removeListener(_onQueryChanged);
widget.animation.removeStatusListener(_onAnimationStatusChanged);
widget.delegate._currentBodyNotifier.removeListener(_onSearchBodyChanged);
widget.delegate._focusNode = null;
focusNode.dispose();
widget.delegate._focusNode.removeListener(_onFocusChanged);
}
void _onAnimationStatusChanged(AnimationStatus status) {
......@@ -378,12 +370,12 @@ class _SearchPageState<T> extends State<_SearchPage<T>> {
}
widget.animation.removeStatusListener(_onAnimationStatusChanged);
if (widget.delegate._currentBody == _SearchBody.suggestions) {
focusNode.requestFocus();
FocusScope.of(context).requestFocus(widget.delegate._focusNode);
}
}
void _onFocusChanged() {
if (focusNode.hasFocus && widget.delegate._currentBody != _SearchBody.suggestions) {
if (widget.delegate._focusNode.hasFocus && widget.delegate._currentBody != _SearchBody.suggestions) {
widget.delegate.showSuggestions(context);
}
}
......@@ -444,7 +436,7 @@ class _SearchPageState<T> extends State<_SearchPage<T>> {
leading: widget.delegate.buildLeading(context),
title: TextField(
controller: queryTextController,
focusNode: focusNode,
focusNode: widget.delegate._focusNode,
style: theme.textTheme.title,
textInputAction: TextInputAction.search,
onSubmitted: (String _) {
......
......@@ -1455,8 +1455,7 @@ class RenderEditable extends RenderBox {
}
TextSelection _selectWordAtOffset(TextPosition position) {
assert(_textLayoutLastWidth == constraints.maxWidth,
'Last width ($_textLayoutLastWidth) not the same as max width constraint (${constraints.maxWidth}).');
assert(_textLayoutLastWidth == constraints.maxWidth);
final TextRange word = _textPainter.getWordBoundary(position);
// When long-pressing past the end of the text, we want a collapsed cursor.
if (position.offset >= word.end)
......@@ -1528,8 +1527,7 @@ class RenderEditable extends RenderBox {
}
void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPosition) {
assert(_textLayoutLastWidth == constraints.maxWidth,
'Last width ($_textLayoutLastWidth) not the same as max width constraint (${constraints.maxWidth}).');
assert(_textLayoutLastWidth == constraints.maxWidth);
// If the floating cursor is enabled, the text cursor's color is [backgroundCursorColor] while
// the floating cursor's color is _cursorColor;
......@@ -1595,8 +1593,7 @@ class RenderEditable extends RenderBox {
}
void _paintFloatingCaret(Canvas canvas, Offset effectiveOffset) {
assert(_textLayoutLastWidth == constraints.maxWidth,
'Last width ($_textLayoutLastWidth) not the same as max width constraint (${constraints.maxWidth}).');
assert(_textLayoutLastWidth == constraints.maxWidth);
assert(_floatingCursorOn);
// We always want the floating cursor to render at full opacity.
......@@ -1683,8 +1680,7 @@ class RenderEditable extends RenderBox {
}
void _paintSelection(Canvas canvas, Offset effectiveOffset) {
assert(_textLayoutLastWidth == constraints.maxWidth,
'Last width ($_textLayoutLastWidth) not the same as max width constraint (${constraints.maxWidth}).');
assert(_textLayoutLastWidth == constraints.maxWidth);
assert(_selectionRects != null);
final Paint paint = Paint()..color = _selectionColor;
for (ui.TextBox box in _selectionRects)
......@@ -1692,8 +1688,7 @@ class RenderEditable extends RenderBox {
}
void _paintContents(PaintingContext context, Offset offset) {
assert(_textLayoutLastWidth == constraints.maxWidth,
'Last width ($_textLayoutLastWidth) not the same as max width constraint (${constraints.maxWidth}).');
assert(_textLayoutLastWidth == constraints.maxWidth);
final Offset effectiveOffset = offset + _paintOffset;
bool showSelection = false;
......
......@@ -90,7 +90,7 @@ import 'package:flutter/foundation.dart';
/// onTap: () {
/// FocusScope.of(context).requestFocus(_focusNode);
/// },
/// child: const Text('Tap to focus'),
/// child: Text('Tap to focus'),
/// );
/// }
/// return Text(_message ?? 'Press a key');
......
......@@ -787,7 +787,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final LayerLink _layerLink = LayerLink();
bool _didAutoFocus = false;
FocusAttachment _focusAttachment;
// This value is an eyeball estimation of the time it takes for the iOS cursor
// to ease in and out.
......@@ -810,7 +809,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
void initState() {
super.initState();
widget.controller.addListener(_didChangeTextEditingValue);
_focusAttachment = widget.focusNode.attach(context);
widget.focusNode.addListener(_handleFocusChanged);
_scrollController.addListener(() { _selectionOverlay?.updateForScroll(); });
_cursorBlinkOpacityController = AnimationController(vsync: this, duration: _fadeDuration);
......@@ -838,8 +836,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
if (widget.focusNode != oldWidget.focusNode) {
oldWidget.focusNode.removeListener(_handleFocusChanged);
_focusAttachment?.detach();
_focusAttachment = widget.focusNode.attach(context);
widget.focusNode.addListener(_handleFocusChanged);
updateKeepAlive();
}
......@@ -856,7 +852,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
assert(_cursorTimer == null);
_selectionOverlay?.dispose();
_selectionOverlay = null;
_focusAttachment.detach();
widget.focusNode.removeListener(_handleFocusChanged);
super.dispose();
}
......@@ -1096,7 +1091,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if (_hasFocus) {
_openInputConnection();
} else {
widget.focusNode.requestFocus();
final List<FocusScopeNode> ancestorScopes = FocusScope.ancestorsOf(context);
for (int i = ancestorScopes.length - 1; i >= 1; i -= 1)
ancestorScopes[i].setFirstFocus(ancestorScopes[i - 1]);
FocusScope.of(context).requestFocus(widget.focusNode);
}
}
......@@ -1402,7 +1400,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
_focusAttachment.reparent();
FocusScope.of(context).reparentIfNeeded(widget.focusNode);
super.build(context); // See AutomaticKeepAliveClientMixin.
final TextSelectionControls controls = widget.selectionControls;
......
......@@ -2124,7 +2124,7 @@ class BuildOwner {
/// the [FocusScopeNode] for a given [BuildContext].
///
/// See [FocusManager] for more details.
FocusManager focusManager = FocusManager();
final FocusManager focusManager = FocusManager();
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when [WidgetsBinding.drawFrame] calls [buildScope].
......
......@@ -1468,7 +1468,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
final Set<Route<dynamic>> _poppedRoutes = <Route<dynamic>>{};
/// The [FocusScopeNode] for the [FocusScope] that encloses the routes.
final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'Navigator Scope');
final FocusScopeNode focusScopeNode = FocusScopeNode();
final List<OverlayEntry> _initialOverlayEntries = <OverlayEntry>[];
......@@ -1556,7 +1556,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
route.dispose();
_poppedRoutes.clear();
_history.clear();
focusScopeNode.dispose();
focusScopeNode.detach();
super.dispose();
assert(() { _debugLocked = false; return true; }());
}
......
......@@ -7,7 +7,6 @@ import 'package:flutter/services.dart';
import 'basic.dart';
import 'focus_manager.dart';
import 'focus_scope.dart';
import 'framework.dart';
export 'package:flutter/services.dart' show RawKeyEvent;
......@@ -113,5 +112,5 @@ class _RawKeyboardListenerState extends State<RawKeyboardListener> {
}
@override
Widget build(BuildContext context) => Focus(focusNode: widget.focusNode, child: widget.child);
Widget build(BuildContext context) => widget.child;
}
......@@ -583,9 +583,6 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
// This is the combination of the two animations for the route.
Listenable _listenable;
/// The node this scope will use for its root [FocusScope] widget.
final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: '$_ModalScopeState Focus Scope');
@override
void initState() {
super.initState();
......@@ -595,14 +592,12 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
if (widget.route.secondaryAnimation != null)
animations.add(widget.route.secondaryAnimation);
_listenable = Listenable.merge(animations);
widget.route._grabFocusIfNeeded(focusScopeNode);
}
@override
void didUpdateWidget(_ModalScope<T> oldWidget) {
super.didUpdateWidget(oldWidget);
assert(widget.route == oldWidget.route);
widget.route._grabFocusIfNeeded(focusScopeNode);
}
@override
......@@ -617,12 +612,6 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
});
}
@override
void dispose() {
super.dispose();
focusScopeNode.dispose();
}
// This should be called to wrap any changes to route.isCurrent, route.canPop,
// and route.offstage.
void _routeSetState(VoidCallback fn) {
......@@ -640,7 +629,7 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
child: PageStorage(
bucket: widget.route._storageBucket, // immutable
child: FocusScope(
node: focusScopeNode, // immutable
node: widget.route.focusScopeNode, // immutable
child: RepaintBoundary(
child: AnimatedBuilder(
animation: _listenable, // immutable
......@@ -898,6 +887,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
return child;
}
/// The node this route will use for its root [FocusScope] widget.
final FocusScopeNode focusScopeNode = FocusScopeNode();
@override
void install(OverlayEntry insertionPoint) {
super.install(insertionPoint);
......@@ -905,20 +897,18 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
_secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation);
}
bool _wantsFocus = false;
void _grabFocusIfNeeded(FocusScopeNode node) {
if (_wantsFocus) {
_wantsFocus = false;
navigator.focusScopeNode.setFirstFocus(node);
}
}
@override
TickerFuture didPush() {
_wantsFocus = true;
navigator.focusScopeNode.setFirstFocus(focusScopeNode);
return super.didPush();
}
@override
void dispose() {
focusScopeNode.detach();
super.dispose();
}
// The API for subclasses to override - used by this class
/// Whether you can dismiss this route by tapping the modal barrier.
......
......@@ -106,10 +106,7 @@ void main() {
testWidgets('Last tab gets focus', (WidgetTester tester) async {
// 2 nodes for 2 tabs
final List<FocusNode> focusNodes = <FocusNode>[
FocusNode(debugLabel: 'Node 1'),
FocusNode(debugLabel: 'Node 2'),
];
final List<FocusNode> focusNodes = <FocusNode>[FocusNode(), FocusNode()];
await tester.pumpWidget(
CupertinoApp(
......@@ -142,10 +139,7 @@ void main() {
testWidgets('Do not affect focus order in the route', (WidgetTester tester) async {
final List<FocusNode> focusNodes = <FocusNode>[
FocusNode(debugLabel: 'Node 1'),
FocusNode(debugLabel: 'Node 2'),
FocusNode(debugLabel: 'Node 3'),
FocusNode(debugLabel: 'Node 4'),
FocusNode(), FocusNode(), FocusNode(), FocusNode(),
];
await tester.pumpWidget(
......
......@@ -9,14 +9,11 @@ void main() {
testWidgets('Dialog interaction', (WidgetTester tester) async {
expect(tester.testTextInput.isVisible, isFalse);
final FocusNode focusNode = FocusNode(debugLabel: 'Editable Text Node');
await tester.pumpWidget(
MaterialApp(
const MaterialApp(
home: Material(
child: Center(
child: TextField(
focusNode: focusNode,
autofocus: true,
),
),
......@@ -133,7 +130,7 @@ void main() {
await tester.pumpWidget(Container());
expect(tester.testTextInput.isVisible, isFalse);
});
}, skip: true); // https://github.com/flutter/flutter/issues/29384.
testWidgets('Focus triggers keep-alive', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
......
......@@ -2754,31 +2754,29 @@ void main() {
controller = TextEditingController();
});
Future<void> setupWidget(WidgetTester tester) async {
MaterialApp setupWidget() {
final FocusNode focusNode = FocusNode();
controller = TextEditingController();
await tester.pumpWidget(
MaterialApp(
home: Material(
child: RawKeyboardListener(
focusNode: focusNode,
onKey: null,
child: TextField(
controller: controller,
maxLines: 3,
strutStyle: StrutStyle.disabled,
),
return MaterialApp(
home: Material(
child: RawKeyboardListener(
focusNode: focusNode,
onKey: null,
child: TextField(
controller: controller,
maxLines: 3,
strutStyle: StrutStyle.disabled,
),
),
) ,
),
);
focusNode.requestFocus();
await tester.pump();
}
testWidgets('Shift test 1', (WidgetTester tester) async {
await setupWidget(tester);
await tester.pumpWidget(setupWidget());
const String testValue = 'a big house';
await tester.enterText(find.byType(TextField), testValue);
......@@ -2791,7 +2789,7 @@ void main() {
});
testWidgets('Control Shift test', (WidgetTester tester) async {
await setupWidget(tester);
await tester.pumpWidget(setupWidget());
const String testValue = 'their big house';
await tester.enterText(find.byType(TextField), testValue);
......@@ -2807,7 +2805,7 @@ void main() {
});
testWidgets('Down and up test', (WidgetTester tester) async {
await setupWidget(tester);
await tester.pumpWidget(setupWidget());
const String testValue = 'a big house';
await tester.enterText(find.byType(TextField), testValue);
......@@ -2829,7 +2827,7 @@ void main() {
});
testWidgets('Down and up test 2', (WidgetTester tester) async {
await setupWidget(tester);
await tester.pumpWidget(setupWidget());
const String testValue = 'a big house\njumped over a mouse\nOne more line yay'; // 11 \n 19
await tester.enterText(find.byType(TextField), testValue);
......@@ -2916,8 +2914,6 @@ void main() {
),
),
);
focusNode.requestFocus();
await tester.pump();
const String testValue = 'a big house\njumped over a mouse'; // 11 \n 19
await tester.enterText(find.byType(TextField), testValue);
......@@ -2988,8 +2984,6 @@ void main() {
),
),
);
focusNode.requestFocus();
await tester.pump();
const String testValue = 'a big house\njumped over a mouse'; // 11 \n 19
await tester.enterText(find.byType(TextField), testValue);
......@@ -3099,8 +3093,6 @@ void main() {
),
),
);
focusNode.requestFocus();
await tester.pump();
const String testValue = 'a big house\njumped over a mouse'; // 11 \n 19
await tester.enterText(find.byType(TextField), testValue);
......
......@@ -16,8 +16,8 @@ import 'editable_text_utils.dart';
import 'semantics_tester.dart';
final TextEditingController controller = TextEditingController();
final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Node');
final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'EditableText Scope Node');
final FocusNode focusNode = FocusNode();
final FocusScopeNode focusScopeNode = FocusScopeNode();
const TextStyle textStyle = TextStyle();
const Color cursorColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00);
......@@ -975,9 +975,6 @@ void main() {
),
));
focusNode.requestFocus();
await tester.pump();
expect(
semantics,
includesNodeWith(
......@@ -1535,8 +1532,6 @@ void main() {
),
);
focusNode.requestFocus();
// Now change it to make it obscure text.
await tester.pumpWidget(MaterialApp(
home: EditableText(
......@@ -1911,7 +1906,7 @@ void main() {
);
final GlobalKey<EditableTextState> editableTextKey =
GlobalKey<EditableTextState>();
final FocusNode focusNode = FocusNode(debugLabel: 'Test Focus Node');
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(MaterialApp( // So we can show overlays.
home: EditableText(
......
This diff is collapsed.
......@@ -12,7 +12,7 @@ void sendFakeKeyEvent(Map<String, dynamic> data) {
BinaryMessages.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData data) {},
(ByteData data) { },
);
}
......@@ -29,15 +29,13 @@ void main() {
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
RawKeyboardListener(
focusNode: focusNode,
onKey: events.add,
child: Container(),
),
);
await tester.pumpWidget(RawKeyboardListener(
focusNode: focusNode,
onKey: events.add,
child: Container(),
));
focusNode.requestFocus();
tester.binding.focusManager.rootScope.requestFocus(focusNode);
await tester.idle();
sendFakeKeyEvent(<String, dynamic>{
......@@ -67,15 +65,13 @@ void main() {
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
RawKeyboardListener(
focusNode: focusNode,
onKey: events.add,
child: Container(),
),
);
await tester.pumpWidget(RawKeyboardListener(
focusNode: focusNode,
onKey: events.add,
child: Container(),
));
focusNode.requestFocus();
tester.binding.focusManager.rootScope.requestFocus(focusNode);
await tester.idle();
sendFakeKeyEvent(<String, dynamic>{
......
......@@ -692,7 +692,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
FlutterError.onError = _oldExceptionHandler;
_pendingExceptionDetails = null;
_parentZone = null;
buildOwner.focusManager = FocusManager();
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment