Unverified Commit 898646f0 authored by krisgiesing's avatar krisgiesing Committed by GitHub

Separate focus management by build owner (#13334)

Separate focus management by build owner

Currently the focus manager is held by the singleton WidgetBinding.
This change places a focus manager in each build owner object,
which allows clients to run offscreen pipelines without disturbing
focus management for the main UI.
parent da5be601
......@@ -317,7 +317,7 @@ abstract class WidgetsBinding extends BindingBase with GestureBinding, RendererB
/// the [FocusScopeNode] for a given [BuildContext].
///
/// See [FocusManager] for more details.
final FocusManager focusManager = new FocusManager();
FocusManager get focusManager => _buildOwner.focusManager;
final List<WidgetsBindingObserver> _observers = <WidgetsBindingObserver>[];
......
......@@ -5,7 +5,6 @@
import 'package:flutter/foundation.dart';
import 'basic.dart';
import 'binding.dart';
import 'focus_manager.dart';
import 'framework.dart';
......@@ -75,7 +74,7 @@ class FocusScope extends StatefulWidget {
/// given [BuildContext].
static FocusScopeNode of(BuildContext context) {
final _FocusScopeMarker scope = context.inheritFromWidgetOfExactType(_FocusScopeMarker);
return scope?.node ?? WidgetsBinding.instance.focusManager.rootScope;
return scope?.node ?? context.owner.focusManager.rootScope;
}
@override
......
......@@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'debug.dart';
import 'focus_manager.dart';
export 'dart:ui' show hashValues, hashList;
......@@ -1852,6 +1853,10 @@ abstract class BuildContext {
/// The current configuration of the [Element] that is this [BuildContext].
Widget get widget;
/// The [BuildOwner] for this context. The [BuildOwner] is in charge of
/// managing the rendering pipeline for this context.
BuildOwner get owner;
/// The current [RenderObject] for the widget. If the widget is a
/// [RenderObjectWidget], this is the render object that the widget created
/// for itself. Otherwise, it is the render object of the first descendant
......@@ -2098,6 +2103,14 @@ class BuildOwner {
bool _scheduledFlushDirtyElements = false;
bool _dirtyElementsNeedsResorting; // null means we're not in a buildScope
/// The object in charge of the focus tree.
///
/// Rarely used directly. Instead, consider using [FocusScope.of] to obtain
/// the [FocusScopeNode] for a given [BuildContext].
///
/// See [FocusManager] for more details.
final FocusManager focusManager = new FocusManager();
/// Adds an element to the dirty elements list so that it will be rebuilt
/// when [WidgetsBinding.drawFrame] calls [buildScope].
void scheduleBuildFor(Element element) {
......@@ -2559,6 +2572,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
Widget _widget;
/// The object that manages the lifecycle of this element.
@override
BuildOwner get owner => _owner;
BuildOwner _owner;
......
......@@ -96,6 +96,38 @@ class TriggerableState extends State<TriggerableWidget> {
}
}
class TestFocusable extends StatefulWidget {
const TestFocusable({
Key key,
this.focusNode,
this.autofocus: true,
}) : super(key: key);
final bool autofocus;
final FocusNode focusNode;
@override
TestFocusableState createState() => new TestFocusableState();
}
class TestFocusableState extends State<TestFocusable> {
bool _didAutofocus = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_didAutofocus && widget.autofocus) {
_didAutofocus = true;
FocusScope.of(context).autofocus(widget.focusNode);
}
}
@override
Widget build(BuildContext context) {
return const Text('Test focus node', textDirection: TextDirection.ltr);
}
}
void main() {
testWidgets('no crosstalk between widget build owners', (WidgetTester tester) async {
final Trigger trigger1 = new Trigger();
......@@ -146,4 +178,28 @@ void main() {
expect(counter1.count, equals(3));
expect(counter2.count, equals(3));
});
testWidgets('no crosstalk between focus nodes', (WidgetTester tester) async {
final OffscreenWidgetTree tree = new OffscreenWidgetTree();
final FocusNode onscreenFocus = new FocusNode();
final FocusNode offscreenFocus = new FocusNode();
await tester.pumpWidget(
new TestFocusable(
focusNode: onscreenFocus,
),
);
tree.pumpWidget(
new TestFocusable(
focusNode: offscreenFocus,
),
);
// Autofocus is delayed one frame.
await tester.pump();
tree.pumpFrame();
expect(onscreenFocus.hasFocus, isTrue);
expect(offscreenFocus.hasFocus, isTrue);
});
}
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