// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Layout { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; public enum HorizontalAlignment { Stretch, Left, Center, Right, } public enum VerticalAlignment { Stretch, Top, Center, Bottom, } public class Layoutable : Visual, ILayoutable { public static readonly PerspexProperty WidthProperty = PerspexProperty.Register("Width", double.NaN); public static readonly PerspexProperty HeightProperty = PerspexProperty.Register("Height", double.NaN); public static readonly PerspexProperty MinWidthProperty = PerspexProperty.Register("MinWidth"); public static readonly PerspexProperty MaxWidthProperty = PerspexProperty.Register("MaxWidth", double.PositiveInfinity); public static readonly PerspexProperty MinHeightProperty = PerspexProperty.Register("MinHeight"); public static readonly PerspexProperty MaxHeightProperty = PerspexProperty.Register("MaxHeight", double.PositiveInfinity); public static readonly PerspexProperty MarginProperty = PerspexProperty.Register("Margin"); public static readonly PerspexProperty HorizontalAlignmentProperty = PerspexProperty.Register("HorizontalAlignment"); public static readonly PerspexProperty VerticalAlignmentProperty = PerspexProperty.Register("VerticalAlignment"); public double Width { get { return this.GetValue(WidthProperty); } set { this.SetValue(WidthProperty, value); } } public double Height { get { return this.GetValue(HeightProperty); } set { this.SetValue(HeightProperty, value); } } public double MinWidth { get { return this.GetValue(MinWidthProperty); } set { this.SetValue(MinWidthProperty, value); } } public double MaxWidth { get { return this.GetValue(MaxWidthProperty); } set { this.SetValue(MaxWidthProperty, value); } } public double MinHeight { get { return this.GetValue(MinHeightProperty); } set { this.SetValue(MinHeightProperty, value); } } public double MaxHeight { get { return this.GetValue(MaxHeightProperty); } set { this.SetValue(MaxHeightProperty, value); } } public Thickness Margin { get { return this.GetValue(MarginProperty); } set { this.SetValue(MarginProperty, value); } } public HorizontalAlignment HorizontalAlignment { get { return this.GetValue(HorizontalAlignmentProperty); } set { this.SetValue(HorizontalAlignmentProperty, value); } } public VerticalAlignment VerticalAlignment { get { return this.GetValue(VerticalAlignmentProperty); } set { this.SetValue(VerticalAlignmentProperty, value); } } public Size ActualSize { get { return ((IVisual)this).Bounds.Size; } } public Size? DesiredSize { get; set; } public void Measure(Size availableSize) { availableSize = availableSize.Deflate(this.Margin); this.DesiredSize = this.MeasureCore(availableSize).Constrain(availableSize); } public void Arrange(Rect rect) { if (this.DesiredSize.HasValue) { this.ArrangeCore(rect); } } public void InvalidateMeasure() { ILayoutRoot root = this.GetVisualAncestorOrSelf(); if (root != null && root.LayoutManager != null) { root.LayoutManager.InvalidateMeasure(this); } } public void InvalidateArrange() { ILayoutRoot root = this.GetVisualAncestorOrSelf(); if (root != null) { root.LayoutManager.InvalidateArrange(this); } } protected static void AffectsArrange(PerspexProperty property) { property.Changed.Subscribe(AffectsArrangeInvalidate); } protected static void AffectsMeasure(PerspexProperty property) { property.Changed.Subscribe(AffectsMeasureInvalidate); } protected virtual void ArrangeCore(Rect finalRect) { double originX = finalRect.X + this.Margin.Left; double originY = finalRect.Y + this.Margin.Top; double sizeX = Math.Max(0, finalRect.Width - this.Margin.Left - this.Margin.Right); double sizeY = Math.Max(0, finalRect.Height - this.Margin.Top - this.Margin.Bottom); if (this.HorizontalAlignment != HorizontalAlignment.Stretch) { sizeX = Math.Min(sizeX, this.DesiredSize.Value.Width); } if (this.VerticalAlignment != VerticalAlignment.Stretch) { sizeY = Math.Min(sizeY, this.DesiredSize.Value.Height); } Size taken = this.ArrangeOverride(new Size(sizeX, sizeY)); sizeX = Math.Min(taken.Width, sizeX); sizeY = Math.Min(taken.Height, sizeY); switch (this.HorizontalAlignment) { case HorizontalAlignment.Center: originX += (finalRect.Width - sizeX) / 2; break; case HorizontalAlignment.Right: originX += finalRect.Width - sizeX; break; } switch (this.VerticalAlignment) { case VerticalAlignment.Center: originY += (finalRect.Height - sizeY) / 2; break; case VerticalAlignment.Bottom: originY += finalRect.Height - sizeY; break; } ((IVisual)this).Bounds = new Rect(originX, originY, sizeX, sizeY); } protected virtual Size ArrangeOverride(Size finalSize) { return finalSize; } protected virtual Size MeasureCore(Size availableSize) { if (this.IsVisible) { availableSize = LayoutHelper.ApplyLayoutConstraints(this, availableSize) .Deflate(this.Margin); var measured = this.MeasureOverride(availableSize); return measured.Inflate(this.Margin); } else { return new Size(); } } protected virtual Size MeasureOverride(Size availableSize) { return new Size(); } private static void AffectsArrangeInvalidate(PerspexPropertyChangedEventArgs e) { ILayoutable control = e.Sender as ILayoutable; if (control != null) { control.InvalidateArrange(); } } private static void AffectsMeasureInvalidate(PerspexPropertyChangedEventArgs e) { ILayoutable control = e.Sender as ILayoutable; if (control != null) { control.InvalidateMeasure(); } } } }