/// <summary>
        /// Allocates and routes connectors.
        /// </summary>
        public override void RouteConnectors([NotNull] LayoutState state, [NotNull] BoxTree.Node node)
        {
            if (node.ChildCount == 0)
            {
                return;
            }

            // one parent connector (also serves as mid-sibling carrier) and horizontal carriers
            var count = 1 + node.State.NumberOfSiblings;

            var segments = new Edge[count];

            var rootRect = node.State;
            var center   = rootRect.CenterH;

            var verticalCarrierHeight = node.Children[node.State.NumberOfSiblings - 1].State.CenterV - node.State.Bottom;

            // big vertical connector, from parent to last row
            segments[0] = new Edge(new Point(center, rootRect.Bottom), new Point(center, rootRect.Bottom + verticalCarrierHeight));

            for (var ix = 0; ix < node.State.NumberOfSiblings; ix++)
            {
                var rect        = node.Children[ix].State;
                var destination = ParentAlignment == BranchParentAlignment.Left ? rect.Left : rect.Right;
                segments[1 + ix] = new Edge(
                    new Point(center, rect.CenterV),
                    new Point(destination, rect.CenterV));
            }

            node.State.Connector = new Connector(segments);
        }
        /// <summary>
        /// Moves a given branch horizontally, except its root box.
        /// Also updates branch exterior rects.
        /// Also updates branch boundary for the current <paramref name="layoutLevel"/>.
        /// </summary>
        public static void MoveChildrenOnly([NotNull] LayoutState state, LayoutState.LayoutLevel layoutLevel, double offset)
        {
            var children = layoutLevel.BranchRoot.Children;

            if (children == null || children.Count == 0)
            {
                throw new InvalidOperationException("Should never be invoked when children not set");
            }

            Func <BoxTree.Node, bool> action = node =>
            {
                if (!node.State.IsHidden)
                {
                    node.State.TopLeft        = node.State.TopLeft.MoveH(offset);
                    node.State.BranchExterior = node.State.BranchExterior.MoveH(offset);
                }
                return(true);
            };

            foreach (var child in children)
            {
                child.IterateChildFirst(action);
            }

            layoutLevel.Boundary.ReloadFromBranch(layoutLevel.BranchRoot);
            layoutLevel.BranchRoot.State.BranchExterior = layoutLevel.Boundary.BoundingRect;
        }
Exemple #3
0
        /// <summary>
        /// Applies layout changes to a given box and its children.
        /// </summary>
        public override void ApplyVerticalLayout([NotNull] LayoutState state, [NotNull] LayoutState.LayoutLevel level)
        {
            var node = level.BranchRoot;

            if (node.State.NumberOfSiblings <= MaxGroups * 2)
            {
                base.ApplyVerticalLayout(state, level);
                return;
            }

            if (node.Level == 0)
            {
                node.State.SiblingsRowV = new Dimensions(node.State.Top, node.State.Bottom);
            }

            if (node.AssistantsRoot != null)
            {
                // assistants root has to be initialized with main node's exterior
                node.AssistantsRoot.State.CopyExteriorFrom(node.State);
                LayoutAlgorithm.VerticalLayout(state, node.AssistantsRoot);
            }

            var adapter = new SingleFishboneLayoutAdapter(node);

            while (adapter.NextGroup())
            {
                LayoutAlgorithm.VerticalLayout(state, adapter.SpecialRoot);
            }
        }
        /// <summary>
        /// A chance for layout strategy to append special auto-generated boxes into the visual tree.
        /// </summary>
        public override void PreProcessThisNode([NotNull] LayoutState state, [NotNull] BoxTree.Node node)
        {
            if (MaxSiblingsPerRow <= 0 || MaxSiblingsPerRow % 2 != 0)
            {
                throw new InvalidOperationException(nameof(MaxSiblingsPerRow) + " must be a positive even value");
            }

            if (node.ChildCount <= MaxSiblingsPerRow)
            {
                // fall back to linear layout, only have one row of boxes
                base.PreProcessThisNode(state, node);
                return;
            }

            node.State.NumberOfSiblings = node.ChildCount;

            // only add spacers for non-collapsed boxes
            if (node.State.NumberOfSiblings > 0)
            {
                var lastRowBoxCount = node.ChildCount % MaxSiblingsPerRow;

                // add one (for vertical spacer) into the count of layout columns
                node.State.NumberOfSiblingColumns = 1 + MaxSiblingsPerRow;

                node.State.NumberOfSiblingRows = node.ChildCount / MaxSiblingsPerRow;
                if (lastRowBoxCount != 0)
                {
                    node.State.NumberOfSiblingRows++;
                }

                // include vertical spacers into the count of layout siblings
                node.State.NumberOfSiblings = node.ChildCount + node.State.NumberOfSiblingRows;
                if (lastRowBoxCount > 0 && lastRowBoxCount <= MaxSiblingsPerRow / 2)
                {
                    // don't need the last spacer, last row is half-full or even less
                    node.State.NumberOfSiblings--;
                }

                // sibling middle-spacers have to be inserted between siblings
                var ix = MaxSiblingsPerRow / 2;
                while (ix < node.State.NumberOfSiblings)
                {
                    var siblingSpacer = Box.Special(Box.None, node.Element.Id, false);
                    node.InsertRegularChild(ix, siblingSpacer);
                    ix += node.State.NumberOfSiblingColumns;
                }

                // add parent vertical spacer to the end
                var verticalSpacer = Box.Special(Box.None, node.Element.Id, false);
                node.AddRegularChild(verticalSpacer);

                // add horizontal spacers to the end
                for (var i = 0; i < node.State.NumberOfSiblingRows; i++)
                {
                    var horizontalSpacer = Box.Special(Box.None, node.Element.Id, false);
                    node.AddRegularChild(horizontalSpacer);
                }
            }
        }
        /// <summary>
        /// Applies layout changes to a given box and its children.
        /// </summary>
        public override void ApplyHorizontalLayout([NotNull] LayoutState state, [NotNull] LayoutState.LayoutLevel level)
        {
            var node = level.BranchRoot;

            var nodeState = node.State;

            if (node.AssistantsRoot != null)
            {
                LayoutAlgorithm.HorizontalLayout(state, node.AssistantsRoot);
            }

            // first, perform horizontal layout for every node in this column
            for (var row = 0; row < nodeState.NumberOfSiblings; row++)
            {
                var child = node.Children[row];

                // re-enter layout algorithm for child branch
                // siblings are guaranteed not to offend each other
                LayoutAlgorithm.HorizontalLayout(state, child);
            }

            // now align the column
            var edges = LayoutAlgorithm.AlignHorizontalCenters(state, level, EnumerateColumn(node));

            if (node.Level > 0 && node.ChildCount > 0)
            {
                var    rect = node.State;
                double diff;
                if (ParentAlignment == BranchParentAlignment.Left)
                {
                    var desiredLeft = rect.CenterH + ParentConnectorShield / 2;
                    diff = desiredLeft - edges.From;
                }
                else if (ParentAlignment == BranchParentAlignment.Right)
                {
                    var desiredRight = rect.CenterH - ParentConnectorShield / 2;
                    diff = desiredRight - edges.To;
                }
                else
                {
                    throw new InvalidOperationException("Invalid ParentAlignment setting");
                }

                // vertical connector from parent
                LayoutAlgorithm.MoveChildrenOnly(state, level, diff);

                // spacer for the vertical carrier
                var verticalSpacer = node.Level > 0 ? node.Children[node.ChildCount - 1] : null;
                if (verticalSpacer != null)
                {
                    var spacerTop    = node.State.Bottom;
                    var spacerBottom = node.Children[node.ChildCount - 2].State.Bottom;
                    verticalSpacer.State.AdjustSpacer(
                        rect.CenterH - ParentConnectorShield / 2, spacerTop,
                        ParentConnectorShield, spacerBottom - spacerTop);
                    state.MergeSpacer(verticalSpacer);
                }
            }
        }
Exemple #6
0
        /// <summary>
        /// Allocates and routes connectors.
        /// </summary>
        public override void RouteConnectors([NotNull] LayoutState state, [NotNull] BoxTree.Node node)
        {
            var normalChildCount = node.State.NumberOfSiblings;

            var count = normalChildCount == 0
                ? 0                         // no visible children = no edges
                : normalChildCount == 1
                    ? 1                     // one child = one direct edge between parent and child
                    : 1                     // one downward edge for parent
                        + 1                 // one for horizontal carrier
                        + normalChildCount; // one upward edge for each child

            if (count == 0)
            {
                node.State.Connector = null;
                return;
            }

            var segments = new Edge[count];

            var rootRect = node.State;
            var center   = rootRect.CenterH;

            if (node.Children == null)
            {
                throw new Exception("State is present, but children not set");
            }

            if (count == 1)
            {
                segments[0] = new Edge(new Point(center, rootRect.Bottom),
                                       new Point(center, node.Children[0].State.Top));
            }
            else
            {
                var space = node.Children[0].State.SiblingsRowV.From - rootRect.Bottom;

                segments[0] = new Edge(new Point(center, rootRect.Bottom),
                                       new Point(center, rootRect.Bottom + space - ChildConnectorHookLength));

                for (var i = 0; i < normalChildCount; i++)
                {
                    var childRect   = node.Children[i].State;
                    var childCenter = childRect.CenterH;
                    segments[1 + i] = new Edge(new Point(childCenter, childRect.Top),
                                               new Point(childCenter, childRect.Top - ChildConnectorHookLength));
                }

                segments[count - 1] = new Edge(
                    new Point(segments[1].To.X, segments[1].To.Y),
                    new Point(segments[count - 2].To.X, segments[1].To.Y));
            }

            node.State.Connector = new Connector(segments);
        }
        /// <summary>
        /// Applies layout changes to a given box and its children.
        /// </summary>
        public override void ApplyVerticalLayout([NotNull] LayoutState state, [NotNull] LayoutState.LayoutLevel level)
        {
            var node = level.BranchRoot;

            if (node.Level == 0)
            {
                throw new InvalidOperationException("Should never be invoked on root node");
            }

            var prevRowBottom = node.State.SiblingsRowV.To;

            var maxOnLeft = MaxOnLeft(node);

            for (var i = 0; i < maxOnLeft; i++)
            {
                var spacing = i == 0 ? ParentChildSpacing : SiblingSpacing;

                var child = node.Children[i];
                var frame = child.State;
                frame.MoveTo(frame.Left, prevRowBottom + spacing);

                var rowExterior = new Dimensions(frame.Top, frame.Bottom);

                var i2 = i + maxOnLeft;
                if (i2 < node.State.NumberOfSiblings)
                {
                    var child2 = node.Children[i2];
                    var frame2 = child2.State;
                    frame2.MoveTo(frame2.Left, prevRowBottom + spacing);

                    if (frame2.Bottom > frame.Bottom)
                    {
                        frame.MoveTo(frame.Left, frame2.CenterV - frame.Size.Height / 2);
                    }
                    else if (frame2.Bottom < frame.Bottom)
                    {
                        frame2.MoveTo(frame2.Left, frame.CenterV - frame2.Size.Height / 2);
                    }

                    frame2.BranchExterior = new Rect(frame2.TopLeft, frame2.Size);
                    rowExterior          += new Dimensions(frame2.Top, frame2.Bottom);

                    frame2.SiblingsRowV = rowExterior;
                    LayoutAlgorithm.VerticalLayout(state, child2);
                    prevRowBottom = frame2.BranchExterior.Bottom;
                }

                frame.BranchExterior = new Rect(frame.TopLeft, frame.Size);
                frame.SiblingsRowV   = rowExterior;
                LayoutAlgorithm.VerticalLayout(state, child);
                prevRowBottom = Math.Max(prevRowBottom, frame.BranchExterior.Bottom);
            }
        }
 /// <summary>
 /// Moves a given branch horizontally, except its root box.
 /// Also updates branch exterior rects.
 /// Unlike <see cref="MoveChildrenOnly"/> and <see cref="MoveBranch"/>, does NOT update the boundary.
 /// </summary>
 /// <remarks>DOES NOT update branch boundary! Must call <see cref="Boundary.ReloadFromBranch"/> after batch of updates is complete</remarks>
 private static void MoveOneChild([NotNull] LayoutState state, [NotNull] BoxTree.Node root, double offset)
 {
     root.IterateChildFirst(node =>
     {
         if (!node.State.IsHidden)
         {
             node.State.TopLeft        = node.State.TopLeft.MoveH(offset);
             node.State.BranchExterior = node.State.BranchExterior.MoveH(offset);
         }
         return(true);
     });
 }
Exemple #9
0
        /// <summary>
        /// Applies layout changes to a given box and its children.
        /// </summary>
        public override void ApplyHorizontalLayout([NotNull] LayoutState state, [NotNull] LayoutState.LayoutLevel level)
        {
            var node = level.BranchRoot;

            if (node.AssistantsRoot != null)
            {
                LayoutAlgorithm.HorizontalLayout(state, node.AssistantsRoot);
            }

            for (var i = 0; i < node.State.NumberOfSiblings; i++)
            {
                var child = node.Children[i];
                // re-enter layout algorithm for child branch
                LayoutAlgorithm.HorizontalLayout(state, child);
            }

            if (node.Level > 0 && node.ChildCount > 0)
            {
                var rect          = node.State;
                var leftmost      = node.Children[0].State.CenterH;
                var rightmost     = node.Children[node.State.NumberOfSiblings - 1].State.CenterH;
                var desiredCenter =
                    node.State.NumberOfSiblings == 1 || ParentAlignment == BranchParentAlignment.Center
                    ? leftmost + (rightmost - leftmost) / 2
                    : ParentAlignment == BranchParentAlignment.Left
                    ? leftmost + ChildConnectorHookLength
                    : rightmost - ChildConnectorHookLength;
                var center = rect.CenterH;
                var diff   = center - desiredCenter;
                LayoutAlgorithm.MoveChildrenOnly(state, level, diff);

                // vertical connector from parent
                var verticalSpacer = node.Children[node.State.NumberOfSiblings];
                verticalSpacer.State.AdjustSpacer(
                    center - ParentConnectorShield / 2, rect.Bottom,
                    ParentConnectorShield, node.Children[0].State.SiblingsRowV.From - rect.Bottom);

                state.MergeSpacer(verticalSpacer);

                // horizontal protector
                var firstInRow = node.Children[0].State;

                var horizontalSpacer = node.Children[node.State.NumberOfSiblings + 1];
                horizontalSpacer.State.AdjustSpacer(
                    firstInRow.Left,
                    firstInRow.SiblingsRowV.From - ParentChildSpacing,
                    node.Children[node.State.NumberOfSiblings - 1].State.Right - firstInRow.Left,
                    ParentChildSpacing);
                state.MergeSpacer(horizontalSpacer);
            }
        }
Exemple #10
0
        /// <summary>
        /// Applies layout changes to a given box and its children.
        /// </summary>
        public override void ApplyVerticalLayout([NotNull] LayoutState state, [NotNull] LayoutState.LayoutLevel level)
        {
            var node = level.BranchRoot;

            if (node.Level == 0)
            {
                node.State.SiblingsRowV = new Dimensions(
                    node.State.Top,
                    node.State.Bottom);
            }

            if (node.AssistantsRoot != null)
            {
                // assistants root has to be initialized with main node's exterior
                node.AssistantsRoot.State.CopyExteriorFrom(node.State);
                LayoutAlgorithm.VerticalLayout(state, node.AssistantsRoot);
            }

            if (node.State.NumberOfSiblings == 0)
            {
                return;
            }

            var siblingsRowExterior = Dimensions.MinMax();

            var top = node.AssistantsRoot == null
                ? node.State.SiblingsRowV.To + ParentChildSpacing
                : node.State.BranchExterior.Bottom + ParentChildSpacing;

            for (var i = 0; i < node.State.NumberOfSiblings; i++)
            {
                var child = node.Children[i];
                var rect  = child.State;

                child.State.MoveTo(0, top);
                child.State.BranchExterior = new Rect(child.State.TopLeft, child.State.Size);

                siblingsRowExterior += new Dimensions(top, top + rect.Size.Height);
            }

            siblingsRowExterior = new Dimensions(siblingsRowExterior.From, siblingsRowExterior.To);

            for (var i = 0; i < node.State.NumberOfSiblings; i++)
            {
                var child = node.Children[i];
                child.State.SiblingsRowV = siblingsRowExterior;

                // re-enter layout algorithm for child branch
                LayoutAlgorithm.VerticalLayout(state, child);
            }
        }
        /// <summary>
        /// Allocates and routes connectors.
        /// </summary>
        public override void RouteConnectors([NotNull] LayoutState state, [NotNull] BoxTree.Node node)
        {
            var count = node.State.NumberOfSiblings;

            if (count == 0)
            {
                return;
            }

            if (NeedCarrierProtector(node))
            {
                count++;
            }

            var segments = new Edge[count];

            var ix = 0;

            // one hook for each child
            var maxOnLeft = MaxOnLeft(node);
            var carrier   = node.Children[node.State.NumberOfSiblings].State;
            var from      = carrier.CenterH;

            var isLeft          = true;
            var countOnThisSide = 0;
            var bottomMost      = double.MinValue;

            for (var i = 0; i < node.State.NumberOfSiblings; i++)
            {
                var to = isLeft ? node.Children[i].State.Right : node.Children[i].State.Left;
                var y  = node.Children[i].State.CenterV;
                bottomMost     = Math.Max(bottomMost, y);
                segments[ix++] = new Edge(new Point(from, y), new Point(to, y));

                if (++countOnThisSide == maxOnLeft)
                {
                    countOnThisSide = 0;
                    isLeft          = !isLeft;
                }
            }

            if (NeedCarrierProtector(node))
            {
                // one for each vertical carrier
                segments[node.State.NumberOfSiblings] = new Edge(
                    new Point(carrier.CenterH, carrier.Top),
                    new Point(carrier.CenterH, bottomMost));
            }

            node.State.Connector = new Connector(segments);
        }
Exemple #12
0
            public override void ApplyHorizontalLayout(LayoutState state, [NotNull] LayoutState.LayoutLevel level)
            {
                if (level.BranchRoot != SpecialRoot)
                {
                    throw new InvalidOperationException("Wrong root node received");
                }

                var left            = true;
                var countOnThisSide = 0;

                for (var i = 0; i < Iterator.Count; i++)
                {
                    var child = SpecialRoot.Children[i];
                    LayoutAlgorithm.HorizontalLayout(state, child);

                    // we go top-bottom to layout left side of the group,
                    // then add a carrier protector
                    // then top-bottom to fill right side of the group
                    if (++countOnThisSide == Iterator.MaxOnLeft)
                    {
                        if (left)
                        {
                            // horizontally align children in left pillar
                            LayoutAlgorithm.AlignHorizontalCenters(state, level, EnumerateSiblings(0, Iterator.MaxOnLeft));

                            left            = false;
                            countOnThisSide = 0;

                            var rightmost = double.MinValue;
                            for (var k = 0; k < i; k++)
                            {
                                rightmost = Math.Max(rightmost, SpecialRoot.Children[k].State.BranchExterior.Right);
                            }

                            rightmost = Math.Max(rightmost, child.State.Right);

                            // integrate protector for group's vertical carrier
                            var spacer = SpecialRoot.Children[SpecialRoot.State.NumberOfSiblings];

                            spacer.State.AdjustSpacer(
                                rightmost, SpecialRoot.Children[0].State.SiblingsRowV.From,
                                SiblingSpacing, child.State.SiblingsRowV.To - SpecialRoot.Children[0].State.SiblingsRowV.From);
                            level.Boundary.MergeFrom(spacer);
                        }
                    }
                }
                // horizontally align children in right pillar
                LayoutAlgorithm.AlignHorizontalCenters(state, level, EnumerateSiblings(Iterator.MaxOnLeft, Iterator.Count));
            }
        /// <summary>
        /// A chance for layout strategy to append special auto-generated boxes into the visual tree.
        /// This strategy does not use connectors and spacers.
        /// </summary>
        public override void PreProcessThisNode([NotNull] LayoutState state, [NotNull] BoxTree.Node node)
        {
            node.State.NumberOfSiblings = node.Element.IsCollapsed ? 0 : node.ChildCount;

            if (node.State.NumberOfSiblings > 0)
            {
                // this strategy requires certain adjustments to be made to the box sizes
                // they will only affect corresponding Nodes, not the size on the box itself
                if (Orientation != StackOrientation.SingleRowHorizontal &&
                    Orientation != StackOrientation.SingleColumnVertical)
                {
                    throw new InvalidOperationException("Unsupported value for orientation: " + Orientation);
                }
            }
        }
Exemple #14
0
        /// <summary>
        /// A chance for layout strategy to append special auto-generated boxes into the visual tree.
        /// </summary>
        public override void PreProcessThisNode([NotNull] LayoutState state, [NotNull] BoxTree.Node node)
        {
            if (node.ChildCount > 0)
            {
                node.State.NumberOfSiblings = node.Element.IsCollapsed ? 0 : node.ChildCount;

                // only add spacers for non-collapsed boxes
                if (!node.Element.IsCollapsed)
                {
                    var verticalSpacer = Box.Special(Box.None, node.Element.Id, false);
                    node.AddRegularChild(verticalSpacer);

                    var horizontalSpacer = Box.Special(Box.None, node.Element.Id, false);
                    node.AddRegularChild(horizontalSpacer);
                }
            }
        }
Exemple #15
0
            public override void ApplyVerticalLayout(LayoutState state, [NotNull] LayoutState.LayoutLevel level)
            {
                var prevRowBottom =
                    RealRoot.AssistantsRoot?.State.BranchExterior.Bottom
                    ?? SpecialRoot.State.SiblingsRowV.To;

                for (var i = 0; i < Iterator.MaxOnLeft; i++)
                {
                    var spacing = i == 0 ? ParentChildSpacing : SiblingSpacing;

                    var child = SpecialRoot.Children[i];
                    var frame = child.State;
                    frame.MoveTo(frame.Left, prevRowBottom + spacing);

                    var rowExterior = new Dimensions(frame.Top, frame.Bottom);

                    var i2 = i + Iterator.MaxOnLeft;
                    if (i2 < Iterator.Count)
                    {
                        var child2 = SpecialRoot.Children[i2];
                        var frame2 = child2.State;
                        frame2.MoveTo(frame2.Left, prevRowBottom + spacing);

                        if (frame2.Bottom > frame.Bottom)
                        {
                            frame.MoveTo(frame.Left, frame2.CenterV - frame.Size.Height / 2);
                        }
                        else if (frame2.Bottom < frame.Bottom)
                        {
                            frame2.MoveTo(frame2.Left, frame.CenterV - frame2.Size.Height / 2);
                        }

                        frame2.BranchExterior = new Rect(frame2.TopLeft, frame2.Size);
                        rowExterior          += new Dimensions(frame2.Top, frame2.Bottom);

                        frame2.SiblingsRowV = rowExterior;
                        LayoutAlgorithm.VerticalLayout(state, child2);
                        prevRowBottom = frame2.BranchExterior.Bottom;
                    }

                    frame.BranchExterior = new Rect(frame.TopLeft, frame.Size);
                    frame.SiblingsRowV   = rowExterior;
                    LayoutAlgorithm.VerticalLayout(state, child);
                    prevRowBottom = Math.Max(prevRowBottom, frame.BranchExterior.Bottom);
                }
            }
Exemple #16
0
        /// <summary>
        /// A chance for layout strategy to append special auto-generated boxes into the visual tree.
        /// </summary>
        public override void PreProcessThisNode([NotNull] LayoutState state, [NotNull] BoxTree.Node node)
        {
            if (MaxGroups <= 0)
            {
                throw new InvalidOperationException(nameof(MaxGroups) + " must be a positive value");
            }

            if (node.ChildCount <= MaxGroups * 2)
            {
                base.PreProcessThisNode(state, node);
                return;
            }

            node.State.NumberOfSiblings = node.ChildCount;

            // only add spacers for non-collapsed boxes
            if (node.State.NumberOfSiblings > 0)
            {
                // using column == group here,
                // and each group consists of two vertical stretches of boxes with a vertical carrier in between
                node.State.NumberOfSiblingColumns = MaxGroups;
                node.State.NumberOfSiblingRows    = node.State.NumberOfSiblings / (MaxGroups * 2);
                if (node.State.NumberOfSiblings % (MaxGroups * 2) != 0)
                {
                    node.State.NumberOfSiblingRows++;
                }

                // a connector from parent to horizontal carrier
                var parentSpacer = Box.Special(Box.None, node.Element.Id, false);
                node.AddRegularChild(parentSpacer);

                // spacers for vertical carriers
                for (var i = 0; i < node.State.NumberOfSiblingColumns; i++)
                {
                    var verticalSpacer = Box.Special(Box.None, node.Element.Id, false);
                    node.AddRegularChild(verticalSpacer);
                }

                // if needed, horizontal carrier
                if (node.State.NumberOfSiblingColumns > 1)
                {
                    var horizontalSpacer = Box.Special(Box.None, node.Element.Id, false);
                    node.AddRegularChild(horizontalSpacer);
                }
            }
        }
        /// <summary>
        /// Applies layout changes to a given box and its children.
        /// </summary>
        public override void ApplyVerticalLayout([NotNull] LayoutState state, [NotNull] LayoutState.LayoutLevel level)
        {
            var node = level.BranchRoot;

            if (node.Level == 0)
            {
                node.State.SiblingsRowV = new Dimensions(node.State.Top, node.State.Bottom);
            }

            if (node.AssistantsRoot != null)
            {
                // assistants root has to be initialized with main node's exterior
                node.AssistantsRoot.State.CopyExteriorFrom(node.State);
                LayoutAlgorithm.VerticalLayout(state, node.AssistantsRoot);
            }

            var prevRowExterior = new Dimensions(
                node.State.SiblingsRowV.From,
                node.AssistantsRoot == null
                ? node.State.SiblingsRowV.To
                : node.State.BranchExterior.Bottom);

            for (var row = 0; row < node.State.NumberOfSiblings; row++)
            {
                // first, compute
                var child = node.Children[row];
                var rect  = child.State;

                var top = prevRowExterior.To + (row == 0 ? ParentChildSpacing : SiblingSpacing);
                child.State.MoveTo(rect.Left, top);
                child.State.BranchExterior = new Rect(child.State.TopLeft, child.State.Size);

                var rowExterior = new Dimensions(top, top + rect.Size.Height);

                child = node.Children[row];
                child.State.SiblingsRowV = rowExterior;

                // re-enter layout algorithm for child branch
                LayoutAlgorithm.VerticalLayout(state, child);

                var childBranchBottom = child.State.BranchExterior.Bottom;

                prevRowExterior = new Dimensions(rowExterior.From, Math.Max(childBranchBottom, rowExterior.To));
            }
        }
Exemple #18
0
        /// <summary>
        /// Constructs a new tree.
        /// </summary>
        public static BoxTree Build([NotNull] LayoutState state)
        {
            var result = new BoxTree();

            // build dictionary of nodes
            foreach (var box in state.Diagram.Boxes.BoxesById.Values)
            {
                var node = new Node(box);
                result.Nodes.Add(box.Id, node);
            }

            // build the tree
            foreach (var node in result.Nodes.Values)
            {
                var parentKey = node.Element.ParentId;

                Node parentNode;
                if (result.Nodes.TryGetValue(parentKey, out parentNode))
                {
                    if (node.Element.IsAssistant && parentNode.Element.ParentId != Box.None)
                    {
                        parentNode.AddAssistantChild(node);
                    }
                    else
                    {
                        parentNode.AddRegularChild(node);
                    }
                }
                else
                {
                    if (result.Root != null)
                    {
                        throw new InvalidOperationException("More then one root found: " + node.Element.Id);
                    }
                    // In case of data errors, parent key may be not null, but parent node is not there.
                    // Just add the node to roots.
                    result.Root = node;
                }
            }

            return(result);
        }
        /// <summary>
        /// A chance for layout strategy to append special auto-generated boxes into the visual tree.
        /// </summary>
        public override void PreProcessThisNode([NotNull] LayoutState state, [NotNull] BoxTree.Node node)
        {
            node.State.NumberOfSiblings = node.ChildCount;

            // only add spacers for non-collapsed boxes
            if (node.State.NumberOfSiblings > 0)
            {
                // using column == group here,
                // and each group consists of two vertical stretches of boxes with a vertical carrier in between
                node.State.NumberOfSiblingColumns = 1;
                node.State.NumberOfSiblingRows    = node.State.NumberOfSiblings / 2;
                if (node.State.NumberOfSiblings % 2 != 0)
                {
                    node.State.NumberOfSiblingRows++;
                }

                // a vertical carrier from parent
                var spacer = Box.Special(Box.None, node.Element.Id, false);
                node.AddRegularChild(spacer);
            }
        }
        /// <summary>
        /// Vertically aligns a subset of child nodes, presumably located one above another.
        /// All children must belong to the current layout level's root.
        /// Returns leftmost and rightmost boundaries of all branches in the <paramref name="subset"/>, after alignment.
        /// </summary>
        public static Dimensions AlignHorizontalCenters(
            [NotNull] LayoutState state,
            [NotNull] LayoutState.LayoutLevel level,
            [NotNull] IEnumerable <BoxTree.Node> subset)
        {
            // compute the rightmost center in the column
            var center = double.MinValue;

            foreach (var child in subset)
            {
                var c = child.State.CenterH;
                if (c > center)
                {
                    center = c;
                }
            }

            // move those boxes in the column that are not aligned with the rightmost center
            var leftmost  = double.MaxValue;
            var rightmost = double.MinValue;

            foreach (var child in subset)
            {
                var frame = child.State;
                var c     = frame.CenterH;
                if (!c.IsEqual(center))
                {
                    var diff = center - c;
                    MoveOneChild(state, child, diff);
                }
                leftmost  = Math.Min(leftmost, child.State.BranchExterior.Left);
                rightmost = Math.Max(rightmost, child.State.BranchExterior.Right);
            }

            // update branch boundary
            level.Boundary.ReloadFromBranch(level.BranchRoot);

            return(new Dimensions(leftmost, rightmost));
        }
        /// <summary>
        /// A chance for layout strategy to append special auto-generated boxes into the visual tree.
        /// </summary>
        public override void PreProcessThisNode([NotNull] LayoutState state, [NotNull] BoxTree.Node node)
        {
            if (ParentAlignment != BranchParentAlignment.Left &&
                ParentAlignment != BranchParentAlignment.Right)
            {
                throw new InvalidOperationException("Unsupported value for " + nameof(ParentAlignment));
            }

            node.State.NumberOfSiblings = node.Element.IsCollapsed ? 0 : node.ChildCount;

            // only add spacers for non-collapsed boxes
            if (node.State.NumberOfSiblings > 0 && node.Level > 0)
            {
                // add one (for vertical spacer) into the count of layout columns
                node.State.NumberOfSiblingColumns = 1;
                node.State.NumberOfSiblingRows    = node.ChildCount;

                // add parent's vertical carrier to the end
                var verticalSpacer = Box.Special(Box.None, node.Element.Id, false);
                node.AddRegularChild(verticalSpacer);
            }
        }
        /// <summary>
        /// Re-entrant layout algorithm.
        /// </summary>
        public static void VerticalLayout([NotNull] LayoutState state, [NotNull] BoxTree.Node branchRoot)
        {
            if (branchRoot.State.IsHidden)
            {
                throw new InvalidOperationException($"Branch root {branchRoot.Element.Id} does not affect layout");
            }

            var level = state.PushLayoutLevel(branchRoot);

            try
            {
                if (branchRoot.Level == 0 ||
                    (branchRoot.State.NumberOfSiblings > 0 || branchRoot.AssistantsRoot != null) &&
                    !branchRoot.Element.IsCollapsed)
                {
                    branchRoot.State.RequireLayoutStrategy().ApplyVerticalLayout(state, level);
                }
            }
            finally
            {
                state.PopLayoutLevel();
            }
        }
        private static void RouteConnectors([NotNull] LayoutState state, [NotNull] BoxTree visualTree)
        {
            visualTree.IterateParentFirst(node =>
            {
                if (node.Element.IsCollapsed || node.State.NumberOfSiblings == 0 && node.AssistantsRoot == null)
                {
                    return(false);
                }

                if (node.Level == 0)
                {
                    return(true);
                }

                if (!node.Element.IsSpecial || node.IsAssistantRoot)
                {
                    node.State.RequireLayoutStrategy().RouteConnectors(state, node);
                    return(true);
                }

                return(false);
            });
        }
        /// <summary>
        /// Applies layout changes to a given box and its children.
        /// </summary>
        public override void ApplyHorizontalLayout([NotNull] LayoutState state, [NotNull] LayoutState.LayoutLevel level)
        {
            var node = level.BranchRoot;

            foreach (var child in node.Children)
            {
                // re-enter layout algorithm for child branch
                LayoutAlgorithm.HorizontalLayout(state, child);
            }

            if (node.ChildCount > 0)
            {
                if (Orientation == StackOrientation.SingleRowHorizontal)
                {
                    // now auto-extend or contract the parent box
                    var width = node.Children[node.State.NumberOfSiblings - 1].State.Right - node.Children[0].State.Left;
                    node.State.Size = new Size(Math.Max(node.State.Size.Width, width), node.State.Size.Height);

                    // now position children under the parent
                    var center        = (node.Children[0].State.Left + node.Children[node.ChildCount - 1].State.Right) / 2;
                    var desiredCenter = node.State.CenterH;
                    var diff          = desiredCenter - center;
                    LayoutAlgorithm.MoveChildrenOnly(state, level, diff);
                }
                else if (Orientation == StackOrientation.SingleColumnVertical)
                {
                    LayoutAlgorithm.AlignHorizontalCenters(state, level, node.Children);

                    // now position children under the parent
                    var center        = node.Children[0].State.CenterH;
                    var desiredCenter = node.State.CenterH;
                    var diff          = desiredCenter - center;
                    LayoutAlgorithm.MoveChildrenOnly(state, level, diff);
                }
            }
        }
        /// <summary>
        /// Applies layout changes to a given box and its children.
        /// </summary>
        public override void ApplyVerticalLayout([NotNull] LayoutState state, [NotNull] LayoutState.LayoutLevel level)
        {
            var node = level.BranchRoot;

            if (node.State.NumberOfSiblings <= MaxSiblingsPerRow)
            {
                // fall back to linear layout, only have one row of boxes
                base.ApplyVerticalLayout(state, level);
                return;
            }

            if (node.Level == 0)
            {
                node.State.SiblingsRowV = new Dimensions(node.State.Top, node.State.Bottom);
            }

            if (node.AssistantsRoot != null)
            {
                // assistants root has to be initialized with main node's exterior
                node.AssistantsRoot.State.CopyExteriorFrom(node.State);
                LayoutAlgorithm.VerticalLayout(state, node.AssistantsRoot);
            }

            var prevRowExterior = new Dimensions(
                node.State.SiblingsRowV.From,
                node.AssistantsRoot == null
                ? node.State.SiblingsRowV.To
                : node.State.BranchExterior.Bottom);

            for (var row = 0; row < node.State.NumberOfSiblingRows; row++)
            {
                var siblingsRowExterior = Dimensions.MinMax();

                var spacing = row == 0 ? ParentChildSpacing : SiblingSpacing;

                // first, compute
                var from = row * node.State.NumberOfSiblingColumns;
                var to   = Math.Min(from + node.State.NumberOfSiblingColumns, node.State.NumberOfSiblings);
                for (var i = from; i < to; i++)
                {
                    var child = node.Children[i];
                    if (child.Element.IsSpecial)
                    {
                        // skip vertical spacers for now
                        continue;
                    }

                    var rect = child.State;

                    var top = prevRowExterior.To + spacing;
                    child.State.MoveTo(rect.Left, top);
                    child.State.BranchExterior = new Rect(child.State.TopLeft, child.State.Size);

                    siblingsRowExterior += new Dimensions(top, top + rect.Size.Height);
                }

                siblingsRowExterior = new Dimensions(siblingsRowExterior.From, siblingsRowExterior.To);

                var siblingsBottom = double.MinValue;
                for (var i = from; i < to; i++)
                {
                    var child = node.Children[i];
                    child.State.SiblingsRowV = siblingsRowExterior;

                    // re-enter layout algorithm for child branch
                    LayoutAlgorithm.VerticalLayout(state, child);

                    siblingsBottom = Math.Max(siblingsBottom, child.State.BranchExterior.Bottom);
                }

                prevRowExterior = new Dimensions(siblingsRowExterior.From, Math.Max(siblingsBottom, siblingsRowExterior.To));

                // now assign size to the vertical spacer, if any
                var spacerIndex = from + node.State.NumberOfSiblingColumns / 2;
                if (spacerIndex < node.State.NumberOfSiblings)
                {
                    // in the last row, spacer should only extend to the siblings row bottom,
                    // because main vertical carrier does not go below last row
                    // and thus cannot conflict with branches of children of the last row
                    var spacerBottom = row == node.State.NumberOfSiblingRows - 1
                        ? node.Children[spacerIndex - 1].State.SiblingsRowV.To
                        : prevRowExterior.To;

                    var spacer = node.Children[spacerIndex].State;
                    spacer.AdjustSpacer(
                        0, prevRowExterior.From,
                        ParentConnectorShield, spacerBottom - prevRowExterior.From);
                }
            }
        }
        /// <summary>
        /// Applies layout changes to a given box and its children.
        /// </summary>
        public override void ApplyHorizontalLayout([NotNull] LayoutState state, [NotNull] LayoutState.LayoutLevel level)
        {
            var node = level.BranchRoot;

            if (node.Level == 0)
            {
                node.State.SiblingsRowV = new Dimensions(node.State.Top, node.State.Bottom);
            }

            var left            = true;
            var countOnThisSide = 0;
            var maxOnLeft       = MaxOnLeft(node);

            for (var i = 0; i < node.State.NumberOfSiblings; i++)
            {
                var child = node.Children[i];
                LayoutAlgorithm.HorizontalLayout(state, child);

                // we go top-bottom to layout left side of the group,
                // then add a carrier protector
                // then top-bottom to fill right side of the group
                if (++countOnThisSide == maxOnLeft)
                {
                    if (left)
                    {
                        // horizontally align children in left pillar
                        LayoutAlgorithm.AlignHorizontalCenters(state, level, EnumerateSiblings(node, 0, maxOnLeft));

                        left            = false;
                        countOnThisSide = 0;

                        var rightmost = double.MinValue;
                        for (var k = 0; k <= i; k++)
                        {
                            rightmost = Math.Max(rightmost, node.Children[k].State.BranchExterior.Right);
                        }

                        // vertical spacer does not have to be extended to the bottom of the lowest branch,
                        // unless the lowest branch on the right side has some children and is expanded
                        if (node.State.NumberOfSiblings % 2 != 0)
                        {
                            rightmost = Math.Max(rightmost, child.State.Right);
                        }
                        else
                        {
                            var opposite = node.Children[node.State.NumberOfSiblings - 1];
                            if (opposite.Element.IsCollapsed || opposite.ChildCount == 0)
                            {
                                rightmost = Math.Max(rightmost, child.State.Right);
                            }
                            else
                            {
                                rightmost = Math.Max(rightmost, child.State.BranchExterior.Right);
                            }
                        }

                        // integrate protector for group's vertical carrier
                        // it must prevent boxes on the right side from overlapping the middle vertical connector,
                        // so protector's height must be set to height of this entire assistant branch
                        var spacer = node.Children[node.State.NumberOfSiblings];
                        spacer.State.AdjustSpacer(
                            rightmost,
                            node.State.Bottom,
                            ParentConnectorShield,
                            node.State.BranchExterior.Bottom - node.State.Bottom
                            );
                        level.Boundary.MergeFrom(spacer);
                    }
                }
            }

            // horizontally align children in right pillar
            LayoutAlgorithm.AlignHorizontalCenters(state, level, EnumerateSiblings(node, maxOnLeft, node.State.NumberOfSiblings));

            // align children under parent
            if (node.Level > 0 && node.State.NumberOfSiblings > 0)
            {
                double diff;
                var    carrier       = node.Children[node.State.NumberOfSiblings].State.CenterH;
                var    desiredCenter = node.State.CenterH;
                diff = desiredCenter - carrier;
                LayoutAlgorithm.MoveChildrenOnly(state, level, diff);
            }
        }
 /// <summary>
 /// Allocates and routes connectors.
 /// </summary>
 public abstract void RouteConnectors([NotNull] LayoutState state, [NotNull] BoxTree.Node node);
 /// <summary>
 /// Applies layout changes to a given box and its children.
 /// </summary>
 public abstract void ApplyHorizontalLayout([NotNull] LayoutState state, [NotNull] LayoutState.LayoutLevel level);
 /// <summary>
 /// Applies layout changes to a given box and its children.
 /// </summary>
 public abstract void ApplyVerticalLayout([NotNull] LayoutState state, [NotNull] LayoutState.LayoutLevel level);
 /// <summary>
 /// A chance for layout strategy to insert special auto-generated boxes into the visual tree.
 /// </summary>
 public abstract void PreProcessThisNode([NotNull] LayoutState state, [NotNull] BoxTree.Node node);