From af9f8408eb38c2ae5eda9ccbb9887e54b3919ef7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 27 Jun 2014 01:25:02 +0200 Subject: [PATCH] Moved layout stuff into Layoutable Out of Control. --- .../Controls/BorderTests.cs | 1 + Perspex.Windows/Window.cs | 6 - Perspex/Controls/ContentPresenter.cs | 1 + Perspex/Controls/Control.cs | 252 +---------------- Perspex/Interactive.cs | 3 +- Perspex/Layout/ILayoutable.cs | 2 - Perspex/Layout/LayoutManager.cs | 6 +- Perspex/Layout/Layoutable.cs | 263 ++++++++++++++++++ Perspex/Perspex.csproj | 1 + Perspex/Themes/Default/CheckBoxStyle.cs | 1 + TestApplication/Program.cs | 1 + 11 files changed, 275 insertions(+), 262 deletions(-) create mode 100644 Perspex/Layout/Layoutable.cs diff --git a/Perspex.Direct2D1.RenderTests/Controls/BorderTests.cs b/Perspex.Direct2D1.RenderTests/Controls/BorderTests.cs index b61bf6b40d..4b7142d039 100644 --- a/Perspex.Direct2D1.RenderTests/Controls/BorderTests.cs +++ b/Perspex.Direct2D1.RenderTests/Controls/BorderTests.cs @@ -8,6 +8,7 @@ namespace Perspex.Direct2D1.RenderTests.Controls { using Microsoft.VisualStudio.TestTools.UnitTesting; using Perspex.Controls; + using Perspex.Layout; using Perspex.Media; [TestClass] diff --git a/Perspex.Windows/Window.cs b/Perspex.Windows/Window.cs index 073ed138ab..41f24927e2 100644 --- a/Perspex.Windows/Window.cs +++ b/Perspex.Windows/Window.cs @@ -9,7 +9,6 @@ namespace Perspex.Windows using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; - using System.Linq; using System.Reactive.Linq; using System.Runtime.InteropServices; using Perspex.Controls; @@ -36,11 +35,6 @@ namespace Perspex.Windows private bool layoutPending; - static Window() - { - FontSizeProperty.OverrideDefaultValue(typeof(Window), 18.0); - } - public Window() { IPlatformRenderInterface factory = Locator.Current.GetService(); diff --git a/Perspex/Controls/ContentPresenter.cs b/Perspex/Controls/ContentPresenter.cs index 4c882f8f60..e4a27a5590 100644 --- a/Perspex/Controls/ContentPresenter.cs +++ b/Perspex/Controls/ContentPresenter.cs @@ -9,6 +9,7 @@ namespace Perspex.Controls using System; using System.Collections.Generic; using System.Linq; + using Perspex.Layout; using Perspex.Media; public class ContentPresenter : Control, IVisual diff --git a/Perspex/Controls/Control.cs b/Perspex/Controls/Control.cs index ecd901e1b4..5b77b9049a 100644 --- a/Perspex/Controls/Control.cs +++ b/Perspex/Controls/Control.cs @@ -17,23 +17,7 @@ namespace Perspex.Controls using Perspex.Styling; using Splat; - public enum HorizontalAlignment - { - Stretch, - Left, - Center, - Right, - } - - public enum VerticalAlignment - { - Stretch, - Top, - Center, - Bottom, - } - - public class Control : Interactive, ILayoutable, IFocusable, ILogical, IStyleable, IStyled + public class Control : Interactive, IFocusable, ILogical, IStyleable, IStyled { public static readonly PerspexProperty BackgroundProperty = PerspexProperty.Register("Background", inherits: true); @@ -56,42 +40,15 @@ namespace Perspex.Controls public static readonly PerspexProperty ForegroundProperty = PerspexProperty.Register("Foreground", new SolidColorBrush(0xff000000), true); - public static readonly PerspexProperty HeightProperty = - PerspexProperty.Register("Height", double.NaN); - public static readonly PerspexProperty IsFocusedProperty = PerspexProperty.Register("IsFocused", false); public static readonly PerspexProperty IsPointerOverProperty = PerspexProperty.Register("IsPointerOver"); - public static readonly PerspexProperty HorizontalAlignmentProperty = - PerspexProperty.Register("HorizontalAlignment"); - - public static readonly PerspexProperty MarginProperty = - PerspexProperty.Register("Margin"); - - public static readonly PerspexProperty MaxHeightProperty = - PerspexProperty.Register("MaxHeight", double.PositiveInfinity); - - public static readonly PerspexProperty MaxWidthProperty = - PerspexProperty.Register("MaxWidth", double.PositiveInfinity); - - public static readonly PerspexProperty MinHeightProperty = - PerspexProperty.Register("MinHeight"); - - public static readonly PerspexProperty MinWidthProperty = - PerspexProperty.Register("MinWidth"); - public static readonly PerspexProperty ParentProperty = PerspexProperty.Register("Parent"); - public static readonly PerspexProperty VerticalAlignmentProperty = - PerspexProperty.Register("VerticalAlignment"); - - public static readonly PerspexProperty WidthProperty = - PerspexProperty.Register("Width", double.NaN); - public static readonly RoutedEvent GotFocusEvent = RoutedEvent.Register("GotFocus", RoutingStrategy.Bubble); @@ -177,11 +134,6 @@ namespace Perspex.Controls remove { this.RemoveHandler(PointerReleasedEvent, value); } } - public Size ActualSize - { - get { return ((IVisual)this).Bounds.Size; } - } - public Brush Background { get { return this.GetValue(BackgroundProperty); } @@ -217,12 +169,6 @@ namespace Perspex.Controls } } - public Size? DesiredSize - { - get; - set; - } - public double FontSize { get { return this.GetValue(FontSizeProperty); } @@ -241,12 +187,6 @@ namespace Perspex.Controls set { this.SetValue(ForegroundProperty, value); } } - public double Height - { - get { return this.GetValue(HeightProperty); } - set { this.SetValue(HeightProperty, value); } - } - public bool IsFocused { get { return this.GetValue(IsFocusedProperty); } @@ -282,42 +222,6 @@ namespace Perspex.Controls internal set { this.SetValue(IsPointerOverProperty, value); } } - public HorizontalAlignment HorizontalAlignment - { - get { return this.GetValue(HorizontalAlignmentProperty); } - set { this.SetValue(HorizontalAlignmentProperty, value); } - } - - public Thickness Margin - { - get { return this.GetValue(MarginProperty); } - set { this.SetValue(MarginProperty, value); } - } - - public double MaxHeight - { - get { return this.GetValue(MaxHeightProperty); } - set { this.SetValue(MaxHeightProperty, 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 MinWidth - { - get { return this.GetValue(MinWidthProperty); } - set { this.SetValue(MinWidthProperty, value); } - } - public Control Parent { get { return this.GetValue(ParentProperty); } @@ -348,18 +252,6 @@ namespace Perspex.Controls internal set; } - public VerticalAlignment VerticalAlignment - { - get { return this.GetValue(VerticalAlignmentProperty); } - set { this.SetValue(VerticalAlignmentProperty, value); } - } - - public double Width - { - get { return this.GetValue(WidthProperty); } - set { this.SetValue(WidthProperty, value); } - } - ILogical ILogical.LogicalParent { get { return this.Parent; } @@ -371,77 +263,11 @@ namespace Perspex.Controls get { return Enumerable.Empty(); } } - protected static void AffectsArrange(PerspexProperty property) - { - property.Changed.Subscribe(AffectsArrangeInvalidate); - } - - protected static void AffectsMeasure(PerspexProperty property) - { - property.Changed.Subscribe(AffectsMeasureInvalidate); - } - - private static void AffectsArrangeInvalidate(PerspexPropertyChangedEventArgs e) - { - Control control = e.Sender as Control; - - if (control != null) - { - control.InvalidateArrange(); - } - } - - private static void AffectsMeasureInvalidate(PerspexPropertyChangedEventArgs e) - { - Control control = e.Sender as Control; - - if (control != null) - { - control.InvalidateMeasure(); - } - } - - public ILayoutRoot GetLayoutRoot() - { - return this.GetVisualAncestorOrSelf(); - } - - public void Arrange(Rect rect) - { - this.ArrangeCore(rect); - } - - public void Measure(Size availableSize) - { - availableSize = availableSize.Deflate(this.Margin); - this.DesiredSize = this.MeasureCore(availableSize).Constrain(availableSize); - } - public void Focus() { Locator.Current.GetService().Focus(this); } - public void InvalidateArrange() - { - ILayoutRoot root = this.GetLayoutRoot(); - - if (root != null) - { - root.LayoutManager.InvalidateArrange(this); - } - } - - public void InvalidateMeasure() - { - ILayoutRoot root = this.GetLayoutRoot(); - - if (root != null && root.LayoutManager != null) - { - root.LayoutManager.InvalidateMeasure(this); - } - } - protected void AddPseudoClass(PerspexProperty property, string className) { this.GetObservable(property).Subscribe(x => @@ -457,82 +283,6 @@ namespace Perspex.Controls }); } - 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) - { - Size measuredSize = this.MeasureOverride(availableSize.Deflate(this.Margin)); - double width = (this.Width > 0) ? this.Width : measuredSize.Width; - double height = (this.Height > 0) ? this.Height : measuredSize.Height; - - width = Math.Min(width, this.MaxWidth); - width = Math.Max(width, this.MinWidth); - height = Math.Min(height, this.MaxHeight); - height = Math.Max(height, this.MinHeight); - - return new Size(width, height); - } - else - { - return new Size(); - } - } - - protected virtual Size MeasureOverride(Size availableSize) - { - return new Size(); - } - protected override void AttachedToVisualTree() { IStyler styler = Locator.Current.GetService(); diff --git a/Perspex/Interactive.cs b/Perspex/Interactive.cs index ea6360426e..4860820056 100644 --- a/Perspex/Interactive.cs +++ b/Perspex/Interactive.cs @@ -10,8 +10,9 @@ namespace Perspex using System.Collections.Generic; using System.Reactive; using System.Reactive.Linq; + using Perspex.Layout; - public class Interactive : Visual + public class Interactive : Layoutable { private Dictionary> eventHandlers = new Dictionary>(); diff --git a/Perspex/Layout/ILayoutable.cs b/Perspex/Layout/ILayoutable.cs index 56641ccdd8..6fd20e309b 100644 --- a/Perspex/Layout/ILayoutable.cs +++ b/Perspex/Layout/ILayoutable.cs @@ -10,8 +10,6 @@ namespace Perspex.Layout { Size? DesiredSize { get; } - ILayoutRoot GetLayoutRoot(); - void Arrange(Rect rect); void Measure(Size availableSize); diff --git a/Perspex/Layout/LayoutManager.cs b/Perspex/Layout/LayoutManager.cs index e1111e830d..ea09e628d0 100644 --- a/Perspex/Layout/LayoutManager.cs +++ b/Perspex/Layout/LayoutManager.cs @@ -41,13 +41,15 @@ namespace Perspex.Layout public void InvalidateMeasure(ILayoutable item) { - this.root = item.GetLayoutRoot(); + IVisual visual = item as IVisual; + this.root = visual.GetVisualAncestorOrSelf(); this.layoutNeeded.OnNext(Unit.Default); } public void InvalidateArrange(ILayoutable item) { - this.root = item.GetLayoutRoot(); + IVisual visual = item as IVisual; + this.root = visual.GetVisualAncestorOrSelf(); this.layoutNeeded.OnNext(Unit.Default); } } diff --git a/Perspex/Layout/Layoutable.cs b/Perspex/Layout/Layoutable.cs new file mode 100644 index 0000000000..8b510cde3b --- /dev/null +++ b/Perspex/Layout/Layoutable.cs @@ -0,0 +1,263 @@ +// ----------------------------------------------------------------------- +// +// 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) + { + 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) + { + Size measuredSize = this.MeasureOverride(availableSize.Deflate(this.Margin)); + double width = (this.Width > 0) ? this.Width : measuredSize.Width; + double height = (this.Height > 0) ? this.Height : measuredSize.Height; + + width = Math.Min(width, this.MaxWidth); + width = Math.Max(width, this.MinWidth); + height = Math.Min(height, this.MaxHeight); + height = Math.Max(height, this.MinHeight); + + return new Size(width, height); + } + 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(); + } + } + + } +} diff --git a/Perspex/Perspex.csproj b/Perspex/Perspex.csproj index 939a07c31d..9c5ac137bf 100644 --- a/Perspex/Perspex.csproj +++ b/Perspex/Perspex.csproj @@ -100,6 +100,7 @@ + diff --git a/Perspex/Themes/Default/CheckBoxStyle.cs b/Perspex/Themes/Default/CheckBoxStyle.cs index 8071e79da6..206a37a022 100644 --- a/Perspex/Themes/Default/CheckBoxStyle.cs +++ b/Perspex/Themes/Default/CheckBoxStyle.cs @@ -9,6 +9,7 @@ namespace Perspex.Themes.Default using System; using System.Linq; using Perspex.Controls; + using Perspex.Layout; using Perspex.Media; using Perspex.Shapes; using Perspex.Styling; diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index 9b417053cf..325d5da082 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Perspex; using Perspex.Controls; using Perspex.Input; +using Perspex.Layout; using Perspex.Media; using Perspex.Shapes; using Perspex.Styling;