private IEnumerable <BoxTree.Node> EnumerateColumn(BoxTree.Node branchRoot) { for (var i = 0; i < branchRoot.State.NumberOfSiblings; i++) { yield return(branchRoot.Children[i]); } }
/// <summary> /// Ctr. /// </summary> public Step([NotNull] BoxTree.Node node, double x, double top, double bottom) { Node = node; X = x; Top = top; Bottom = bottom; }
/// <summary> /// Push a new box onto the layout stack, thus getting deeper into layout hierarchy. /// Automatically allocates a Bondary object from pool. /// </summary> public LayoutLevel PushLayoutLevel([NotNull] BoxTree.Node node) { if (m_pooledBoundaries.Count == 0) { m_pooledBoundaries.Push(new Boundary()); } var boundary = m_pooledBoundaries.Pop(); switch (CurrentOperation) { case Operation.VerticalLayout: boundary.Prepare(node); break; case Operation.HorizontalLayout: boundary.PrepareForHorizontalLayout(node); break; default: throw new InvalidOperationException("This operation can only be invoked when performing vertical or horizontal layouts"); } var result = new LayoutLevel(node, boundary); m_layoutStack.Push(result); BoundaryChanged?.Invoke(this, new BoundaryChangedEventArgs(boundary, result, this)); return(result); }
/// <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); }
private IEnumerable <BoxTree.Node> EnumerateSiblings(BoxTree.Node node, int from, int to) { for (var i = from; i < to; i++) { yield return(node.Children[i]); } }
/// <summary> /// Re-initializes left and right edges based on actual coordinates of boxes. /// </summary> public void ReloadFromBranch(BoxTree.Node branchRoot) { var leftmost = double.MaxValue; var rightmost = double.MinValue; for (var i = 0; i < Left.Count; i++) { var left = Left[i]; var newLeft = left.Node.State.Left; Left[i] = left.ChangeX(newLeft); leftmost = Math.Min(leftmost, newLeft); } for (var i = 0; i < Right.Count; i++) { var right = Right[i]; var newRight = right.Node.State.Right; Right[i] = right.ChangeX(newRight); rightmost = Math.Max(rightmost, newRight); } leftmost = Math.Min(branchRoot.State.Left, leftmost); rightmost = Math.Max(branchRoot.State.Right, rightmost); BoundingRect = new Rect(new Point(leftmost, BoundingRect.Top), new Size(rightmost - leftmost, BoundingRect.Size.Height)); }
/// <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> /// Resets the edges, use when re-using this object from pool. /// </summary> public void Prepare([NotNull] BoxTree.Node node) { Left.Clear(); Right.Clear(); // adjust the top edge to fit the logical grid BoundingRect = new Rect(node.State.TopLeft, node.State.Size); }
/// <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> /// 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); }); }
private IEnumerable <BoxTree.Node> EnumerateColumn(BoxTree.Node branchRoot, int col) { for (var row = 0; row < branchRoot.State.NumberOfSiblingRows; row++) { var ix = row * branchRoot.State.NumberOfSiblingColumns + col; if (ix >= branchRoot.State.NumberOfSiblings) { yield break; } yield return(branchRoot.Children[ix]); } }
/// <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); }
/// <summary> /// Resets the edges, use when re-using this object from pool. /// </summary> public void PrepareForHorizontalLayout([NotNull] BoxTree.Node node) { Prepare(node); if (node.Element.DisableCollisionDetection) { return; } var rect = node.State; Left.Add(new Step(node, rect.Left, rect.Top, rect.Bottom)); Right.Add(new Step(node, rect.Right, rect.Top, rect.Bottom)); }
/// <summary> /// Merges a box into this one, potentially pushing its edges out. /// </summary> public void MergeFrom([NotNull] BoxTree.Node node) { if (node.Element.DisableCollisionDetection) { return; } if (node.State.Size.Height.IsZero()) { return; } m_spacerMerger.PrepareForHorizontalLayout(node); MergeFrom(m_spacerMerger); }
/// <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); } } }
/// <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> /// 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); } } }
/// <summary> /// Merges a provided spacer box into the current branch boundary. /// </summary> public void MergeSpacer([NotNull] BoxTree.Node spacer) { if (CurrentOperation != Operation.HorizontalLayout) { throw new InvalidOperationException("Spacers can only be merged during horizontal layout"); } if (m_layoutStack.Count == 0) { throw new InvalidOperationException("Cannot merge spacers at top nesting level"); } var level = m_layoutStack.Peek(); level.Boundary.MergeFrom(spacer); BoundaryChanged?.Invoke(this, new BoundaryChangedEventArgs(level.Boundary, level, this)); }
/// <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); } }
public SingleFishboneLayoutAdapter([NotNull] BoxTree.Node realRoot) { Iterator = new GroupIterator(realRoot.State.NumberOfSiblings, realRoot.State.NumberOfSiblingColumns); RealRoot = realRoot; SpecialRoot = new TreeNodeView(Box.Special(Box.None, realRoot.Element.Id, true)) { Level = RealRoot.Level, ParentNode = RealRoot }; SpecialRoot.State.EffectiveLayoutStrategy = this; var parentStrategy = (MultiLineFishboneLayoutStrategy)realRoot.State.RequireLayoutStrategy(); SiblingSpacing = parentStrategy.SiblingSpacing; ParentConnectorShield = parentStrategy.ParentConnectorShield; ParentChildSpacing = parentStrategy.ParentChildSpacing; ParentAlignment = parentStrategy.ParentAlignment; ChildConnectorHookLength = parentStrategy.ChildConnectorHookLength; }
/// <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(); } }
public override void RouteConnectors(LayoutState state, BoxTree.Node node) { throw new NotSupportedException(); }
/// <summary> /// Returns a new <see cref="Step"/> whose <see cref="Node"/> property was set to <paramref name="newNode"/> and <see cref="X"/> to <paramref name="newX"/>. /// </summary> public Step ChangeOwner([NotNull] BoxTree.Node newNode, double newX) { return(new Step(newNode, newX, Top, Bottom)); }
/// <summary> /// Allocates and routes connectors. /// </summary> public override void RouteConnectors([NotNull] LayoutState state, [NotNull] BoxTree.Node node) { // this strategy does not use connectors }
private bool NeedCarrierProtector(BoxTree.Node node) => node.ParentNode.ChildCount == 0;
private int MaxOnLeft(BoxTree.Node node) => node.State.NumberOfSiblings / 2 + node.State.NumberOfSiblings % 2;
/// <summary> /// Allocates and routes connectors. /// </summary> public override void RouteConnectors([NotNull] LayoutState state, [NotNull] BoxTree.Node node) { if (node.State.NumberOfSiblings <= MaxSiblingsPerRow) { // fall back to linear layout, only have one row of boxes base.RouteConnectors(state, node); return; } // one parent connector (also serves as mid-sibling carrier) and horizontal carriers var count = 1 + node.State.NumberOfSiblingRows; foreach (var child in node.Children) { // normal boxes get one upward hook if (!child.Element.IsSpecial) { count++; } } var segments = new Edge[count]; var rootRect = node.State; var center = rootRect.CenterH; var verticalCarrierHeight = node.Children[node.State.NumberOfSiblings - 1].State.SiblingsRowV.From - ChildConnectorHookLength - rootRect.Bottom; // central mid-sibling vertical connector, from parent to last row segments[0] = new Edge(new Point(center, rootRect.Bottom), new Point(center, rootRect.Bottom + verticalCarrierHeight)); // short hook for each child var ix = 1; for (var i = 0; i < node.State.NumberOfSiblings; i++) { var child = node.Children[i]; if (!child.Element.IsSpecial) { var childRect = child.State; var childCenter = childRect.CenterH; segments[ix++] = new Edge( new Point(childCenter, childRect.Top), new Point(childCenter, childRect.Top - ChildConnectorHookLength)); } } // horizontal carriers go from leftmost child hook to righmost child hook // for the last row which is just half or less full, it will only go to the central vertical carrier var lastChildHookIndex = count - node.State.NumberOfSiblingRows - 1; for (var firstInRowIndex = 1; firstInRowIndex < count - node.State.NumberOfSiblingRows; firstInRowIndex += MaxSiblingsPerRow) { var firstInRow = segments[firstInRowIndex]; var lastInRow = segments[Math.Min(firstInRowIndex + MaxSiblingsPerRow - 1, lastChildHookIndex)]; if (lastInRow.From.X < segments[0].From.X) { segments[ix++] = new Edge( new Point(firstInRow.To.X, firstInRow.To.Y), new Point(segments[0].To.X, firstInRow.To.Y)); } else { segments[ix++] = new Edge( new Point(firstInRow.To.X, firstInRow.To.Y), new Point(lastInRow.To.X, firstInRow.To.Y)); } } node.State.Connector = new Connector(segments); }
/// <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);
/// <summary> /// Allocates and routes connectors. /// </summary> public abstract void RouteConnectors([NotNull] LayoutState state, [NotNull] BoxTree.Node node);