diff --git a/Perspex/Controls/ContentPresenter.cs b/Perspex/Controls/ContentPresenter.cs index 4227db97a4..c0e7991049 100644 --- a/Perspex/Controls/ContentPresenter.cs +++ b/Perspex/Controls/ContentPresenter.cs @@ -21,6 +21,24 @@ namespace Perspex.Controls private Visual visualChild; + public ContentPresenter() + { + this.GetObservableWithHistory(ContentProperty).Subscribe(x => + { + if (x.Item1 is Control) + { + ((IVisual)x.Item1).VisualParent = null; + ((ILogical)x.Item1).LogicalParent = null; + } + + if (x.Item2 is Control) + { + ((IVisual)x.Item2).VisualParent = this; + ((ILogical)x.Item2).LogicalParent = this; + } + }); + } + public object Content { get { return this.GetValue(ContentProperty); } diff --git a/Perspex/Controls/Control.cs b/Perspex/Controls/Control.cs index 05a7b76afb..940f46437b 100644 --- a/Perspex/Controls/Control.cs +++ b/Perspex/Controls/Control.cs @@ -47,8 +47,11 @@ 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 IsMouseOverProperty = - PerspexProperty.Register("IsMouseOver"); + PerspexProperty.Register("IsMouseOver"); public static readonly PerspexProperty HorizontalAlignmentProperty = PerspexProperty.Register("HorizontalAlignment"); @@ -56,12 +59,27 @@ namespace Perspex.Controls 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 ReadOnlyPerspexProperty ParentProperty = new ReadOnlyPerspexProperty(ParentPropertyRW); public static readonly PerspexProperty VerticalAlignmentProperty = PerspexProperty.Register("VerticalAlignment"); + public static readonly PerspexProperty WidthProperty = + PerspexProperty.Register("Width", double.NaN); + public static readonly RoutedEvent MouseLeftButtonDownEvent = RoutedEvent.Register("MouseLeftButtonDown", RoutingStrategy.Bubble); @@ -196,6 +214,12 @@ namespace Perspex.Controls } } + public double Height + { + get { return this.GetValue(HeightProperty); } + set { this.SetValue(HeightProperty, value); } + } + public bool IsMouseOver { get { return this.GetValue(IsMouseOverProperty); } @@ -214,6 +238,30 @@ namespace Perspex.Controls 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(ParentPropertyRW); } @@ -250,6 +298,12 @@ namespace Perspex.Controls 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; } diff --git a/Perspex/Controls/LogicalChildren.cs b/Perspex/Controls/LogicalChildren.cs index dc49a9e5aa..9b5a308ce8 100644 --- a/Perspex/Controls/LogicalChildren.cs +++ b/Perspex/Controls/LogicalChildren.cs @@ -26,12 +26,25 @@ namespace Perspex.Controls { private T parent; + private PerspexList childrenCollection; + private List inner = new List(); - public LogicalChildren(T parent, INotifyCollectionChanged childrenCollection) + public LogicalChildren(T parent, PerspexList childrenCollection) { this.parent = parent; - childrenCollection.CollectionChanged += CollectionChanged; + this.childrenCollection = childrenCollection; + this.Add(childrenCollection); + childrenCollection.CollectionChanged += this.CollectionChanged; + } + + public void Change(PerspexList childrenCollection) + { + this.childrenCollection.CollectionChanged -= this.CollectionChanged; + this.Remove(inner.ToList()); + this.childrenCollection = childrenCollection; + this.Add(childrenCollection); + childrenCollection.CollectionChanged += this.CollectionChanged; } private void Add(IEnumerable items) diff --git a/Perspex/Controls/Panel.cs b/Perspex/Controls/Panel.cs index 6b4e748376..ead4f2957d 100644 --- a/Perspex/Controls/Panel.cs +++ b/Perspex/Controls/Panel.cs @@ -17,20 +17,43 @@ namespace Perspex.Controls /// /// Base class for controls that can contain multiple children. /// - public class Panel : Control + public class Panel : Control, IVisual { + private PerspexList children; + private LogicalChildren logicalChildren; - public Panel() + public PerspexList Children { - this.Children = new ObservableCollection(); - this.logicalChildren = new LogicalChildren(this, this.Children); + get + { + if (this.children == null) + { + this.children = new PerspexList(); + this.logicalChildren = new LogicalChildren(this, this.children); + } + + return this.children; + } + + set + { + this.children = value; + + if (this.logicalChildren != null) + { + this.logicalChildren.Change(this.children); + } + else + { + this.logicalChildren = new LogicalChildren(this, this.children); + } + } } - public ObservableCollection Children + IEnumerable IVisual.VisualChildren { - get; - private set; + get { return this.children; } } } } diff --git a/Perspex/Controls/StackPanel.cs b/Perspex/Controls/StackPanel.cs new file mode 100644 index 0000000000..63ce6e3e88 --- /dev/null +++ b/Perspex/Controls/StackPanel.cs @@ -0,0 +1,154 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 Tricycle. All rights reserved. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + public enum Orientation + { + Horizontal, + Vertical, + } + + public class StackPanel : Panel + { + public static readonly PerspexProperty OrientationProperty = + PerspexProperty.Register("Orientation"); + + public Orientation Orientation + { + get { return this.GetValue(OrientationProperty); } + set { this.SetValue(OrientationProperty, value); } + } + + protected override Size MeasureContent(Size availableSize) + { + double childAvailableWidth = double.PositiveInfinity; + double childAvailableHeight = double.PositiveInfinity; + + if (this.Orientation == Orientation.Vertical) + { + childAvailableWidth = availableSize.Width; + + if (!double.IsNaN(this.Width)) + { + childAvailableWidth = this.Width; + } + + childAvailableWidth = Math.Min(childAvailableWidth, this.MaxWidth); + childAvailableWidth = Math.Max(childAvailableWidth, this.MinWidth); + } + else + { + childAvailableHeight = availableSize.Height; + + if (!double.IsNaN(this.Height)) + { + childAvailableHeight = this.Height; + } + + childAvailableHeight = Math.Min(childAvailableHeight, this.MaxHeight); + childAvailableHeight = Math.Max(childAvailableHeight, this.MinHeight); + } + + double measuredWidth = 0; + double measuredHeight = 0; + + foreach (Control child in this.Children) + { + child.Measure(new Size(childAvailableWidth, childAvailableHeight)); + Size size = child.DesiredSize.Value; + + if (Orientation == Orientation.Vertical) + { + measuredHeight += size.Height; + measuredWidth = Math.Max(measuredWidth, size.Width); + } + else + { + measuredWidth += size.Width; + measuredHeight = Math.Max(measuredHeight, size.Height); + } + } + + return new Size(measuredWidth, measuredHeight); + } + + protected override Size ArrangeContent(Size finalSize) + { + double arrangedWidth = finalSize.Width; + double arrangedHeight = finalSize.Height; + + if (Orientation == Orientation.Vertical) + { + arrangedHeight = 0; + } + else + { + arrangedWidth = 0; + } + + foreach (Control child in this.Children) + { + double childWidth = child.DesiredSize.Value.Width; + double childHeight = child.DesiredSize.Value.Height; + + if (Orientation == Orientation.Vertical) + { + childWidth = finalSize.Width; + + Rect childFinal = new Rect(0, arrangedHeight, childWidth, childHeight); + + if (childFinal.IsEmpty) + { + child.Arrange(new Rect()); + } + else + { + child.Arrange(childFinal); + } + + arrangedWidth = Math.Max(arrangedWidth, childWidth); + arrangedHeight += childHeight; + } + else + { + childHeight = finalSize.Height; + + Rect childFinal = new Rect(arrangedWidth, 0, childWidth, childHeight); + + if (childFinal.IsEmpty) + { + child.Arrange(new Rect()); + } + else + { + child.Arrange(childFinal); + } + + arrangedWidth += childWidth; + arrangedHeight = Math.Max(arrangedHeight, childHeight); + } + } + + if (Orientation == Orientation.Vertical) + { + arrangedHeight = Math.Max(arrangedHeight, finalSize.Height); + } + else + { + arrangedWidth = Math.Max(arrangedWidth, finalSize.Width); + } + + return new Size(arrangedWidth, arrangedHeight); + } + } +} diff --git a/Perspex/Controls/TemplatedControl.cs b/Perspex/Controls/TemplatedControl.cs index 341d010a97..7f24a948f7 100644 --- a/Perspex/Controls/TemplatedControl.cs +++ b/Perspex/Controls/TemplatedControl.cs @@ -10,6 +10,7 @@ namespace Perspex.Controls using System.Collections.Generic; using System.Linq; using Perspex.Media; + using Splat; public class TemplatedControl : Control, IVisual, ITemplatedControl { @@ -37,6 +38,11 @@ namespace Perspex.Controls if (this.visualChild == null && template != null) { + this.Log().Debug(string.Format( + "Creating template for {0} (#{1:x8})", + this.GetType().Name, + this.GetHashCode())); + this.visualChild = template.Build(this); this.visualChild.VisualParent = this; } diff --git a/Perspex/Controls/TextBlock.cs b/Perspex/Controls/TextBlock.cs index cbae827ec3..3467a2dd35 100644 --- a/Perspex/Controls/TextBlock.cs +++ b/Perspex/Controls/TextBlock.cs @@ -14,6 +14,7 @@ namespace Perspex.Controls public static readonly PerspexProperty FontSizeProperty = PerspexProperty.Register( "FontSize", + defaultValue: 12.0, inherits: true); public static readonly PerspexProperty TextProperty = diff --git a/Perspex/Perspex.csproj b/Perspex/Perspex.csproj index cdf03ea9f8..db6c85fdff 100644 --- a/Perspex/Perspex.csproj +++ b/Perspex/Perspex.csproj @@ -70,6 +70,9 @@ + + + diff --git a/Perspex/Rect.cs b/Perspex/Rect.cs index 68fec6d443..3dd3db62ca 100644 --- a/Perspex/Rect.cs +++ b/Perspex/Rect.cs @@ -122,6 +122,14 @@ namespace Perspex get { return new Size(this.width, this.height); } } + /// + /// Gets a value that indicates whether the rectangle is empty. + /// + public bool IsEmpty + { + get { return this.width == 0 && this.height == 0; } + } + /// /// Determines whether a points in in the bounds of the rectangle. /// diff --git a/Perspex/Size.cs b/Perspex/Size.cs index 4976967384..6eee721332 100644 --- a/Perspex/Size.cs +++ b/Perspex/Size.cs @@ -88,6 +88,26 @@ namespace Perspex this.height + thickness.Top + thickness.Bottom); } + /// + /// Returns a new with the same height and the specified width. + /// + /// The width. + /// The new . + public Size WithWidth(double width) + { + return new Size(width, this.height); + } + + /// + /// Returns a new with the same width and the specified height. + /// + /// The height. + /// The new . + public Size WithHeight(double height) + { + return new Size(this.width, height); + } + /// /// Returns the string representation of the size. /// diff --git a/Perspex/Styling/StyleActivator.cs b/Perspex/Styling/StyleActivator.cs index 7c29abcd87..ae0d7fe24a 100644 --- a/Perspex/Styling/StyleActivator.cs +++ b/Perspex/Styling/StyleActivator.cs @@ -17,7 +17,7 @@ namespace Perspex.Styling Or, } - public class StyleActivator : IObservable + public class StyleActivator : IObservable, IDisposable { ActivatorMode mode; @@ -62,6 +62,19 @@ namespace Perspex.Styling private set; } + public void Dispose() + { + foreach (IObserver observer in this.observers) + { + observer.OnCompleted(); + } + + foreach (IDisposable subscription in this.subscriptions) + { + subscription.Dispose(); + } + } + public IDisposable Subscribe(IObserver observer) { Contract.Requires(observer != null); diff --git a/Perspex/Visual.cs b/Perspex/Visual.cs index f88c0e85e9..fb934b7d3d 100644 --- a/Perspex/Visual.cs +++ b/Perspex/Visual.cs @@ -12,6 +12,7 @@ namespace Perspex using Perspex.Controls; using Perspex.Layout; using Perspex.Media; + using Splat; public abstract class Visual : PerspexObject, IVisual { @@ -25,7 +26,7 @@ namespace Perspex IEnumerable IVisual.ExistingVisualChildren { - get { return Enumerable.Empty(); } + get { return ((IVisual)this).VisualChildren; } } IEnumerable IVisual.VisualChildren @@ -51,6 +52,11 @@ namespace Perspex if (this.GetVisualAncestor() != null) { + this.Log().Debug(string.Format( + "Attached {0} (#{1:x8}) to visual tree", + this.GetType().Name, + this.GetHashCode())); + this.AttachedToVisualTree(); } } diff --git a/Perspex/VisualExtensions.cs b/Perspex/VisualExtensions.cs index 29e9c1ca8f..265f91934f 100644 --- a/Perspex/VisualExtensions.cs +++ b/Perspex/VisualExtensions.cs @@ -7,10 +7,29 @@ namespace Perspex { using System; + using System.Collections.Generic; using System.Linq; + using Perspex.Styling; public static class VisualExtensions { + public static IEnumerable GetVisual(this IVisual visual, Func selector) + { + Selector sel = selector(new Selector()); + IEnumerable visuals = Enumerable.Repeat(visual, 1).Concat(visual.GetVisualDescendents()); + + foreach (IStyleable v in visuals.OfType()) + { + using (StyleActivator activator = sel.GetActivator(v)) + { + if (activator.CurrentValue) + { + yield return (IVisual)v; + } + } + } + } + public static T GetVisualAncestor(this IVisual visual) where T : class { Contract.Requires(visual != null); @@ -67,5 +86,18 @@ namespace Perspex return null; } + + public static IEnumerable GetVisualDescendents(this IVisual visual) + { + foreach (IVisual child in visual.VisualChildren) + { + yield return child; + + foreach (IVisual descendent in child.GetVisualDescendents()) + { + yield return descendent; + } + } + } } } diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index b770dca705..98e7c246c2 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -36,7 +36,9 @@ namespace TestApplication { static void Main(string[] args) { - Locator.CurrentMutable.Register(() => new TextService(new SharpDX.DirectWrite.Factory()), typeof(ITextService)); + TextService textService = new TextService(new SharpDX.DirectWrite.Factory()); + + Locator.CurrentMutable.Register(() => textService, typeof(ITextService)); Locator.CurrentMutable.Register(() => new Styler(), typeof(IStyler)); Locator.CurrentMutable.Register(() => new TestLogger(), typeof(ILogger)); @@ -50,15 +52,28 @@ namespace TestApplication Window window = new Window { - Content = new Button - { - Background = new SolidColorBrush(0xff0000ff), - Content = "Hello World", + Content = new StackPanel + { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - }, + Orientation = Orientation.Vertical, + Children = new PerspexList + { + new Button + { + Margin = new Thickness(2), + }, + new Button + { + Content = "Explict Background", + Background = new SolidColorBrush(0xffa0a0ff), + } + } + } }; + var m = window.GetVisual(x => x.OfType