您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
975 行
34 KiB
975 行
34 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unity.UIWidgets.async2;
|
|
using Unity.UIWidgets.cupertino;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.gestures;
|
|
using Unity.UIWidgets.painting;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.service;
|
|
using Unity.UIWidgets.ui;
|
|
using Unity.UIWidgets.widgets;
|
|
|
|
namespace Unity.UIWidgets.widgets {
|
|
public enum FocusHighlightMode {
|
|
touch,
|
|
traditional,
|
|
}
|
|
enum FocusHighlightStrategy {
|
|
automatic,
|
|
alwaysTouch,
|
|
alwaysTraditional,
|
|
}
|
|
|
|
public enum UnfocusDisposition {
|
|
|
|
scope,
|
|
|
|
previouslyFocusedChild,
|
|
}
|
|
public delegate bool FocusOnKeyCallback(FocusNode node, RawKeyEvent Event);
|
|
|
|
public class FocusManagerUtils {
|
|
public static readonly bool _kDebugFocus = false;
|
|
|
|
public static bool _focusDebug(string message, List<string> details = null) {
|
|
if (_kDebugFocus) {
|
|
//UnityEngine.Debug.Log();
|
|
UnityEngine.Debug.Log($"FOCUS: {message}");
|
|
if (details != null && details.Count() != 0) {
|
|
foreach (string detail in details) {
|
|
UnityEngine.Debug.Log($" {detail}");
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static FocusNode primaryFocus {
|
|
get { return WidgetsBinding.instance.focusManager.primaryFocus; }
|
|
}
|
|
|
|
|
|
public static string debugDescribeFocusTree() {
|
|
D.assert(WidgetsBinding.instance != null);
|
|
string result = null;
|
|
D.assert(() => {
|
|
result = FocusManager.instance.toStringDeep();
|
|
return true;
|
|
});
|
|
return result ?? "";
|
|
}
|
|
|
|
|
|
public static void debugDumpFocusTree() {
|
|
D.assert(() => {
|
|
UnityEngine.Debug.Log(debugDescribeFocusTree());
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
|
|
public class FocusAttachment {
|
|
public FocusAttachment(FocusNode _node) {
|
|
D.assert(_node != null);
|
|
this._node = _node;
|
|
}
|
|
|
|
public readonly FocusNode _node;
|
|
|
|
bool isAttached {
|
|
get {
|
|
return _node._attachment == this;
|
|
}
|
|
}
|
|
|
|
public void detach() {
|
|
D.assert(_node != null);
|
|
D.assert(FocusManagerUtils._focusDebug("Detaching node:", new List<string>{_node.ToString(), $"With enclosing scope {_node.enclosingScope}"}));
|
|
if (isAttached) {
|
|
if (_node.hasPrimaryFocus || (_node._manager != null && _node._manager._markedForFocus == _node)) {
|
|
_node.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
|
|
}
|
|
|
|
_node._manager?._markDetached(_node);
|
|
_node._parent?._removeChild(_node);
|
|
_node._attachment = null;
|
|
D.assert(!_node.hasPrimaryFocus);
|
|
D.assert(_node._manager?._markedForFocus != _node);
|
|
}
|
|
D.assert(!isAttached);
|
|
}
|
|
|
|
public void reparent(FocusNode parent = null) {
|
|
D.assert(_node != null);
|
|
if (isAttached) {
|
|
D.assert(_node.context != null);
|
|
parent = parent ?? Focus.of(_node.context, nullOk: true, scopeOk: true);
|
|
parent = parent ?? _node.context.owner.focusManager.rootScope;
|
|
D.assert(parent != null);
|
|
parent._reparent(_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class FocusNode : DiagnosticableTreeMixinChangeNotifier{
|
|
public FocusNode(
|
|
string debugLabel = "",
|
|
FocusOnKeyCallback onKey = null,
|
|
bool skipTraversal = false,
|
|
bool canRequestFocus = true
|
|
) {
|
|
D.assert(skipTraversal != null);
|
|
D.assert(canRequestFocus != null);
|
|
_skipTraversal = skipTraversal;
|
|
_canRequestFocus = canRequestFocus;
|
|
_onKey = onKey;
|
|
this.debugLabel = debugLabel;
|
|
}
|
|
|
|
public bool skipTraversal {
|
|
get { return _skipTraversal; }
|
|
set {
|
|
if (value != _skipTraversal) {
|
|
_skipTraversal = value;
|
|
_manager?._markPropertiesChanged(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool _skipTraversal;
|
|
|
|
|
|
public bool canRequestFocus {
|
|
get {
|
|
FocusScopeNode scope = enclosingScope;
|
|
return _canRequestFocus && (scope == null || scope.canRequestFocus);
|
|
}
|
|
set {
|
|
if (value != _canRequestFocus) {
|
|
if (!value) {
|
|
unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
|
|
}
|
|
|
|
_canRequestFocus = value;
|
|
_manager?._markPropertiesChanged(this);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
bool _canRequestFocus;
|
|
|
|
|
|
public BuildContext context {
|
|
get { return _context; }
|
|
|
|
}
|
|
|
|
BuildContext _context;
|
|
|
|
public FocusOnKeyCallback onKey {
|
|
get { return _onKey; }
|
|
|
|
}
|
|
|
|
FocusOnKeyCallback _onKey;
|
|
|
|
public FocusManager _manager;
|
|
List<FocusNode> _ancestors;
|
|
List<FocusNode> _descendants;
|
|
bool _hasKeyboardToken = false;
|
|
|
|
public FocusNode parent {
|
|
get { return _parent; }
|
|
|
|
}
|
|
|
|
public FocusNode _parent;
|
|
|
|
|
|
IEnumerable<FocusNode> children {
|
|
get { return _children; }
|
|
|
|
}
|
|
|
|
public readonly List<FocusNode> _children = new List<FocusNode>();
|
|
|
|
IEnumerable<FocusNode> traversalChildren {
|
|
get {
|
|
if (!canRequestFocus) {
|
|
return new List<FocusNode>();
|
|
}
|
|
|
|
List<FocusNode> nodes = new List<FocusNode>();
|
|
foreach (FocusNode node in children) {
|
|
if (!node.skipTraversal && node.canRequestFocus)
|
|
nodes.Add(node);
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
}
|
|
|
|
public string debugLabel {
|
|
get { return _debugLabel; }
|
|
set {
|
|
D.assert(() => {
|
|
// Only set the value in debug builds.
|
|
_debugLabel = value;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
string _debugLabel;
|
|
|
|
|
|
public FocusAttachment _attachment;
|
|
|
|
|
|
public IEnumerable<FocusNode> descendants {
|
|
|
|
get {
|
|
if (_descendants == null) {
|
|
List<FocusNode> result = new List<FocusNode>();
|
|
foreach (FocusNode child in _children) {
|
|
result.AddRange(child.descendants);
|
|
result.Add(child);
|
|
}
|
|
|
|
_descendants = result;
|
|
}
|
|
|
|
return _descendants;
|
|
}
|
|
|
|
}
|
|
|
|
public IEnumerable<FocusNode> traversalDescendants {
|
|
get {
|
|
List<FocusNode> nodes = new List<FocusNode>();
|
|
foreach (FocusNode node in descendants) {
|
|
if (!node.skipTraversal && node.canRequestFocus) {
|
|
nodes.Add(node);
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
}
|
|
|
|
public IEnumerable<FocusNode> ancestors {
|
|
get {
|
|
if (_ancestors == null) {
|
|
List<FocusNode> result = new List<FocusNode>();
|
|
FocusNode parent = _parent;
|
|
while (parent != null) {
|
|
result.Add(parent);
|
|
parent = parent._parent;
|
|
}
|
|
|
|
_ancestors = result;
|
|
}
|
|
|
|
return _ancestors;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public bool hasFocus {
|
|
get { return hasPrimaryFocus || (_manager?.primaryFocus?.ancestors?.Contains(this) ?? false); }
|
|
}
|
|
|
|
public bool hasPrimaryFocus {
|
|
get { return _manager?.primaryFocus == this; }
|
|
}
|
|
|
|
/// Returns the [FocusHighlightMode] that is currently in effect for this node.
|
|
FocusHighlightMode highlightMode {
|
|
get { return FocusManager.instance.highlightMode; }
|
|
}
|
|
|
|
public FocusScopeNode nearestScope {
|
|
get { return enclosingScope; }
|
|
}
|
|
|
|
|
|
public FocusScopeNode enclosingScope {
|
|
get {
|
|
foreach (FocusNode node in ancestors) {
|
|
if (node is FocusScopeNode) {
|
|
return (FocusScopeNode) node;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
Size size {
|
|
get {
|
|
D.assert(
|
|
context != null, () =>
|
|
"Tried to get the size of a focus node that didn't have its context set yet.\n" +
|
|
"The context needs to be set before trying to evaluate traversal policies. This " +
|
|
"is typically done with the attach method."
|
|
);
|
|
return context.findRenderObject().semanticBounds.size;
|
|
}
|
|
}
|
|
|
|
|
|
Offset offset {
|
|
get {
|
|
D.assert(
|
|
context != null, () =>
|
|
"Tried to get the offset of a focus node that didn't have its context set yet.\n" +
|
|
"The context needs to be set before trying to evaluate traversal policies. This " +
|
|
"is typically done with the attach method.");
|
|
RenderObject renderObject = context.findRenderObject();
|
|
return MatrixUtils.transformPoint(renderObject.getTransformTo(null),
|
|
renderObject.semanticBounds.topLeft);
|
|
}
|
|
}
|
|
|
|
public Rect rect {
|
|
get {
|
|
D.assert(
|
|
context != null, () =>
|
|
"Tried to get the bounds of a focus node that didn't have its context set yet.\n" +
|
|
"The context needs to be set before trying to evaluate traversal policies. This " +
|
|
"is typically done with the attach method.");
|
|
RenderObject renderObject = context.findRenderObject();
|
|
Offset globalOffset = MatrixUtils.transformPoint(renderObject.getTransformTo(null),
|
|
renderObject.semanticBounds.topLeft);
|
|
return globalOffset & renderObject.semanticBounds.size;
|
|
}
|
|
}
|
|
|
|
public void unfocus(
|
|
UnfocusDisposition disposition = UnfocusDisposition.scope
|
|
) {
|
|
D.assert(disposition != null);
|
|
if (!hasFocus && (_manager == null || _manager._markedForFocus != this)) {
|
|
return;
|
|
}
|
|
|
|
FocusScopeNode scope = enclosingScope;
|
|
if (scope == null) {
|
|
|
|
return;
|
|
}
|
|
|
|
switch (disposition) {
|
|
case UnfocusDisposition.scope:
|
|
if (scope.canRequestFocus) {
|
|
scope._focusedChildren.Clear();
|
|
}
|
|
|
|
while (!scope.canRequestFocus) {
|
|
scope = scope.enclosingScope ?? _manager?.rootScope;
|
|
}
|
|
|
|
scope?._doRequestFocus(findFirstFocus: false);
|
|
break;
|
|
case UnfocusDisposition.previouslyFocusedChild:
|
|
|
|
if (scope.canRequestFocus) {
|
|
scope?._focusedChildren?.Remove(this);
|
|
}
|
|
|
|
while (!scope.canRequestFocus) {
|
|
scope.enclosingScope?._focusedChildren?.Remove(scope);
|
|
scope = scope.enclosingScope ?? _manager?.rootScope;
|
|
}
|
|
|
|
scope?._doRequestFocus(findFirstFocus: true);
|
|
break;
|
|
}
|
|
|
|
D.assert(FocusManagerUtils._focusDebug("Unfocused node:",
|
|
new List<string> {$"primary focus was {this}", $"next focus will be {_manager?._markedForFocus}"}));
|
|
}
|
|
|
|
|
|
public bool consumeKeyboardToken() {
|
|
if (!_hasKeyboardToken) {
|
|
return false;
|
|
}
|
|
|
|
_hasKeyboardToken = false;
|
|
return true;
|
|
}
|
|
|
|
public void _markNextFocus(FocusNode newFocus) {
|
|
if (_manager != null) {
|
|
// If we have a manager, then let it handle the focus change.
|
|
_manager._markNextFocus(this);
|
|
return;
|
|
}
|
|
|
|
newFocus?._setAsFocusedChildForScope();
|
|
newFocus?._notify();
|
|
if (newFocus != this) {
|
|
_notify();
|
|
}
|
|
}
|
|
|
|
|
|
public void _removeChild(FocusNode node, bool removeScopeFocus = true) {
|
|
D.assert(node != null);
|
|
D.assert(_children.Contains(node), () => "Tried to remove a node that wasn't a child.");
|
|
D.assert(node._parent == this);
|
|
D.assert(node._manager == _manager);
|
|
|
|
if (removeScopeFocus) {
|
|
node.enclosingScope?._focusedChildren?.Remove(node);
|
|
}
|
|
|
|
node._parent = null;
|
|
_children.Remove(node);
|
|
foreach (FocusNode ancestor in ancestors) {
|
|
ancestor._descendants = null;
|
|
}
|
|
|
|
_descendants = null;
|
|
D.assert(_manager == null || !_manager.rootScope.descendants.Contains(node));
|
|
}
|
|
|
|
void _updateManager(FocusManager manager) {
|
|
_manager = manager;
|
|
foreach (FocusNode descendant in descendants) {
|
|
descendant._manager = manager;
|
|
descendant._ancestors = null;
|
|
}
|
|
}
|
|
|
|
public void _reparent(FocusNode child) {
|
|
D.assert(child != null);
|
|
D.assert(child != this, () => "Tried to make a child into a parent of itself.");
|
|
if (child._parent == this) {
|
|
D.assert(_children.Contains(child),
|
|
() => "Found a node that says it's a child, but doesn't appear in the child list.");
|
|
// The child is already a child of this parent.
|
|
return;
|
|
}
|
|
|
|
D.assert(_manager == null || child != _manager.rootScope, () => "Reparenting the root node isn't allowed.");
|
|
D.assert(!ancestors.Contains(child),
|
|
() => "The supplied child is already an ancestor of this node. Loops are not allowed.");
|
|
FocusScopeNode oldScope = child.enclosingScope;
|
|
bool hadFocus = child.hasFocus;
|
|
child._parent?._removeChild(child, removeScopeFocus: oldScope != nearestScope);
|
|
_children.Add(child);
|
|
child._parent = this;
|
|
child._ancestors = null;
|
|
child._updateManager(_manager);
|
|
foreach (FocusNode ancestor in child.ancestors) {
|
|
ancestor._descendants = null;
|
|
}
|
|
|
|
if (hadFocus) {
|
|
_manager?.primaryFocus?._setAsFocusedChildForScope();
|
|
}
|
|
|
|
if (oldScope != null && child.context != null && child.enclosingScope != oldScope) {
|
|
//UnityEngine.Debug.Log("FocusTraversalGroup.of(child.context, nullOk: true)?.changedScope(node: child, oldScope: oldScope);");
|
|
FocusTraversalGroup.of(child.context, nullOk: true)?.changedScope(node: child, oldScope: oldScope);
|
|
}
|
|
|
|
if (child._requestFocusWhenReparented) {
|
|
child._doRequestFocus(findFirstFocus: true);
|
|
child._requestFocusWhenReparented = false;
|
|
}
|
|
}
|
|
|
|
public FocusAttachment attach(BuildContext context, FocusOnKeyCallback onKey = null) {
|
|
_context = context;
|
|
_onKey = onKey ?? _onKey;
|
|
_attachment = new FocusAttachment(this);
|
|
return _attachment;
|
|
}
|
|
|
|
|
|
public override void dispose() {
|
|
_attachment?.detach();
|
|
base.dispose();
|
|
}
|
|
|
|
|
|
public void _notify() {
|
|
if (_parent == null) {
|
|
return;
|
|
}
|
|
|
|
if (hasPrimaryFocus) {
|
|
_setAsFocusedChildForScope();
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
public void requestFocus(FocusNode node = null) {
|
|
if (node != null) {
|
|
if (node._parent == null) {
|
|
_reparent(node);
|
|
}
|
|
|
|
D.assert(node.ancestors.Contains(this),
|
|
() =>
|
|
"Focus was requested for a node that is not a descendant of the scope from which it was requested.");
|
|
node._doRequestFocus(findFirstFocus: true);
|
|
return;
|
|
}
|
|
|
|
_doRequestFocus(findFirstFocus: true);
|
|
}
|
|
|
|
// Note that this is overridden in FocusScopeNode.
|
|
public virtual void _doRequestFocus(bool findFirstFocus = false) {
|
|
D.assert(findFirstFocus != null);
|
|
if (!canRequestFocus) {
|
|
D.assert(FocusManagerUtils._focusDebug(
|
|
$"Node NOT requesting focus because canRequestFocus is false: {this}"));
|
|
return;
|
|
}
|
|
|
|
if (_parent == null) {
|
|
_requestFocusWhenReparented = true;
|
|
return;
|
|
}
|
|
|
|
_setAsFocusedChildForScope();
|
|
if (hasPrimaryFocus && (_manager._markedForFocus == null || _manager._markedForFocus == this)) {
|
|
return;
|
|
}
|
|
|
|
_hasKeyboardToken = true;
|
|
D.assert(FocusManagerUtils._focusDebug($"Node requesting focus: {this}"));
|
|
_markNextFocus(this);
|
|
}
|
|
|
|
|
|
bool _requestFocusWhenReparented = false;
|
|
|
|
|
|
public void _setAsFocusedChildForScope() {
|
|
FocusNode scopeFocus = this;
|
|
foreach (FocusScopeNode ancestor in ancestors) {
|
|
if (ancestor is FocusScopeNode) {
|
|
D.assert(scopeFocus != ancestor, () => "Somehow made a loop by setting focusedChild to its scope.");
|
|
D.assert(FocusManagerUtils._focusDebug($"Setting {scopeFocus} as focused child for scope:",
|
|
new List<string> {ancestor.ToString()}));
|
|
ancestor._focusedChildren.Remove(scopeFocus);
|
|
|
|
ancestor._focusedChildren.Add(scopeFocus);
|
|
scopeFocus = ancestor;
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool nextFocus() {
|
|
return FocusTraversalGroup.of(context).next(this);
|
|
}
|
|
|
|
public bool previousFocus() {
|
|
return FocusTraversalGroup.of(context).previous(this);
|
|
}
|
|
|
|
public bool focusInDirection(TraversalDirection direction) {
|
|
return FocusTraversalGroup.of(context).inDirection(this, direction);
|
|
}
|
|
|
|
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
base.debugFillProperties(properties);
|
|
properties.add(new DiagnosticsProperty<BuildContext>("context", context, defaultValue: null));
|
|
properties.add(new FlagProperty("canRequestFocus", value: canRequestFocus, ifFalse: "NOT FOCUSABLE", defaultValue: true));
|
|
properties.add(new FlagProperty("hasFocus", value: hasFocus && !hasPrimaryFocus, ifTrue: "IN FOCUS PATH", defaultValue: false));
|
|
properties.add(new FlagProperty("hasPrimaryFocus", value: hasPrimaryFocus, ifTrue: "PRIMARY FOCUS", defaultValue: false));
|
|
}
|
|
|
|
public override List<DiagnosticsNode> debugDescribeChildren() {
|
|
int count = 1;
|
|
return _children.Select((FocusNode child)=> {
|
|
return child.toDiagnosticsNode(name: $"Child {count++}");
|
|
}).ToList();
|
|
}
|
|
|
|
public override string toStringShort() {//override
|
|
bool hasDebugLabel = debugLabel != null && debugLabel.isNotEmpty();
|
|
string nullStr = "";
|
|
string extraData = $"{(hasDebugLabel ? debugLabel : nullStr)} " +
|
|
$"{(hasFocus && hasDebugLabel ? nullStr : nullStr)}" +
|
|
$"{(hasFocus && !hasPrimaryFocus ? "[IN FOCUS PATH]" : nullStr)}"+
|
|
$"{(hasPrimaryFocus ? "[PRIMARY FOCUS]" : nullStr)}";
|
|
return $"{foundation_.describeIdentity(this)}" + $"{(extraData.isNotEmpty() ? extraData : nullStr)}";
|
|
}
|
|
}
|
|
}
|
|
|
|
public class FocusScopeNode : FocusNode {
|
|
public FocusScopeNode(
|
|
string debugLabel = null,
|
|
FocusOnKeyCallback onKey = null,
|
|
bool skipTraversal = false,
|
|
bool canRequestFocus = true
|
|
) : base(
|
|
debugLabel: debugLabel,
|
|
onKey: onKey,
|
|
canRequestFocus: canRequestFocus,
|
|
skipTraversal: skipTraversal
|
|
) {}
|
|
|
|
public FocusScopeNode nearestScope {
|
|
get { return this; }
|
|
}
|
|
|
|
public bool isFirstFocus {
|
|
get {
|
|
return enclosingScope.focusedChild == this;
|
|
}
|
|
}
|
|
|
|
|
|
public FocusNode focusedChild {
|
|
get {
|
|
D.assert(_focusedChildren.isEmpty() || _focusedChildren.Last().enclosingScope == this,()=> "Focused child does not have the same idea of its enclosing scope as the scope does.");
|
|
return _focusedChildren.isNotEmpty() ? _focusedChildren.Last() : null;
|
|
}
|
|
}
|
|
|
|
public readonly List<FocusNode> _focusedChildren = new List<FocusNode>();
|
|
|
|
public void setFirstFocus(FocusScopeNode scope) {
|
|
D.assert(scope != null);
|
|
D.assert(scope != this, ()=>"Unexpected self-reference in setFirstFocus.");
|
|
D.assert(FocusManagerUtils._focusDebug($"Setting scope as first focus in {this} to node:", new List<string>{scope.ToString()}));
|
|
if (scope._parent == null) {
|
|
_reparent(scope);
|
|
}
|
|
D.assert(scope.ancestors.Contains(this), ()=>$"{typeof(FocusScopeNode)}" + $"{scope} must be a child of"+ $" {this} to set it as first focus.");
|
|
if (hasFocus) {
|
|
scope._doRequestFocus(findFirstFocus: true);
|
|
} else {
|
|
scope._setAsFocusedChildForScope();
|
|
}
|
|
}
|
|
|
|
public void autofocus(FocusNode node) {
|
|
D.assert(FocusManagerUtils._focusDebug($"Node autofocusing: {node}"));
|
|
if (focusedChild == null) {
|
|
if (node._parent == null) {
|
|
_reparent(node);
|
|
}
|
|
D.assert(node.ancestors.Contains(this), ()=>"Autofocus was requested for a node that is not a descendant of the scope from which it was requested.");
|
|
node._doRequestFocus(findFirstFocus: true);
|
|
}
|
|
}
|
|
|
|
public override void _doRequestFocus( bool findFirstFocus = false) {
|
|
D.assert(findFirstFocus != null);
|
|
|
|
// It is possible that a previously focused child is no longer focusable.
|
|
while (focusedChild != null && !focusedChild.canRequestFocus)
|
|
_focusedChildren.removeLast();
|
|
|
|
if (!findFirstFocus) {
|
|
if (canRequestFocus) {
|
|
_setAsFocusedChildForScope();
|
|
_markNextFocus(this);
|
|
}
|
|
return;
|
|
}
|
|
|
|
FocusNode primaryFocus = focusedChild ?? this;
|
|
|
|
while (primaryFocus is FocusScopeNode && (( FocusScopeNode)primaryFocus).focusedChild != null) {
|
|
FocusScopeNode scope = primaryFocus as FocusScopeNode;
|
|
primaryFocus = scope.focusedChild;
|
|
}
|
|
if (primaryFocus == this) {
|
|
|
|
if (primaryFocus.canRequestFocus) {
|
|
_setAsFocusedChildForScope();
|
|
_markNextFocus(this);
|
|
}
|
|
} else {
|
|
primaryFocus._doRequestFocus(findFirstFocus: findFirstFocus);
|
|
}
|
|
}
|
|
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
base.debugFillProperties(properties);
|
|
if (_focusedChildren.isEmpty()) {
|
|
return;
|
|
}
|
|
List<string> childList = new List<string>();
|
|
_focusedChildren.Reverse();
|
|
childList = _focusedChildren.Select((FocusNode child)=> {
|
|
return child.toStringShort();
|
|
}).ToList();
|
|
properties.add(new EnumerableProperty<string>("focusedChildren", childList, defaultValue: new List<string>()));
|
|
}
|
|
}
|
|
|
|
public class FocusManager : DiagnosticableTreeMixinChangeNotifier{
|
|
public FocusManager()
|
|
{
|
|
rootScope._manager = this;
|
|
RawKeyboard.instance.addListener(_handleRawKeyEvent);
|
|
GestureBinding.instance.pointerRouter.addGlobalRoute(_handlePointerEvent);
|
|
}
|
|
public static FocusManager instance {
|
|
get {
|
|
return WidgetsBinding.instance.focusManager;
|
|
}
|
|
}
|
|
|
|
bool _lastInteractionWasTouch = true;
|
|
|
|
|
|
FocusHighlightStrategy highlightStrategy {
|
|
get {
|
|
return _highlightStrategy;
|
|
}
|
|
set {
|
|
_highlightStrategy = highlightStrategy;
|
|
_updateHighlightMode();
|
|
}
|
|
}
|
|
FocusHighlightStrategy _highlightStrategy = FocusHighlightStrategy.automatic;
|
|
|
|
public FocusHighlightMode highlightMode {
|
|
get {
|
|
return _highlightMode;
|
|
}
|
|
}
|
|
FocusHighlightMode _highlightMode = FocusHighlightMode.touch;
|
|
|
|
|
|
void _updateHighlightMode() {
|
|
_lastInteractionWasTouch = false;//(Platform.isAndroid || Platform.isIOS || !WidgetsBinding.instance.mouseTracker.mouseIsConnected);
|
|
FocusHighlightMode newMode = FocusHighlightMode.touch;
|
|
switch (highlightStrategy) {
|
|
case FocusHighlightStrategy.automatic:
|
|
if (_lastInteractionWasTouch) {
|
|
newMode = FocusHighlightMode.touch;
|
|
} else {
|
|
newMode = FocusHighlightMode.traditional;
|
|
}
|
|
break;
|
|
case FocusHighlightStrategy.alwaysTouch:
|
|
newMode = FocusHighlightMode.touch;
|
|
break;
|
|
case FocusHighlightStrategy.alwaysTraditional:
|
|
newMode = FocusHighlightMode.traditional;
|
|
break;
|
|
}
|
|
if (newMode != _highlightMode) {
|
|
_highlightMode = newMode;
|
|
_notifyHighlightModeListeners();
|
|
}
|
|
}
|
|
|
|
// The list of listeners for [highlightMode] state changes.
|
|
public readonly List<ValueChanged<FocusHighlightMode>> _listeners = new List<ValueChanged<FocusHighlightMode>>();
|
|
|
|
public void addHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.Add(listener);
|
|
|
|
public void removeHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners?.Remove(listener);
|
|
|
|
void _notifyHighlightModeListeners() {
|
|
if (_listeners.isEmpty()) {
|
|
return;
|
|
}
|
|
List<ValueChanged<FocusHighlightMode>> localListeners = new List<ValueChanged<FocusHighlightMode>>();
|
|
foreach (var listener in _listeners) {
|
|
localListeners.Add(listener);
|
|
}
|
|
foreach( ValueChanged<FocusHighlightMode> listener in localListeners) {
|
|
if (_listeners.Contains(listener)) {
|
|
listener(_highlightMode);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public readonly FocusScopeNode rootScope = new FocusScopeNode(debugLabel: "Root Focus Scope");
|
|
|
|
void _handlePointerEvent(PointerEvent Event) {
|
|
bool currentInteractionIsTouch = false;
|
|
switch (Event.kind) {
|
|
case PointerDeviceKind.touch:
|
|
case PointerDeviceKind.stylus:
|
|
case PointerDeviceKind.invertedStylus:
|
|
currentInteractionIsTouch = true;
|
|
break;
|
|
case PointerDeviceKind.mouse:
|
|
case PointerDeviceKind.unknown:
|
|
currentInteractionIsTouch = false;
|
|
break;
|
|
}
|
|
if (_lastInteractionWasTouch != currentInteractionIsTouch) {
|
|
_lastInteractionWasTouch = currentInteractionIsTouch;
|
|
_updateHighlightMode();
|
|
}
|
|
}
|
|
|
|
void _handleRawKeyEvent(RawKeyEvent Event) {
|
|
|
|
if (_lastInteractionWasTouch) {
|
|
_lastInteractionWasTouch = false;
|
|
_updateHighlightMode();
|
|
}
|
|
|
|
//D.assert(FocusManagerUtils._focusDebug($"Received key event {Event.logicalKey}"));
|
|
|
|
if (_primaryFocus == null) {
|
|
D.assert(FocusManagerUtils._focusDebug($"No primary focus for key event, ignored: {Event}"));
|
|
return;
|
|
}
|
|
|
|
bool handled = false;
|
|
List<FocusNode> nodes = new List<FocusNode>();
|
|
nodes.Add(_primaryFocus);
|
|
foreach (var node in _primaryFocus.ancestors) {
|
|
nodes.Add(node);
|
|
|
|
}
|
|
foreach (FocusNode node in nodes) {
|
|
if (node.onKey != null && node.onKey(node, Event)) {
|
|
D.assert(FocusManagerUtils._focusDebug($"Node {node} handled key event {Event}."));
|
|
handled = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!handled) {
|
|
D.assert(FocusManagerUtils._focusDebug($"Key event not handled by anyone: {Event}."));
|
|
}
|
|
}
|
|
|
|
|
|
public FocusNode primaryFocus {
|
|
get{return _primaryFocus;}
|
|
}
|
|
FocusNode _primaryFocus;
|
|
|
|
|
|
public readonly HashSet<FocusNode> _dirtyNodes = new HashSet<FocusNode>();
|
|
|
|
public FocusNode _markedForFocus;
|
|
|
|
public void _markDetached(FocusNode node) {
|
|
D.assert(FocusManagerUtils._focusDebug($"Node was detached: {node}"));
|
|
if (_primaryFocus == node) {
|
|
_primaryFocus = null;
|
|
}
|
|
_dirtyNodes?.Remove(node);
|
|
}
|
|
|
|
public void _markPropertiesChanged(FocusNode node) {
|
|
_markNeedsUpdate();
|
|
D.assert(FocusManagerUtils._focusDebug($"Properties changed for node {node}."));
|
|
_dirtyNodes?.Add(node);
|
|
}
|
|
|
|
public void _markNextFocus(FocusNode node) {
|
|
if (_primaryFocus == node) {
|
|
_markedForFocus = null;
|
|
} else {
|
|
_markedForFocus = node;
|
|
_markNeedsUpdate();
|
|
}
|
|
}
|
|
|
|
// True indicates that there is an update pending.
|
|
bool _haveScheduledUpdate = false;
|
|
|
|
void _markNeedsUpdate() {
|
|
D.assert(FocusManagerUtils._focusDebug($"Scheduling update, current focus is {_primaryFocus}, next focus will be {_markedForFocus}"));
|
|
if (_haveScheduledUpdate) {
|
|
return;
|
|
}
|
|
_haveScheduledUpdate = true;
|
|
async_.scheduleMicrotask(()=> {
|
|
_applyFocusChange();
|
|
return null;
|
|
});
|
|
}
|
|
void _applyFocusChange() {
|
|
_haveScheduledUpdate = false;
|
|
FocusNode previousFocus = _primaryFocus;
|
|
if (_primaryFocus == null && _markedForFocus == null) {
|
|
|
|
_markedForFocus = rootScope;
|
|
}
|
|
D.assert(FocusManagerUtils._focusDebug($"Refreshing focus state. Next focus will be {_markedForFocus}"));
|
|
|
|
if (_markedForFocus != null && _markedForFocus != _primaryFocus) {
|
|
HashSet<FocusNode> previousPath = previousFocus?.ancestors != null ? new HashSet<FocusNode>(previousFocus.ancestors) : new HashSet<FocusNode>();
|
|
HashSet<FocusNode> nextPath = new HashSet<FocusNode>(_markedForFocus.ancestors);
|
|
foreach(FocusNode node in FocusTravesalUtils.difference(nextPath,previousPath)) {
|
|
_dirtyNodes.Add(node);
|
|
}
|
|
foreach(FocusNode node in FocusTravesalUtils.difference(previousPath,nextPath)) {
|
|
_dirtyNodes.Add(node);
|
|
}
|
|
|
|
_primaryFocus = _markedForFocus;
|
|
_markedForFocus = null;
|
|
}
|
|
if (previousFocus != _primaryFocus) {
|
|
D.assert(FocusManagerUtils._focusDebug($"Updating focus from {previousFocus} to {_primaryFocus}"));
|
|
if (previousFocus != null) {
|
|
_dirtyNodes.Add(previousFocus);
|
|
}
|
|
if (_primaryFocus != null) {
|
|
_dirtyNodes.Add(_primaryFocus);
|
|
}
|
|
}
|
|
D.assert(FocusManagerUtils._focusDebug($"Notifying {_dirtyNodes.Count} dirty nodes:",
|
|
_dirtyNodes.ToList().Select((FocusNode node) => {
|
|
return node.toString();
|
|
}).ToList()));
|
|
foreach ( FocusNode node in _dirtyNodes) {
|
|
node._notify();
|
|
}
|
|
_dirtyNodes.Clear();
|
|
if (previousFocus != _primaryFocus) {
|
|
notifyListeners();
|
|
}
|
|
D.assert(()=> {
|
|
if (FocusManagerUtils._kDebugFocus) {
|
|
FocusManagerUtils.debugDumpFocusTree();
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
|
|
|
|
public override List<DiagnosticsNode> debugDescribeChildren() {
|
|
return new List<DiagnosticsNode>{
|
|
rootScope.toDiagnosticsNode(name: "rootScope")
|
|
};
|
|
}
|
|
|
|
|
|
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
properties.add(new FlagProperty("haveScheduledUpdate", value: _haveScheduledUpdate, ifTrue: "UPDATE SCHEDULED"));
|
|
properties.add(new DiagnosticsProperty<FocusNode>("primaryFocus", primaryFocus, defaultValue: null));
|
|
properties.add(new DiagnosticsProperty<FocusNode>("nextFocus", _markedForFocus, defaultValue: null));
|
|
Element element = primaryFocus?.context as Element;
|
|
if (element != null) {
|
|
properties.add(new DiagnosticsProperty<String>("primaryFocusCreator", element.debugGetCreatorChain(20)));
|
|
}
|
|
}
|
|
}
|
|
|