diff options
Diffstat (limited to 'Xamarin.Forms.Core/StackLayout.cs')
-rw-r--r-- | Xamarin.Forms.Core/StackLayout.cs | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/Xamarin.Forms.Core/StackLayout.cs b/Xamarin.Forms.Core/StackLayout.cs new file mode 100644 index 00000000..4dc7bfa3 --- /dev/null +++ b/Xamarin.Forms.Core/StackLayout.cs @@ -0,0 +1,460 @@ +using System; + +namespace Xamarin.Forms +{ + public class StackLayout : Layout<View> + { + public static readonly BindableProperty OrientationProperty = BindableProperty.Create("Orientation", typeof(StackOrientation), typeof(StackLayout), StackOrientation.Vertical, + propertyChanged: (bindable, oldvalue, newvalue) => ((StackLayout)bindable).InvalidateLayout()); + + public static readonly BindableProperty SpacingProperty = BindableProperty.Create("Spacing", typeof(double), typeof(StackLayout), 6d, + propertyChanged: (bindable, oldvalue, newvalue) => ((StackLayout)bindable).InvalidateLayout()); + + LayoutInformation _layoutInformation = new LayoutInformation(); + + public StackOrientation Orientation + { + get { return (StackOrientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + + public double Spacing + { + get { return (double)GetValue(SpacingProperty); } + set { SetValue(SpacingProperty, value); } + } + + protected override void LayoutChildren(double x, double y, double width, double height) + { + if (!HasVisibileChildren()) + { + return; + } + + if (width == _layoutInformation.Constraint.Width && height == _layoutInformation.Constraint.Height) + { + StackOrientation orientation = Orientation; + + AlignOffAxis(_layoutInformation, orientation, width, height); + ProcessExpanders(_layoutInformation, orientation, x, y, width, height); + } + else + { + CalculateLayout(_layoutInformation, x, y, width, height, true); + } + + LayoutInformation layoutInformationCopy = _layoutInformation; + + for (var i = 0; i < LogicalChildren.Count; i++) + { + var child = (View)LogicalChildren[i]; + if (child.IsVisible) + LayoutChildIntoBoundingRegion(child, layoutInformationCopy.Plots[i], layoutInformationCopy.Requests[i]); + } + } + + [Obsolete("Use OnMeasure")] + protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint) + { + if (!HasVisibileChildren()) + { + return new SizeRequest(); + } + + // calculate with padding inset for X,Y so we can hopefully re-use this in the layout pass + Thickness padding = Padding; + CalculateLayout(_layoutInformation, padding.Left, padding.Top, widthConstraint, heightConstraint, false); + var result = new SizeRequest(_layoutInformation.Bounds.Size, _layoutInformation.MinimumSize); + return result; + } + + internal override void ComputeConstraintForView(View view) + { + ComputeConstraintForView(view, false); + } + + internal override void InvalidateMeasure(InvalidationTrigger trigger) + { + _layoutInformation = new LayoutInformation(); + base.InvalidateMeasure(trigger); + } + + void AlignOffAxis(LayoutInformation layout, StackOrientation orientation, double widthConstraint, double heightConstraint) + { + for (var i = 0; i < layout.Plots.Length; i++) + { + if (!((View)LogicalChildren[i]).IsVisible) + continue; + if (orientation == StackOrientation.Vertical) + { + layout.Plots[i].Width = widthConstraint; + } + else + { + layout.Plots[i].Height = heightConstraint; + } + } + } + + void CalculateLayout(LayoutInformation layout, double x, double y, double widthConstraint, double heightConstraint, bool processExpanders) + { + layout.Constraint = new Size(widthConstraint, heightConstraint); + layout.Expanders = 0; + layout.CompressionSpace = 0; + layout.Plots = new Rectangle[Children.Count]; + layout.Requests = new SizeRequest[Children.Count]; + + StackOrientation orientation = Orientation; + + CalculateNaiveLayout(layout, orientation, x, y, widthConstraint, heightConstraint); + CompressNaiveLayout(layout, orientation, widthConstraint, heightConstraint); + + if (processExpanders) + { + AlignOffAxis(layout, orientation, widthConstraint, heightConstraint); + ProcessExpanders(layout, orientation, x, y, widthConstraint, heightConstraint); + } + } + + void CalculateNaiveLayout(LayoutInformation layout, StackOrientation orientation, double x, double y, double widthConstraint, double heightConstraint) + { + layout.CompressionSpace = 0; + + double xOffset = x; + double yOffset = y; + double boundsWidth = 0; + double boundsHeight = 0; + double minimumWidth = 0; + double minimumHeight = 0; + double spacing = Spacing; + if (orientation == StackOrientation.Vertical) + { + View expander = null; + for (var i = 0; i < LogicalChildren.Count; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + + if (child.VerticalOptions.Expands) + { + layout.Expanders++; + if (expander != null) + { + // we have multiple expanders, make sure previous expanders are reset to not be fixed because they no logner are + ComputeConstraintForView(child, false); + } + expander = child; + } + SizeRequest request = child.Measure(widthConstraint, double.PositiveInfinity, MeasureFlags.IncludeMargins); + + var bounds = new Rectangle(x, yOffset, request.Request.Width, request.Request.Height); + layout.Plots[i] = bounds; + layout.Requests[i] = request; + layout.CompressionSpace += Math.Max(0, request.Request.Height - request.Minimum.Height); + yOffset = bounds.Bottom + spacing; + + boundsWidth = Math.Max(boundsWidth, request.Request.Width); + boundsHeight = bounds.Bottom - y; + minimumHeight += request.Minimum.Height + spacing; + minimumWidth = Math.Max(minimumWidth, request.Minimum.Width); + } + minimumHeight -= spacing; + if (expander != null) + ComputeConstraintForView(expander, layout.Expanders == 1); // warning : slightly obtuse, but we either need to setup the expander or clear the last one + } + else + { + View expander = null; + for (var i = 0; i < LogicalChildren.Count; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + + if (child.HorizontalOptions.Expands) + { + layout.Expanders++; + if (expander != null) + { + ComputeConstraintForView(child, false); + } + expander = child; + } + SizeRequest request = child.Measure(double.PositiveInfinity, heightConstraint, MeasureFlags.IncludeMargins); + + var bounds = new Rectangle(xOffset, y, request.Request.Width, request.Request.Height); + layout.Plots[i] = bounds; + layout.Requests[i] = request; + layout.CompressionSpace += Math.Max(0, request.Request.Width - request.Minimum.Width); + xOffset = bounds.Right + spacing; + + boundsWidth = bounds.Right - x; + boundsHeight = Math.Max(boundsHeight, request.Request.Height); + minimumWidth += request.Minimum.Width + spacing; + minimumHeight = Math.Max(minimumHeight, request.Minimum.Height); + } + minimumWidth -= spacing; + if (expander != null) + ComputeConstraintForView(expander, layout.Expanders == 1); + } + + layout.Bounds = new Rectangle(x, y, boundsWidth, boundsHeight); + layout.MinimumSize = new Size(minimumWidth, minimumHeight); + } + + void CompressHorizontalLayout(LayoutInformation layout, double widthConstraint, double heightConstraint) + { + double xOffset = 0; + + if (widthConstraint >= layout.Bounds.Width) + { + // no need to compress + return; + } + + double requiredCompression = layout.Bounds.Width - widthConstraint; + double compressionSpace = layout.CompressionSpace; + double compressionPressure = (requiredCompression / layout.CompressionSpace).Clamp(0, 1); + + for (var i = 0; i < layout.Plots.Length; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + + Size minimum = layout.Requests[i].Minimum; + + layout.Plots[i].X -= xOffset; + + Rectangle plot = layout.Plots[i]; + double availableSpace = plot.Width - minimum.Width; + if (availableSpace <= 0) + continue; + + compressionSpace -= availableSpace; + + double compression = availableSpace * compressionPressure; + xOffset += compression; + + double newWidth = plot.Width - compression; + SizeRequest newRequest = child.Measure(newWidth, heightConstraint, MeasureFlags.IncludeMargins); + + layout.Requests[i] = newRequest; + + plot.Height = newRequest.Request.Height; + + if (newRequest.Request.Width < newWidth) + { + double delta = newWidth - newRequest.Request.Width; + newWidth = newRequest.Request.Width; + xOffset += delta; + requiredCompression = requiredCompression - xOffset; + compressionPressure = (requiredCompression / compressionSpace).Clamp(0, 1); + } + plot.Width = newWidth; + + layout.Bounds.Height = Math.Max(layout.Bounds.Height, plot.Height); + + layout.Plots[i] = plot; + } + } + + void CompressNaiveLayout(LayoutInformation layout, StackOrientation orientation, double widthConstraint, double heightConstraint) + { + if (layout.CompressionSpace <= 0) + return; + + if (orientation == StackOrientation.Vertical) + { + CompressVerticalLayout(layout, widthConstraint, heightConstraint); + } + else + { + CompressHorizontalLayout(layout, widthConstraint, heightConstraint); + } + } + + void CompressVerticalLayout(LayoutInformation layout, double widthConstraint, double heightConstraint) + { + double yOffset = 0; + + if (heightConstraint >= layout.Bounds.Height) + { + // no need to compress + return; + } + + double requiredCompression = layout.Bounds.Height - heightConstraint; + double compressionSpace = layout.CompressionSpace; + double compressionPressure = (requiredCompression / layout.CompressionSpace).Clamp(0, 1); + + for (var i = 0; i < layout.Plots.Length; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + + Size minimum = layout.Requests[i].Minimum; + + layout.Plots[i].Y -= yOffset; + + Rectangle plot = layout.Plots[i]; + double availableSpace = plot.Height - minimum.Height; + if (availableSpace <= 0) + continue; + + compressionSpace -= availableSpace; + + double compression = availableSpace * compressionPressure; + yOffset += compression; + + double newHeight = plot.Height - compression; + SizeRequest newRequest = child.Measure(widthConstraint, newHeight, MeasureFlags.IncludeMargins); + + layout.Requests[i] = newRequest; + + plot.Width = newRequest.Request.Width; + + if (newRequest.Request.Height < newHeight) + { + double delta = newHeight - newRequest.Request.Height; + newHeight = newRequest.Request.Height; + yOffset += delta; + requiredCompression = requiredCompression - yOffset; + compressionPressure = (requiredCompression / compressionSpace).Clamp(0, 1); + } + plot.Height = newHeight; + + layout.Bounds.Width = Math.Max(layout.Bounds.Width, plot.Width); + + layout.Plots[i] = plot; + } + } + + void ComputeConstraintForView(View view, bool isOnlyExpander) + { + if (Orientation == StackOrientation.Horizontal) + { + if ((Constraint & LayoutConstraint.VerticallyFixed) != 0 && view.VerticalOptions.Alignment == LayoutAlignment.Fill) + { + if (isOnlyExpander && view.HorizontalOptions.Alignment == LayoutAlignment.Fill && Constraint == LayoutConstraint.Fixed) + { + view.ComputedConstraint = LayoutConstraint.Fixed; + } + else + { + view.ComputedConstraint = LayoutConstraint.VerticallyFixed; + } + } + else + { + view.ComputedConstraint = LayoutConstraint.None; + } + } + else + { + if ((Constraint & LayoutConstraint.HorizontallyFixed) != 0 && view.HorizontalOptions.Alignment == LayoutAlignment.Fill) + { + if (isOnlyExpander && view.VerticalOptions.Alignment == LayoutAlignment.Fill && Constraint == LayoutConstraint.Fixed) + { + view.ComputedConstraint = LayoutConstraint.Fixed; + } + else + { + view.ComputedConstraint = LayoutConstraint.HorizontallyFixed; + } + } + else + { + view.ComputedConstraint = LayoutConstraint.None; + } + } + } + + bool HasVisibileChildren() + { + for (var index = 0; index < InternalChildren.Count; index++) + { + var child = (VisualElement)InternalChildren[index]; + if (child.IsVisible) + return true; + } + return false; + } + + void ProcessExpanders(LayoutInformation layout, StackOrientation orientation, double x, double y, double widthConstraint, double heightConstraint) + { + if (layout.Expanders <= 0) + return; + + if (orientation == StackOrientation.Vertical) + { + double extraSpace = heightConstraint - layout.Bounds.Height; + if (extraSpace <= 0) + return; + + double spacePerExpander = extraSpace / layout.Expanders; + double yOffset = 0; + + for (var i = 0; i < LogicalChildren.Count; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + Rectangle plot = layout.Plots[i]; + plot.Y += yOffset; + + if (child.VerticalOptions.Expands) + { + plot.Height += spacePerExpander; + yOffset += spacePerExpander; + } + + layout.Plots[i] = plot; + } + + layout.Bounds.Height = heightConstraint; + } + else + { + double extraSpace = widthConstraint - layout.Bounds.Width; + if (extraSpace <= 0) + return; + + double spacePerExpander = extraSpace / layout.Expanders; + double xOffset = 0; + + for (var i = 0; i < LogicalChildren.Count; i++) + { + var child = (View)LogicalChildren[i]; + if (!child.IsVisible) + continue; + Rectangle plot = layout.Plots[i]; + plot.X += xOffset; + + if (child.HorizontalOptions.Expands) + { + plot.Width += spacePerExpander; + xOffset += spacePerExpander; + } + + layout.Plots[i] = plot; + } + + layout.Bounds.Width = widthConstraint; + } + } + + class LayoutInformation + { + public Rectangle Bounds; + public double CompressionSpace; + public Size Constraint; + public int Expanders; + public Size MinimumSize; + public Rectangle[] Plots; + public SizeRequest[] Requests; + } + } +}
\ No newline at end of file |