您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
315 行
16 KiB
315 行
16 KiB
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.ui;
|
|
|
|
namespace Unity.UIWidgets.widgets {
|
|
{% macro DirectionalFocusTraversalPolicyMixin(with) %}
|
|
{% set className = 'DirectionalFocusTraversalPolicyMixin' + with %}
|
|
public class {{className}} : {{with}}, DirectionalFocusTraversalPolicyMixin {
|
|
protected {{className}}() {
|
|
}
|
|
|
|
public readonly Dictionary<FocusScopeNode, _DirectionalPolicyData> _policyData = new Dictionary<FocusScopeNode, _DirectionalPolicyData>();
|
|
public override void invalidateScopeData(FocusScopeNode node) {
|
|
base.invalidateScopeData(node);
|
|
_policyData.Remove(node);
|
|
}
|
|
public override void changedScope(FocusNode node = null, FocusScopeNode oldScope = null) {
|
|
base.changedScope(node: node, oldScope: oldScope);
|
|
if (oldScope != null) {
|
|
var delEntries = _policyData[oldScope]?.history?.Where((_DirectionalPolicyDataEntry entry)=> {
|
|
return entry.node == node;
|
|
});
|
|
foreach (var delEntry in delEntries) {
|
|
_policyData[oldScope]?.history?.Remove(delEntry);
|
|
}
|
|
}
|
|
}
|
|
public override FocusNode findFirstFocusInDirection(FocusNode currentNode, TraversalDirection direction) {
|
|
D.assert(direction != null);
|
|
D.assert(currentNode != null);
|
|
switch (direction) {
|
|
case TraversalDirection.up:
|
|
return _sortAndFindInitial(currentNode, vertical: true, first: false);
|
|
case TraversalDirection.down:
|
|
return _sortAndFindInitial(currentNode, vertical: true, first: true);
|
|
case TraversalDirection.left:
|
|
return _sortAndFindInitial(currentNode, vertical: false, first: false);
|
|
case TraversalDirection.right:
|
|
return _sortAndFindInitial(currentNode, vertical: false, first: true);
|
|
}
|
|
return null;
|
|
}
|
|
public FocusNode _sortAndFindInitial(FocusNode currentNode, bool vertical = false, bool first = false) {
|
|
IEnumerable<FocusNode> nodes = currentNode.nearestScope.traversalDescendants;
|
|
List<FocusNode> sorted = nodes.ToList();
|
|
FocusTravesalUtils.mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b)=> {
|
|
if (vertical) {
|
|
if (first) {
|
|
return a.rect.top.CompareTo(b.rect.top);
|
|
} else {
|
|
return b.rect.bottom.CompareTo(a.rect.bottom);
|
|
}
|
|
} else {
|
|
if (first) {
|
|
return a.rect.left.CompareTo(b.rect.left);
|
|
} else {
|
|
return b.rect.right.CompareTo(a.rect.right);
|
|
}
|
|
}
|
|
});
|
|
if (sorted.isNotEmpty()) {
|
|
return sorted.First();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public IEnumerable<FocusNode> _sortAndFilterHorizontally(
|
|
TraversalDirection direction,
|
|
Rect target,
|
|
FocusNode nearestScope)
|
|
{
|
|
D.assert(direction == TraversalDirection.left || direction == TraversalDirection.right);
|
|
IEnumerable<FocusNode> nodes = nearestScope.traversalDescendants;
|
|
D.assert(!nodes.Contains(nearestScope));
|
|
List<FocusNode> sorted = nodes.ToList();
|
|
FocusTravesalUtils.mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b) => a.rect.center.dx.CompareTo(b.rect.center.dx));
|
|
IEnumerable<FocusNode> result = new List<FocusNode>();
|
|
switch (direction) {
|
|
case TraversalDirection.left:
|
|
result = sorted.Where((FocusNode node) => node.rect != target && node.rect.center.dx <= target.left);
|
|
break;
|
|
case TraversalDirection.right:
|
|
result = sorted.Where((FocusNode node) => node.rect != target && node.rect.center.dx >= target.right);
|
|
break;
|
|
case TraversalDirection.up:
|
|
case TraversalDirection.down:
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
public IEnumerable<FocusNode> _sortAndFilterVertically(
|
|
TraversalDirection direction,
|
|
Rect target,
|
|
IEnumerable<FocusNode> nodes)
|
|
{
|
|
List<FocusNode> sorted = nodes.ToList();
|
|
FocusTravesalUtils.mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b) => a.rect.center.dy.CompareTo(b.rect.center.dy));
|
|
switch (direction) {
|
|
case TraversalDirection.up:
|
|
return sorted.Where((FocusNode node) => node.rect != target && node.rect.center.dy <= target.top);
|
|
case TraversalDirection.down:
|
|
return sorted.Where((FocusNode node) => node.rect != target && node.rect.center.dy >= target.bottom);
|
|
case TraversalDirection.left:
|
|
case TraversalDirection.right:
|
|
break;
|
|
}
|
|
D.assert(direction == TraversalDirection.up || direction == TraversalDirection.down);
|
|
return null;
|
|
}
|
|
public bool _popPolicyDataIfNeeded(TraversalDirection direction, FocusScopeNode nearestScope, FocusNode focusedChild) {
|
|
_DirectionalPolicyData policyData = _policyData[nearestScope];
|
|
if (policyData != null && policyData.history.isNotEmpty() && policyData.history.First().direction != direction) {
|
|
if (policyData.history.Last().node.parent == null) {
|
|
invalidateScopeData(nearestScope);
|
|
return false;
|
|
|
|
}
|
|
bool popOrInvalidate(TraversalDirection _direction) {
|
|
FocusNode lastNode = policyData.history.removeLast().node;
|
|
if (Scrollable.of(lastNode.context) != Scrollable.of(FocusManagerUtils.primaryFocus.context)) {
|
|
invalidateScopeData(nearestScope);
|
|
return false;
|
|
}
|
|
ScrollPositionAlignmentPolicy alignmentPolicy = ScrollPositionAlignmentPolicy.explicitPolicy;
|
|
switch (_direction) {
|
|
case TraversalDirection.up:
|
|
case TraversalDirection.left:
|
|
alignmentPolicy = ScrollPositionAlignmentPolicy.keepVisibleAtStart;
|
|
break;
|
|
case TraversalDirection.right:
|
|
case TraversalDirection.down:
|
|
alignmentPolicy = ScrollPositionAlignmentPolicy.keepVisibleAtEnd;
|
|
break;
|
|
}
|
|
FocusTravesalUtils._focusAndEnsureVisible(
|
|
lastNode,
|
|
alignmentPolicy: alignmentPolicy
|
|
);
|
|
return true;
|
|
}
|
|
switch (direction) {
|
|
case TraversalDirection.down:
|
|
case TraversalDirection.up:
|
|
switch (policyData.history.First().direction) {
|
|
case TraversalDirection.left:
|
|
case TraversalDirection.right:
|
|
invalidateScopeData(nearestScope);
|
|
break;
|
|
case TraversalDirection.up:
|
|
case TraversalDirection.down:
|
|
if (popOrInvalidate(direction)) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case TraversalDirection.left:
|
|
case TraversalDirection.right:
|
|
switch (policyData.history.First().direction) {
|
|
case TraversalDirection.left:
|
|
case TraversalDirection.right:
|
|
if (popOrInvalidate(direction)) {
|
|
return true;
|
|
}
|
|
break;
|
|
case TraversalDirection.up:
|
|
case TraversalDirection.down:
|
|
invalidateScopeData(nearestScope);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (policyData != null && policyData.history.isEmpty()) {
|
|
invalidateScopeData(nearestScope);
|
|
}
|
|
return false;
|
|
|
|
}
|
|
public void _pushPolicyData(TraversalDirection direction, FocusScopeNode nearestScope, FocusNode focusedChild) {
|
|
_DirectionalPolicyData policyData = _policyData[nearestScope];
|
|
if (policyData != null && !(policyData is _DirectionalPolicyData)) {
|
|
return;
|
|
}
|
|
_DirectionalPolicyDataEntry newEntry = new _DirectionalPolicyDataEntry(node: focusedChild, direction: direction);
|
|
if (policyData != null) {
|
|
policyData.history.Add(newEntry);
|
|
} else {
|
|
_policyData[nearestScope] = new _DirectionalPolicyData(history: new List<_DirectionalPolicyDataEntry>(){newEntry});
|
|
}
|
|
}
|
|
public override bool inDirection(FocusNode currentNode, TraversalDirection direction) {
|
|
FocusScopeNode nearestScope = currentNode.nearestScope;
|
|
FocusNode focusedChild = nearestScope.focusedChild;
|
|
if (focusedChild == null) {
|
|
FocusNode firstFocus = findFirstFocusInDirection(currentNode, direction) ?? currentNode;
|
|
switch (direction) {
|
|
case TraversalDirection.up:
|
|
case TraversalDirection.left:
|
|
FocusTravesalUtils._focusAndEnsureVisible(
|
|
firstFocus,
|
|
alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart
|
|
);
|
|
break;
|
|
case TraversalDirection.right:
|
|
case TraversalDirection.down:
|
|
FocusTravesalUtils._focusAndEnsureVisible(
|
|
firstFocus,
|
|
alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd
|
|
);
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
if (_popPolicyDataIfNeeded(direction, nearestScope, focusedChild)) {
|
|
return true;
|
|
}
|
|
FocusNode found = null;
|
|
ScrollableState focusedScrollable = Scrollable.of(focusedChild.context);
|
|
switch (direction) {
|
|
case TraversalDirection.down:
|
|
case TraversalDirection.up:
|
|
IEnumerable<FocusNode> eligibleNodes = _sortAndFilterVertically(
|
|
direction,
|
|
focusedChild.rect,
|
|
nearestScope.traversalDescendants
|
|
);
|
|
if (focusedScrollable != null && !focusedScrollable.position.atEdge()) {
|
|
IEnumerable<FocusNode> filteredEligibleNodes = eligibleNodes.Where((FocusNode node) => Scrollable.of(node.context) == focusedScrollable);
|
|
if (filteredEligibleNodes.Count() !=0) {
|
|
eligibleNodes = filteredEligibleNodes;
|
|
}
|
|
}
|
|
if (eligibleNodes.Count() == 0) {
|
|
break;
|
|
}
|
|
List<FocusNode> sorted = eligibleNodes.ToList();
|
|
if (direction == TraversalDirection.up) {
|
|
//sorted = sorted.reversed.toList();
|
|
sorted.Reverse();
|
|
sorted = sorted.ToList();
|
|
}
|
|
Rect band = Rect.fromLTRB(focusedChild.rect.left, float.NegativeInfinity, focusedChild.rect.right, float.PositiveInfinity);
|
|
IEnumerable<FocusNode> inBand = sorted.Where((FocusNode node) => !node.rect.intersect(band).isEmpty);
|
|
if (inBand.Count() !=0) {
|
|
found = inBand.First();
|
|
break;
|
|
}
|
|
FocusTravesalUtils.mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b)=> {
|
|
return (a.rect.center.dx - focusedChild.rect.center.dx).abs().CompareTo((b.rect.center.dx - focusedChild.rect.center.dx).abs());
|
|
});
|
|
found = sorted.First();
|
|
break;
|
|
case TraversalDirection.right:
|
|
case TraversalDirection.left:
|
|
eligibleNodes = _sortAndFilterHorizontally(direction, focusedChild.rect, nearestScope);
|
|
if (focusedScrollable != null && !focusedScrollable.position.atEdge()) {
|
|
IEnumerable<FocusNode> filteredEligibleNodes = eligibleNodes.Where((FocusNode node) => Scrollable.of(node.context) == focusedScrollable);
|
|
if (filteredEligibleNodes.Count()!=0) {
|
|
eligibleNodes = filteredEligibleNodes;
|
|
}
|
|
}
|
|
if (eligibleNodes.Count() == 0) {
|
|
break;
|
|
}
|
|
sorted = eligibleNodes.ToList();
|
|
if (direction == TraversalDirection.left) {
|
|
sorted.Reverse();
|
|
sorted = sorted.ToList();
|
|
//sorted = sorted.reversed.toList();
|
|
}
|
|
band = Rect.fromLTRB(float.NegativeInfinity, focusedChild.rect.top, float.PositiveInfinity, focusedChild.rect.bottom);
|
|
inBand = sorted.Where((FocusNode node) => !node.rect.intersect(band).isEmpty);
|
|
if (inBand.Count()!=0) {
|
|
found = inBand.First();
|
|
break;
|
|
}
|
|
FocusTravesalUtils.mergeSort<FocusNode>(sorted, compare: (FocusNode a, FocusNode b) =>{
|
|
return (a.rect.center.dy - focusedChild.rect.center.dy).abs().CompareTo((b.rect.center.dy - focusedChild.rect.center.dy).abs());
|
|
});
|
|
found = sorted.First();
|
|
break;
|
|
}
|
|
if (found != null) {
|
|
_pushPolicyData(direction, nearestScope, focusedChild);
|
|
switch (direction) {
|
|
case TraversalDirection.up:
|
|
case TraversalDirection.left:
|
|
FocusTravesalUtils._focusAndEnsureVisible(
|
|
found,
|
|
alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart
|
|
);
|
|
break;
|
|
case TraversalDirection.down:
|
|
case TraversalDirection.right:
|
|
FocusTravesalUtils._focusAndEnsureVisible(
|
|
found,
|
|
alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd
|
|
);
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
public override IEnumerable<FocusNode> sortDescendants(IEnumerable<FocusNode> descendants) {
|
|
return null;
|
|
}
|
|
|
|
}
|
|
{% endmacro %}
|
|
{{ DirectionalFocusTraversalPolicyMixin('FocusTraversalPolicy') }}
|
|
}
|
|
|