/// <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; }
/// <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); } } }
/// <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); }); }
/// <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); } }
/// <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); }
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); } } }
/// <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); } } }
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); } }
/// <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)); } }
/// <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);