/// <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> /// 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> /// 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 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> /// 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> /// 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.State.NumberOfSiblings <= MaxSiblingsPerRow) { // fall back to linear layout, only have one row of boxes base.ApplyHorizontalLayout(state, level); return; } if (node.AssistantsRoot != null) { LayoutAlgorithm.HorizontalLayout(state, node.AssistantsRoot); } for (var col = 0; col < node.State.NumberOfSiblingColumns; col++) { // first, perform horizontal layout for every node in this column for (var row = 0; row < node.State.NumberOfSiblingRows; row++) { var ix = row * node.State.NumberOfSiblingColumns + col; if (ix >= node.State.NumberOfSiblings) { break; } var child = node.Children[ix]; // re-enter layout algorithm for child branch LayoutAlgorithm.HorizontalLayout(state, child); } LayoutAlgorithm.AlignHorizontalCenters(state, level, EnumerateColumn(node, col)); } // now align children under parent var rect = node.State; var spacer = node.Children[node.State.NumberOfSiblingColumns / 2]; var desiredCenter = spacer.State.CenterH; var diff = rect.CenterH - desiredCenter; LayoutAlgorithm.MoveChildrenOnly(state, level, diff); // vertical connector from parent var verticalSpacer = node.Children[node.State.NumberOfSiblings]; verticalSpacer.State.AdjustSpacer( rect.CenterH - ParentConnectorShield / 2, rect.Bottom, ParentConnectorShield, node.Children[0].State.SiblingsRowV.From - rect.Bottom); state.MergeSpacer(verticalSpacer); // horizontal row carrier protectors var spacing = ParentChildSpacing; for (var firstInRowIndex = 0; firstInRowIndex < node.State.NumberOfSiblings; firstInRowIndex += node.State.NumberOfSiblingColumns) { var firstInRow = node.Children[firstInRowIndex].State; var lastInRow = node.Children[Math.Min(firstInRowIndex + node.State.NumberOfSiblingColumns - 1, node.State.NumberOfSiblings - 1)].State; var horizontalSpacer = node.Children[1 + node.State.NumberOfSiblings + firstInRowIndex / node.State.NumberOfSiblingColumns]; var width = lastInRow.Right >= verticalSpacer.State.Right ? lastInRow.Right - firstInRow.Left : // extend protector at least to the central carrier verticalSpacer.State.Right - firstInRow.Left; horizontalSpacer.State.AdjustSpacer( firstInRow.Left, firstInRow.SiblingsRowV.From - spacing, width, spacing); state.MergeSpacer(horizontalSpacer); spacing = SiblingSpacing; } }
/// <summary> /// Ctr. /// </summary> public BoundaryChangedEventArgs([NotNull] Boundary boundary, [NotNull] LayoutState.LayoutLevel layoutLevel, [NotNull] LayoutState state) { Boundary = boundary; LayoutLevel = layoutLevel; State = state; }
/// <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.State.NumberOfSiblings == 0) { return; } var siblingsRowExterior = Dimensions.MinMax(); if (Orientation == StackOrientation.SingleRowHorizontal) { 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); } } else if (Orientation == StackOrientation.SingleColumnVertical) { var prevRowExterior = new Dimensions( node.State.SiblingsRowV.From, node.State.SiblingsRowV.To); 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> /// 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.State.NumberOfSiblings <= MaxGroups * 2) { base.ApplyHorizontalLayout(state, level); return; } if (node.Level == 0) { node.State.SiblingsRowV = new Dimensions(node.State.Top, node.State.Bottom); } if (node.AssistantsRoot != null) { LayoutAlgorithm.HorizontalLayout(state, node.AssistantsRoot); } var adapter = new SingleFishboneLayoutAdapter(node); while (adapter.NextGroup()) { LayoutAlgorithm.HorizontalLayout(state, adapter.SpecialRoot); } var rect = node.State; // now align child nodes under the parent if (node.Level > 0) { double diff; if (node.State.NumberOfSiblingColumns > 1) { var leftCarrier = node.Children[node.State.NumberOfSiblings + 1].State.CenterH; var rightCarrier = node.Children[node.State.NumberOfSiblings + node.State.NumberOfSiblingColumns].State.CenterH; var desiredCenter = node.State.NumberOfSiblings == 1 || ParentAlignment == BranchParentAlignment.Center ? leftCarrier + (rightCarrier - leftCarrier) / 2 : ParentAlignment == BranchParentAlignment.Left ? leftCarrier + ChildConnectorHookLength : rightCarrier - ChildConnectorHookLength; //var desiredCenter = (leftCarrier + rightCarrier)/2.0; diff = rect.CenterH - desiredCenter; } else { var carrier = node.Children[1 + node.State.NumberOfSiblings].State.CenterH; var desiredCenter = rect.CenterH; diff = desiredCenter - carrier; } LayoutAlgorithm.MoveChildrenOnly(state, level, diff); } if (node.Level > 0) { // vertical connector from parent var ix = node.State.NumberOfSiblings; var verticalSpacer = node.Children[ix]; verticalSpacer.State.AdjustSpacer( rect.CenterH - ParentConnectorShield / 2, rect.Bottom, ParentConnectorShield, node.Children[0].State.SiblingsRowV.From - rect.Bottom); state.MergeSpacer(verticalSpacer); ix++; // vertical carriers already merged in ix += node.State.NumberOfSiblingColumns; if (node.State.NumberOfSiblingColumns > 1) { // have a horizontal carrier var horizontalSpacer = node.Children[ix]; var leftmost = node.Children[node.State.NumberOfSiblings + 1].State.TopLeft; var rightmost = node.Children[ix - 1].State.Right; horizontalSpacer.State.AdjustSpacer( leftmost.X, leftmost.Y - ParentChildSpacing, rightmost - leftmost.X, ParentChildSpacing); state.MergeSpacer(horizontalSpacer); } } }
/// <summary> /// Moves a given branch horizontally, including its root box. /// Also updates branch exterior rects. /// Also updates branch boundary for the current <paramref name="layoutLevel"/>. /// </summary> public static void MoveBranch([NotNull] LayoutState state, LayoutState.LayoutLevel layoutLevel, double offset) { MoveOneChild(state, layoutLevel.BranchRoot, offset); layoutLevel.Boundary.ReloadFromBranch(layoutLevel.BranchRoot); layoutLevel.BranchRoot.State.BranchExterior = layoutLevel.Boundary.BoundingRect; }