예제 #1
0
        internal void _remove(FocusScopeNode child)
        {
            D.assert(child._parent == this);
            D.assert(child._manager == this._manager);
            D.assert(this._debugUltimatePreviousSiblingOf(child, equals: this._firstChild));
            D.assert(this._debugUltimateNextSiblingOf(child, equals: this._lastChild));
            if (child._previousSibling == null)
            {
                D.assert(this._firstChild == child);
                this._firstChild = child._nextSibling;
            }
            else
            {
                child._previousSibling._nextSibling = child._nextSibling;
            }

            if (child._nextSibling == null)
            {
                D.assert(this._lastChild == child);
                this._lastChild = child._previousSibling;
            }
            else
            {
                child._nextSibling._previousSibling = child._previousSibling;
            }

            child._previousSibling = null;
            child._nextSibling     = null;
            child._parent          = null;
            child._updateManager(null);
        }
예제 #2
0
        internal void _prepend(FocusScopeNode child)
        {
            D.assert(child != this);
            D.assert(child != this._firstChild);
            D.assert(child != this._lastChild);
            D.assert(child._parent == null);
            D.assert(child._manager == null);
            D.assert(child._nextSibling == null);
            D.assert(child._previousSibling == null);
            D.assert(() => {
                var node = this;
                while (node._parent != null)
                {
                    node = node._parent;
                }

                D.assert(node != child);
                return(true);
            });
            child._parent      = this;
            child._nextSibling = this._firstChild;
            if (this._firstChild != null)
            {
                this._firstChild._previousSibling = child;
            }

            this._firstChild = child;
            this._lastChild  = this._lastChild ?? child;
            child._updateManager(this._manager);
        }
예제 #3
0
        void _visitChildren(Action <FocusScopeNode> vistor)
        {
            FocusScopeNode child = this._firstChild;

            while (child != null)
            {
                vistor.Invoke(child);
                child = child._nextSibling;
            }
        }
예제 #4
0
        bool _debugUltimateNextSiblingOf(FocusScopeNode child, FocusScopeNode equals)
        {
            while (child._nextSibling != null)
            {
                D.assert(child._nextSibling != child);
                child = child._nextSibling;
            }

            return(child == equals);
        }
예제 #5
0
        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}"
            }));
        }
예제 #6
0
        internal FocusNode _findNextFocus()
        {
            FocusScopeNode scope = this.rootScope;

            while (scope._firstChild != null)
            {
                scope = scope._firstChild;
            }

            return(scope._focus);
        }
예제 #7
0
 internal void _setFocus(FocusNode node)
 {
     D.assert(node != null);
     D.assert(node._parent == null);
     D.assert(this._focus == null);
     this._focus                   = node;
     this._focus._parent           = this;
     this._focus._manager          = this._manager;
     this._focus._hasKeyboardToken = true;
     this._didChangeFocusChain();
 }
예제 #8
0
        internal void _resignFocus(FocusNode node)
        {
            D.assert(node != null);
            if (this._focus != node)
            {
                return;
            }

            this._focus._parent  = null;
            this._focus._manager = null;
            this._focus          = null;
            this._didChangeFocusChain();
        }
예제 #9
0
        public void setFirstFocus(FocusScopeNode child)
        {
            D.assert(child != null);
            D.assert(child._parent == null || child._parent == this);
            if (this._firstChild == child)
            {
                return;
            }

            child.detach();
            this._prepend(child);
            D.assert(child._parent == this);
            this._didChangeFocusChain();
        }
예제 #10
0
        public virtual FocusNode findFirstFocus(FocusNode currentNode)
        {
            D.assert(currentNode != null);
            FocusScopeNode scope     = currentNode.nearestScope;
            FocusNode      candidate = scope.focusedChild;

            if (candidate == null && scope.descendants.Any())
            {
                IEnumerable <FocusNode> sorted = _sortAllDescendants(scope);
                candidate = sorted.Any() ? sorted.First() : null;
            }

            candidate = candidate ?? currentNode;
            return(candidate);
        }
예제 #11
0
        public override void changedScope(FocusNode node = null, FocusScopeNode oldScope = null)
        {
            base.changedScope(node: node, oldScope: oldScope);
            if (oldScope != null)
            {
                var delEntries = LinqUtils <_DirectionalPolicyDataEntry> .WhereList(_policyData[oldScope]?.history, ((_DirectionalPolicyDataEntry entry) => {
                    return(entry.node == node);
                }));

                foreach (var delEntry in delEntries)
                {
                    _policyData[oldScope]?.history?.Remove(delEntry);
                }
            }
        }
예제 #12
0
        internal List <FocusScopeNode> _getFocusPath()
        {
            List <FocusScopeNode> nodes = new List <FocusScopeNode> {
                this
            };
            FocusScopeNode node = this._parent;

            while (node != null && node != this._manager?.rootScope)
            {
                nodes.Add(node);
                node = node._parent;
            }

            return(nodes);
        }
예제 #13
0
        public void reparentScopeIfNeeded(FocusScopeNode child)
        {
            D.assert(child != null);
            if (child._parent == null || child._parent == this)
            {
                return;
            }

            if (child.isFirstFocus)
            {
                this.setFirstFocus(child);
            }
            else
            {
                child.detach();
            }
        }
예제 #14
0
        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;
            }
        }
예제 #15
0
        public override Widget build(BuildContext context)
        {
            List <Widget> slivers       = buildSlivers(context);
            AxisDirection axisDirection = getDirection(context);

            ScrollController scrollController = primary ? PrimaryScrollController.of(context) : controller;

            Scrollable scrollable = new Scrollable(
                dragStartBehavior: dragStartBehavior,
                axisDirection: axisDirection,
                controller: scrollController,
                physics: physics,
                viewportBuilder: (viewportContext, offset) =>
                buildViewport(viewportContext, offset, axisDirection, slivers)
                );

            Widget scrollableResult = primary && scrollController != null
                ? (Widget)PrimaryScrollController.none(child: scrollable)
                : scrollable;

            if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag)
            {
                return(new NotificationListener <ScrollUpdateNotification>(
                           child: scrollableResult,
                           onNotification: (ScrollUpdateNotification notification) => {
                    FocusScopeNode focusScope = FocusScope.of(context);
                    if (notification.dragDetails != null && focusScope.hasFocus)
                    {
                        focusScope.unfocus();
                    }

                    return false;
                }
                           ));
            }
            else
            {
                return(scrollableResult);
            }
        }
예제 #16
0
        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
                });
            }
        }
예제 #17
0
        public override List <DiagnosticsNode> debugDescribeChildren()
        {
            var children = new List <DiagnosticsNode>();

            if (this._firstChild != null)
            {
                FocusScopeNode child = this._firstChild;
                int            count = 1;
                while (true)
                {
                    children.Add(child.toDiagnosticsNode(name: $"child {count}"));
                    if (child == this._lastChild)
                    {
                        break;
                    }

                    child  = child._nextSibling;
                    count += 1;
                }
            }

            return(children);
        }
예제 #18
0
 public FocusScope(
     Key key             = null,
     FocusScopeNode node = null,
     Widget child        = null,
     bool autofocus      = false,
     ValueChanged <bool> onFocusChange = null,
     bool?canRequestFocus     = null,
     bool?skipTraversal       = null,
     FocusOnKeyCallback onKey = null,
     string debugLabel        = null
     ) : base(
         key: key,
         child: child,
         focusNode: node,
         autofocus: autofocus,
         onFocusChange: onFocusChange,
         canRequestFocus: canRequestFocus,
         skipTraversal: skipTraversal,
         onKey: onKey,
         debugLabel: debugLabel)
 {
     D.assert(child != null);
 }
예제 #19
0
        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 = LinqUtils <FocusNode> .WhereList(eligibleNodes, ((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 = LinqUtils <FocusNode> .WhereList(sorted, ((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 = LinqUtils <FocusNode> .WhereList(eligibleNodes, ((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 = LinqUtils <FocusNode> .WhereList(sorted, ((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);
        }
예제 #20
0
        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);
        }
예제 #21
0
 public override void invalidateScopeData(FocusScopeNode node)
 {
     base.invalidateScopeData(node);
     _policyData.Remove(node);
 }
예제 #22
0
        protected bool _moveFocus(FocusNode currentNode, bool forward = false)
        {
            D.assert(forward != null);
            if (currentNode == null)
            {
                return(false);
            }
            FocusScopeNode nearestScope = currentNode.nearestScope;

            invalidateScopeData(nearestScope);
            FocusNode focusedChild = nearestScope.focusedChild;

            if (focusedChild == null)
            {
                FocusNode firstFocus = findFirstFocus(currentNode);
                if (firstFocus != null)
                {
                    FocusTravesalUtils._focusAndEnsureVisible(
                        firstFocus,
                        alignmentPolicy: forward ? ScrollPositionAlignmentPolicy.keepVisibleAtEnd : ScrollPositionAlignmentPolicy.keepVisibleAtStart
                        );
                    return(true);
                }
            }
            List <FocusNode> sortedNodes = _sortAllDescendants(nearestScope);

            if (forward && focusedChild == sortedNodes.Last())
            {
                FocusTravesalUtils._focusAndEnsureVisible(sortedNodes.First(), alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd);
                return(true);
            }
            if (!forward && focusedChild == sortedNodes.First())
            {
                FocusTravesalUtils._focusAndEnsureVisible(sortedNodes.Last(), alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart);
                return(true);
            }

            IEnumerable <FocusNode> maybeFlipped = new List <FocusNode>();

            if (forward)
            {
                maybeFlipped = sortedNodes;
            }
            else
            {
                sortedNodes.Reverse();
                maybeFlipped = sortedNodes;
            }
            FocusNode previousNode = null;

            foreach (FocusNode node in maybeFlipped)
            {
                if (previousNode == focusedChild)
                {
                    FocusTravesalUtils._focusAndEnsureVisible(
                        node,
                        alignmentPolicy: forward ? ScrollPositionAlignmentPolicy.keepVisibleAtEnd : ScrollPositionAlignmentPolicy.keepVisibleAtStart
                        );
                    return(true);
                }
                previousNode = node;
            }
            return(false);
        }
예제 #23
0
        public List <FocusNode> _sortAllDescendants(FocusScopeNode scope)
        {
            D.assert(scope != null);
            _FocusTraversalGroupMarker scopeGroupMarker             = _getMarker(scope.context);
            FocusTraversalPolicy       defaultPolicy                = scopeGroupMarker?.policy ?? new ReadingOrderTraversalPolicy();
            Dictionary <FocusNode, _FocusTraversalGroupInfo> groups = new Dictionary <FocusNode, _FocusTraversalGroupInfo>();

            foreach (FocusNode node in scope.descendants)
            {
                _FocusTraversalGroupMarker groupMarker = _getMarker(node.context);
                FocusNode groupNode = groupMarker?.focusNode;
                if (node == groupNode)
                {
                    BuildContext parentContext = FocusTravesalUtils._getAncestor(groupNode.context, count: 2);
                    _FocusTraversalGroupMarker parentMarker = _getMarker(parentContext);
                    FocusNode parentNode = parentMarker?.focusNode;
                    groups[groupNode] = groups.getOrDefault(parentNode) ?? new _FocusTraversalGroupInfo(parentMarker, members: new List <FocusNode>(), defaultPolicy: defaultPolicy);
                    D.assert(!groups[parentNode].members.Contains(node));
                    groups[parentNode].members.Add(groupNode);
                    continue;
                }
                if (node.canRequestFocus && !node.skipTraversal)
                {
                    groups[groupNode] = groups.getOrDefault(groupNode) ?? new _FocusTraversalGroupInfo(groupMarker, members: new List <FocusNode>(), defaultPolicy: defaultPolicy);
                    D.assert(!groups[groupNode].members.Contains(node));
                    groups[groupNode].members.Add(node);
                }
            }
            HashSet <FocusNode> groupKeys = new HashSet <FocusNode>(groups.Keys);

            foreach (FocusNode key in groups.Keys)
            {
                List <FocusNode> sortedMembers = groups.getOrDefault(key).policy.sortDescendants(groups.getOrDefault(key).members).ToList();
                groups[key].members.Clear();
                groups[key].members.AddRange(sortedMembers);
            }

            List <FocusNode> sortedDescendants = new List <FocusNode>();

            void visitGroups(_FocusTraversalGroupInfo info)
            {
                foreach (FocusNode node in info.members)
                {
                    if (groupKeys.Contains(node))
                    {
                        visitGroups(groups[node]);
                    }
                    else
                    {
                        sortedDescendants.Add(node);
                    }
                }
            }

            visitGroups(groups[scopeGroupMarker?.focusNode]);
            D.assert(
                FocusTravesalUtils.difference(new HashSet <FocusNode>(sortedDescendants), (new HashSet <FocusNode>(scope.traversalDescendants))).isEmpty(),
                () => $"sorted descendants contains more nodes than it should: ({FocusTravesalUtils.difference(new HashSet<FocusNode>(sortedDescendants),(new HashSet<FocusNode>(scope.traversalDescendants)))})"
                );
            D.assert(
                FocusTravesalUtils.difference(new HashSet <FocusNode>(scope.traversalDescendants), new HashSet <FocusNode>(sortedDescendants)).isEmpty(),
                () => $"sorted descendants are missing some nodes: ({FocusTravesalUtils.difference(new HashSet<FocusNode>(scope.traversalDescendants),new HashSet<FocusNode>(sortedDescendants))})"
                );
            return(sortedDescendants);
        }
예제 #24
0
 public virtual void changedScope(FocusNode node = null, FocusScopeNode oldScope = null)
 {
 }
예제 #25
0
 public virtual void invalidateScopeData(FocusScopeNode node)
 {
 }
예제 #26
0
 public FocusScope(FocusScopeNode node, Widget child, Key key = null, bool autofocus = false) : base(key)
 {
     this.node      = node;
     this.child     = child;
     this.autofocus = autofocus;
 }
예제 #27
0
 public _FocusScopeMarker(FocusScopeNode node, Widget child, Key key = null) : base(key, child)
 {
     D.assert(node != null);
     this.node = node;
 }