Focus Manager debug output improvements. (#37891)
This improves the ability to debug focus manager issues. It's not meant to be used by developers unless they're debugging problems with the focus manager itself.
This commit is contained in:
parent
19d113b0e7
commit
aa5cc40368
@ -14,6 +14,24 @@ import 'focus_scope.dart';
|
||||
import 'focus_traversal.dart';
|
||||
import 'framework.dart';
|
||||
|
||||
// Used for debugging focus code. Set to true to see highly verbose debug output
|
||||
// when focus changes occur.
|
||||
const bool _kDebugFocus = false;
|
||||
|
||||
void _focusDebug(String message, [Iterable<String> details]) {
|
||||
assert(() {
|
||||
if (_kDebugFocus) {
|
||||
debugPrint('FOCUS: $message');
|
||||
if (details != null && details.isNotEmpty) {
|
||||
for (String detail in details) {
|
||||
debugPrint(' $detail');
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
}
|
||||
|
||||
/// Signature of a callback used by [Focus.onKey] and [FocusScope.onKey]
|
||||
/// to receive key events.
|
||||
///
|
||||
@ -61,6 +79,7 @@ class FocusAttachment {
|
||||
/// Calling [FocusNode.dispose] will also automatically detach the node.
|
||||
void detach() {
|
||||
assert(_node != null);
|
||||
_focusDebug('Detaching node:', <String>[_node.toString(), 'With enclosing scope ${_node.enclosingScope}']);
|
||||
if (isAttached) {
|
||||
if (_node.hasPrimaryFocus) {
|
||||
_node.unfocus();
|
||||
@ -855,6 +874,7 @@ class FocusScopeNode extends FocusNode {
|
||||
/// tree, the given scope must be a descendant of this scope.
|
||||
void setFirstFocus(FocusScopeNode scope) {
|
||||
assert(scope != null);
|
||||
assert(scope != this, 'Unexpected self-reference in setFirstFocus.');
|
||||
if (scope._parent == null) {
|
||||
_reparent(scope);
|
||||
}
|
||||
@ -998,6 +1018,7 @@ class FocusManager with DiagnosticableTreeMixin {
|
||||
// Called to indicate that the given node is being disposed.
|
||||
void _willDisposeFocusNode(FocusNode node) {
|
||||
assert(node != null);
|
||||
_focusDebug('Disposing of node:', <String>[node.toString(), 'with enclosing scope ${node.enclosingScope}']);
|
||||
_willUnfocusNode(node);
|
||||
_dirtyNodes.remove(node);
|
||||
}
|
||||
@ -1006,6 +1027,7 @@ class FocusManager with DiagnosticableTreeMixin {
|
||||
// pending request to be focused should be canceled.
|
||||
void _willUnfocusNode(FocusNode node) {
|
||||
assert(node != null);
|
||||
_focusDebug('Unfocusing node $node');
|
||||
if (_primaryFocus == node) {
|
||||
_primaryFocus = null;
|
||||
_dirtyNodes.add(node);
|
||||
@ -1016,6 +1038,7 @@ class FocusManager with DiagnosticableTreeMixin {
|
||||
_dirtyNodes.add(node);
|
||||
_markNeedsUpdate();
|
||||
}
|
||||
_focusDebug('Unfocused node $node:', <String>['primary focus is $_primaryFocus', 'next focus will be $_nextFocus']);
|
||||
}
|
||||
|
||||
// True indicates that there is an update pending.
|
||||
@ -1027,6 +1050,7 @@ class FocusManager with DiagnosticableTreeMixin {
|
||||
// If newFocus isn't specified, then don't mess with _nextFocus, just
|
||||
// schedule the update.
|
||||
_nextFocus = newFocus ?? _nextFocus;
|
||||
_focusDebug('Scheduling update, next focus will be $_nextFocus');
|
||||
if (_haveScheduledUpdate) {
|
||||
return;
|
||||
}
|
||||
@ -1036,6 +1060,7 @@ class FocusManager with DiagnosticableTreeMixin {
|
||||
|
||||
void _applyFocusChange() {
|
||||
_haveScheduledUpdate = false;
|
||||
_focusDebug('Refreshing focus state. Next focus will be $_nextFocus');
|
||||
final FocusNode previousFocus = _primaryFocus;
|
||||
if (_primaryFocus == null && _nextFocus == null) {
|
||||
// If we don't have any current focus, and nobody has asked to focus yet,
|
||||
@ -1053,6 +1078,7 @@ class FocusManager with DiagnosticableTreeMixin {
|
||||
_nextFocus = null;
|
||||
}
|
||||
if (previousFocus != _primaryFocus) {
|
||||
_focusDebug('Updating focus from $previousFocus to $_primaryFocus');
|
||||
if (previousFocus != null) {
|
||||
_dirtyNodes.add(previousFocus);
|
||||
}
|
||||
@ -1060,10 +1086,17 @@ class FocusManager with DiagnosticableTreeMixin {
|
||||
_dirtyNodes.add(_primaryFocus);
|
||||
}
|
||||
}
|
||||
_focusDebug('Notifying ${_dirtyNodes.length} dirty nodes:', _dirtyNodes.toList().map<String>((FocusNode node) => node.toString()));
|
||||
for (FocusNode node in _dirtyNodes) {
|
||||
node._notify();
|
||||
}
|
||||
_dirtyNodes.clear();
|
||||
assert(() {
|
||||
if (_kDebugFocus) {
|
||||
debugDumpFocusTree();
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1076,7 +1109,11 @@ class FocusManager with DiagnosticableTreeMixin {
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
properties.add(FlagProperty('haveScheduledUpdate', value: _haveScheduledUpdate, ifTrue: 'UPDATE SCHEDULED'));
|
||||
properties.add(DiagnosticsProperty<FocusNode>('currentFocus', primaryFocus, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<FocusNode>('primaryFocus', primaryFocus, defaultValue: null));
|
||||
final Element element = primaryFocus?.context;
|
||||
if (element != null) {
|
||||
properties.add(DiagnosticsProperty<String>('primaryFocusCreator', element.debugGetCreatorChain(20)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -367,21 +367,21 @@ void main() {
|
||||
});
|
||||
testWidgets('Unfocus works properly', (WidgetTester tester) async {
|
||||
final BuildContext context = await setupWidget(tester);
|
||||
final FocusScopeNode scope1 = FocusScopeNode()..attach(context);
|
||||
final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope1')..attach(context);
|
||||
final FocusAttachment scope1Attachment = scope1.attach(context);
|
||||
final FocusScopeNode scope2 = FocusScopeNode();
|
||||
final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2');
|
||||
final FocusAttachment scope2Attachment = scope2.attach(context);
|
||||
final FocusNode parent1 = FocusNode();
|
||||
final FocusNode parent1 = FocusNode(debugLabel: 'parent1');
|
||||
final FocusAttachment parent1Attachment = parent1.attach(context);
|
||||
final FocusNode parent2 = FocusNode();
|
||||
final FocusNode parent2 = FocusNode(debugLabel: 'parent2');
|
||||
final FocusAttachment parent2Attachment = parent2.attach(context);
|
||||
final FocusNode child1 = FocusNode();
|
||||
final FocusNode child1 = FocusNode(debugLabel: 'child1');
|
||||
final FocusAttachment child1Attachment = child1.attach(context);
|
||||
final FocusNode child2 = FocusNode();
|
||||
final FocusNode child2 = FocusNode(debugLabel: 'child2');
|
||||
final FocusAttachment child2Attachment = child2.attach(context);
|
||||
final FocusNode child3 = FocusNode();
|
||||
final FocusNode child3 = FocusNode(debugLabel: 'child3');
|
||||
final FocusAttachment child3Attachment = child3.attach(context);
|
||||
final FocusNode child4 = FocusNode();
|
||||
final FocusNode child4 = FocusNode(debugLabel: 'child4');
|
||||
final FocusAttachment child4Attachment = child4.attach(context);
|
||||
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
|
||||
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
|
||||
@ -526,46 +526,47 @@ void main() {
|
||||
description,
|
||||
equalsIgnoringHashCodes(
|
||||
'FocusManager#00000\n'
|
||||
' │ currentFocus: FocusNode#00000\n'
|
||||
' │\n'
|
||||
' └─rootScope: FocusScopeNode#00000\n'
|
||||
' │ FOCUSED\n'
|
||||
' │ debugLabel: "Root Focus Scope"\n'
|
||||
' │ focusedChild: FocusScopeNode#00000\n'
|
||||
' │\n'
|
||||
' ├─Child 1: FocusScopeNode#00000\n'
|
||||
' │ │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ │ debugLabel: "Scope 1"\n'
|
||||
' │ │\n'
|
||||
' │ └─Child 1: FocusNode#00000\n'
|
||||
' │ │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ │ debugLabel: "Parent 1"\n'
|
||||
' │ │\n'
|
||||
' │ ├─Child 1: FocusNode#00000\n'
|
||||
' │ │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ │ debugLabel: "Child 1"\n'
|
||||
' │ │\n'
|
||||
' │ └─Child 2: FocusNode#00000\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │\n'
|
||||
' └─Child 2: FocusScopeNode#00000\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ FOCUSED\n'
|
||||
' │ focusedChild: FocusNode#00000\n'
|
||||
' │\n'
|
||||
' └─Child 1: FocusNode#00000\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ FOCUSED\n'
|
||||
' │ debugLabel: "Parent 2"\n'
|
||||
' │\n'
|
||||
' ├─Child 1: FocusNode#00000\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ debugLabel: "Child 3"\n'
|
||||
' │\n'
|
||||
' └─Child 2: FocusNode#00000\n'
|
||||
' context: Container-[GlobalKey#00000]\n'
|
||||
' FOCUSED\n'
|
||||
' debugLabel: "Child 4"\n'
|
||||
' │ primaryFocus: FocusNode#00000\n'
|
||||
' │ primaryFocusCreator: Container-[GlobalKey#00000] ← [root]\n'
|
||||
' │\n'
|
||||
' └─rootScope: FocusScopeNode#00000\n'
|
||||
' │ FOCUSED\n'
|
||||
' │ debugLabel: "Root Focus Scope"\n'
|
||||
' │ focusedChild: FocusScopeNode#00000\n'
|
||||
' │\n'
|
||||
' ├─Child 1: FocusScopeNode#00000\n'
|
||||
' │ │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ │ debugLabel: "Scope 1"\n'
|
||||
' │ │\n'
|
||||
' │ └─Child 1: FocusNode#00000\n'
|
||||
' │ │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ │ debugLabel: "Parent 1"\n'
|
||||
' │ │\n'
|
||||
' │ ├─Child 1: FocusNode#00000\n'
|
||||
' │ │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ │ debugLabel: "Child 1"\n'
|
||||
' │ │\n'
|
||||
' │ └─Child 2: FocusNode#00000\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │\n'
|
||||
' └─Child 2: FocusScopeNode#00000\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ FOCUSED\n'
|
||||
' │ focusedChild: FocusNode#00000\n'
|
||||
' │\n'
|
||||
' └─Child 1: FocusNode#00000\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ FOCUSED\n'
|
||||
' │ debugLabel: "Parent 2"\n'
|
||||
' │\n'
|
||||
' ├─Child 1: FocusNode#00000\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ debugLabel: "Child 3"\n'
|
||||
' │\n'
|
||||
' └─Child 2: FocusNode#00000\n'
|
||||
' context: Container-[GlobalKey#00000]\n'
|
||||
' FOCUSED\n'
|
||||
' debugLabel: "Child 4"\n'
|
||||
));
|
||||
});
|
||||
});
|
||||
|
@ -718,6 +718,7 @@ void main() {
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
TestFocus(
|
||||
debugLabel: 'Child B',
|
||||
key: keyB,
|
||||
name: 'b',
|
||||
autofocus: true,
|
||||
@ -799,6 +800,7 @@ void main() {
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
TestFocus(
|
||||
debugLabel: 'Child B',
|
||||
key: keyB,
|
||||
name: 'b',
|
||||
autofocus: true,
|
||||
|
@ -811,15 +811,9 @@ void main() {
|
||||
debugLabel: 'Scope',
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Focus(
|
||||
focusNode: focusTop,
|
||||
child: Container(width: 100, height: 100)),
|
||||
Focus(
|
||||
focusNode: focusCenter,
|
||||
child: Container(width: 100, height: 100)),
|
||||
Focus(
|
||||
focusNode: focusBottom,
|
||||
child: Container(width: 100, height: 100)),
|
||||
Focus(focusNode: focusTop, child: Container(width: 100, height: 100)),
|
||||
Focus(focusNode: focusCenter, child: Container(width: 100, height: 100)),
|
||||
Focus(focusNode: focusBottom, child: Container(width: 100, height: 100)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -841,12 +835,8 @@ void main() {
|
||||
debugLabel: 'Scope',
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Focus(
|
||||
focusNode: focusTop,
|
||||
child: Container(width: 100, height: 100)),
|
||||
Focus(
|
||||
focusNode: focusBottom,
|
||||
child: Container(width: 100, height: 100)),
|
||||
Focus(focusNode: focusTop, child: Container(width: 100, height: 100)),
|
||||
Focus(focusNode: focusBottom, child: Container(width: 100, height: 100)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user