// This source file is adapted from the Windows Presentation Foundation project. // (https://github.com/dotnet/wpf/) // // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. using System; using Avalonia.Input; using Avalonia.Layout; namespace Avalonia.Controls { /// /// A panel which lays out its children horizontally or vertically. /// public class StackPanel : Panel, INavigableContainer { /// /// Defines the property. /// public static readonly StyledProperty SpacingProperty = StackLayout.SpacingProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty OrientationProperty = StackLayout.OrientationProperty.AddOwner(); /// /// Initializes static members of the class. /// static StackPanel() { AffectsMeasure(SpacingProperty); AffectsMeasure(OrientationProperty); } /// /// Gets or sets the size of the spacing to place between child controls. /// public double Spacing { get { return GetValue(SpacingProperty); } set { SetValue(SpacingProperty, value); } } /// /// Gets or sets the orientation in which child controls will be layed out. /// public Orientation Orientation { get { return GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } /// /// Gets the next control in the specified direction. /// /// The movement direction. /// The control from which movement begins. /// Whether to wrap around when the first or last item is reached. /// The control. IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from, bool wrap) { var result = GetControlInDirection(direction, from as IControl); if (result == null && wrap) { if (Orientation == Orientation.Vertical) { switch (direction) { case NavigationDirection.Up: case NavigationDirection.Previous: case NavigationDirection.PageUp: result = GetControlInDirection(NavigationDirection.Last, null); break; case NavigationDirection.Down: case NavigationDirection.Next: case NavigationDirection.PageDown: result = GetControlInDirection(NavigationDirection.First, null); break; } } else { switch (direction) { case NavigationDirection.Left: case NavigationDirection.Previous: case NavigationDirection.PageUp: result = GetControlInDirection(NavigationDirection.Last, null); break; case NavigationDirection.Right: case NavigationDirection.Next: case NavigationDirection.PageDown: result = GetControlInDirection(NavigationDirection.First, null); break; } } } return result; } /// /// Gets the next control in the specified direction. /// /// The movement direction. /// The control from which movement begins. /// The control. protected virtual IInputElement GetControlInDirection(NavigationDirection direction, IControl from) { var horiz = Orientation == Orientation.Horizontal; int index = from != null ? Children.IndexOf(from) : -1; switch (direction) { case NavigationDirection.First: index = 0; break; case NavigationDirection.Last: index = Children.Count - 1; break; case NavigationDirection.Next: if (index != -1) ++index; break; case NavigationDirection.Previous: if (index != -1) --index; break; case NavigationDirection.Left: if (index != -1) index = horiz ? index - 1 : -1; break; case NavigationDirection.Right: if (index != -1) index = horiz ? index + 1 : -1; break; case NavigationDirection.Up: if (index != -1) index = horiz ? -1 : index - 1; break; case NavigationDirection.Down: if (index != -1) index = horiz ? -1 : index + 1; break; default: index = -1; break; } if (index >= 0 && index < Children.Count) { return Children[index]; } else { return null; } } /// /// General StackPanel layout behavior is to grow unbounded in the "stacking" direction (Size To Content). /// Children in this dimension are encouraged to be as large as they like. In the other dimension, /// StackPanel will assume the maximum size of its children. /// /// Constraint /// Desired size protected override Size MeasureOverride(Size availableSize) { Size stackDesiredSize = new Size(); var children = Children; Size layoutSlotSize = availableSize; bool fHorizontal = (Orientation == Orientation.Horizontal); double spacing = Spacing; bool hasVisibleChild = false; // // Initialize child sizing and iterator data // Allow children as much size as they want along the stack. // if (fHorizontal) { layoutSlotSize = layoutSlotSize.WithWidth(Double.PositiveInfinity); } else { layoutSlotSize = layoutSlotSize.WithHeight(Double.PositiveInfinity); } // // Iterate through children. // While we still supported virtualization, this was hidden in a child iterator (see source history). // for (int i = 0, count = children.Count; i < count; ++i) { // Get next child. var child = children[i]; if (child == null) { continue; } bool isVisible = child.IsVisible; if (isVisible && !hasVisibleChild) { hasVisibleChild = true; } // Measure the child. child.Measure(layoutSlotSize); Size childDesiredSize = child.DesiredSize; // Accumulate child size. if (fHorizontal) { stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width + (isVisible ? spacing : 0) + childDesiredSize.Width); stackDesiredSize = stackDesiredSize.WithHeight(Math.Max(stackDesiredSize.Height, childDesiredSize.Height)); } else { stackDesiredSize = stackDesiredSize.WithWidth(Math.Max(stackDesiredSize.Width, childDesiredSize.Width)); stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height + (isVisible ? spacing : 0) + childDesiredSize.Height); } } if (fHorizontal) { stackDesiredSize = stackDesiredSize.WithWidth(stackDesiredSize.Width - (hasVisibleChild ? spacing : 0)); } else { stackDesiredSize = stackDesiredSize.WithHeight(stackDesiredSize.Height - (hasVisibleChild ? spacing : 0)); } return stackDesiredSize; } /// /// Content arrangement. /// /// Arrange size protected override Size ArrangeOverride(Size finalSize) { var children = Children; bool fHorizontal = (Orientation == Orientation.Horizontal); Rect rcChild = new Rect(finalSize); double previousChildSize = 0.0; var spacing = Spacing; // // Arrange and Position Children. // for (int i = 0, count = children.Count; i < count; ++i) { var child = children[i]; if (child == null || !child.IsVisible) { continue; } if (fHorizontal) { rcChild = rcChild.WithX(rcChild.X + previousChildSize); previousChildSize = child.DesiredSize.Width; rcChild = rcChild.WithWidth(previousChildSize); rcChild = rcChild.WithHeight(Math.Max(finalSize.Height, child.DesiredSize.Height)); previousChildSize += spacing; } else { rcChild = rcChild.WithY(rcChild.Y + previousChildSize); previousChildSize = child.DesiredSize.Height; rcChild = rcChild.WithHeight(previousChildSize); rcChild = rcChild.WithWidth(Math.Max(finalSize.Width, child.DesiredSize.Width)); previousChildSize += spacing; } ArrangeChild(child, rcChild, finalSize, Orientation); } return finalSize; } internal virtual void ArrangeChild( IControl child, Rect rect, Size panelSize, Orientation orientation) { child.Arrange(rect); } } }