diff --git a/Perspex.Windows/DrawingContext.cs b/Perspex.Windows/DrawingContext.cs index d10ab74e2a..048d3056c2 100644 --- a/Perspex.Windows/DrawingContext.cs +++ b/Perspex.Windows/DrawingContext.cs @@ -75,14 +75,17 @@ namespace Perspex.Windows /// The text. public void DrawText(Perspex.Media.Brush foreground, Rect rect, FormattedText text) { - using (SharpDX.Direct2D1.SolidColorBrush brush = this.Convert(foreground)) - using (SharpDX.DirectWrite.TextFormat format = TextService.Convert(this.directWriteFactory, text)) + if (!string.IsNullOrEmpty(text.Text)) { - this.renderTarget.DrawText( - text.Text, - format, - this.Convert(rect), - brush); + using (SharpDX.Direct2D1.SolidColorBrush brush = this.Convert(foreground)) + using (SharpDX.DirectWrite.TextFormat format = TextService.Convert(this.directWriteFactory, text)) + { + this.renderTarget.DrawText( + text.Text, + format, + this.Convert(rect), + brush); + } } } diff --git a/Perspex/Controls/Border.cs b/Perspex/Controls/Border.cs index e85de3a759..197ef18c28 100644 --- a/Perspex/Controls/Border.cs +++ b/Perspex/Controls/Border.cs @@ -14,27 +14,29 @@ namespace Perspex.Controls { public Border() { - // Hacky hack hack! Observable.Merge( this.GetObservable(BackgroundProperty), this.GetObservable(BorderBrushProperty)) - .Subscribe(_ => this.InvalidateArrange()); + .Subscribe(_ => this.InvalidateVisual()); } public override void Render(IDrawingContext context) { - Brush background = this.Background; - Brush borderBrush = this.BorderBrush; - double borderThickness = this.BorderThickness; - - if (background != null) + if (this.Visibility == Visibility.Visible) { - context.FillRectange(background, new Rect(this.Bounds.Size)); - } + Brush background = this.Background; + Brush borderBrush = this.BorderBrush; + double borderThickness = this.BorderThickness; - if (borderBrush != null && borderThickness > 0) - { - context.DrawRectange(new Pen(borderBrush, borderThickness), new Rect(this.Bounds.Size)); + if (background != null) + { + context.FillRectange(background, new Rect(this.Bounds.Size)); + } + + if (borderBrush != null && borderThickness > 0) + { + context.DrawRectange(new Pen(borderBrush, borderThickness), new Rect(this.Bounds.Size)); + } } } } diff --git a/Perspex/Controls/CheckBox.cs b/Perspex/Controls/CheckBox.cs new file mode 100644 index 0000000000..a78e5ab7ae --- /dev/null +++ b/Perspex/Controls/CheckBox.cs @@ -0,0 +1,12 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + public class CheckBox : ToggleButton + { + } +} diff --git a/Perspex/Controls/ContentControl.cs b/Perspex/Controls/ContentControl.cs index ece79b91f1..5a75bb8a28 100644 --- a/Perspex/Controls/ContentControl.cs +++ b/Perspex/Controls/ContentControl.cs @@ -65,17 +65,18 @@ namespace Perspex.Controls protected override Size MeasureContent(Size availableSize) { - Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control; - - if (child != null) - { - child.Measure(availableSize); - return child.DesiredSize.Value; - } - else + if (this.Visibility != Visibility.Collapsed) { - return new Size(); + Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control; + + if (child != null) + { + child.Measure(availableSize); + return child.DesiredSize.Value; + } } + + return new Size(); } } } diff --git a/Perspex/Controls/ContentPresenter.cs b/Perspex/Controls/ContentPresenter.cs index c0e7991049..373c024633 100644 --- a/Perspex/Controls/ContentPresenter.cs +++ b/Perspex/Controls/ContentPresenter.cs @@ -145,17 +145,18 @@ namespace Perspex.Controls protected override Size MeasureContent(Size availableSize) { - Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control; - - if (child != null) - { - child.Measure(availableSize); - return child.DesiredSize.Value; - } - else + if (this.Visibility != Visibility.Collapsed) { - return new Size(); + Control child = ((IVisual)this).VisualChildren.SingleOrDefault() as Control; + + if (child != null) + { + child.Measure(availableSize); + return child.DesiredSize.Value; + } } + + return new Size(); } } } diff --git a/Perspex/Controls/Decorator.cs b/Perspex/Controls/Decorator.cs index a4157fc422..b8d8963524 100644 --- a/Perspex/Controls/Decorator.cs +++ b/Perspex/Controls/Decorator.cs @@ -77,17 +77,18 @@ namespace Perspex.Controls protected override Size MeasureContent(Size availableSize) { - Control content = this.Content; - - if (content != null) - { - content.Measure(availableSize); - return content.DesiredSize.Value.Inflate(this.Padding); - } - else + if (this.Visibility != Visibility.Collapsed) { - return new Size(); + Control content = this.Content; + + if (content != null) + { + content.Measure(availableSize); + return content.DesiredSize.Value.Inflate(this.Padding); + } } + + return new Size(); } } } diff --git a/Perspex/Controls/StackPanel.cs b/Perspex/Controls/StackPanel.cs index 86888ad6ef..6d2ddc6404 100644 --- a/Perspex/Controls/StackPanel.cs +++ b/Perspex/Controls/StackPanel.cs @@ -40,56 +40,61 @@ namespace Perspex.Controls protected override Size MeasureContent(Size availableSize) { - double childAvailableWidth = double.PositiveInfinity; - double childAvailableHeight = double.PositiveInfinity; - - if (this.Orientation == Orientation.Vertical) + if (this.Visibility != Visibility.Collapsed) { - childAvailableWidth = availableSize.Width; + double childAvailableWidth = double.PositiveInfinity; + double childAvailableHeight = double.PositiveInfinity; - if (!double.IsNaN(this.Width)) + if (this.Orientation == Orientation.Vertical) { - childAvailableWidth = this.Width; - } + childAvailableWidth = availableSize.Width; - childAvailableWidth = Math.Min(childAvailableWidth, this.MaxWidth); - childAvailableWidth = Math.Max(childAvailableWidth, this.MinWidth); - } - else - { - childAvailableHeight = availableSize.Height; + if (!double.IsNaN(this.Width)) + { + childAvailableWidth = this.Width; + } - if (!double.IsNaN(this.Height)) - { - childAvailableHeight = this.Height; + childAvailableWidth = Math.Min(childAvailableWidth, this.MaxWidth); + childAvailableWidth = Math.Max(childAvailableWidth, this.MinWidth); } + else + { + childAvailableHeight = availableSize.Height; - childAvailableHeight = Math.Min(childAvailableHeight, this.MaxHeight); - childAvailableHeight = Math.Max(childAvailableHeight, this.MinHeight); - } + if (!double.IsNaN(this.Height)) + { + childAvailableHeight = this.Height; + } - double measuredWidth = 0; - double measuredHeight = 0; - double gap = this.Gap; + childAvailableHeight = Math.Min(childAvailableHeight, this.MaxHeight); + childAvailableHeight = Math.Max(childAvailableHeight, this.MinHeight); + } - foreach (Control child in this.Children) - { - child.Measure(new Size(childAvailableWidth, childAvailableHeight)); - Size size = child.DesiredSize.Value; + double measuredWidth = 0; + double measuredHeight = 0; + double gap = this.Gap; - if (Orientation == Orientation.Vertical) - { - measuredHeight += size.Height + gap; - measuredWidth = Math.Max(measuredWidth, size.Width); - } - else + foreach (Control child in this.Children) { - measuredWidth += size.Width + gap; - measuredHeight = Math.Max(measuredHeight, size.Height); + child.Measure(new Size(childAvailableWidth, childAvailableHeight)); + Size size = child.DesiredSize.Value; + + if (Orientation == Orientation.Vertical) + { + measuredHeight += size.Height + gap; + measuredWidth = Math.Max(measuredWidth, size.Width); + } + else + { + measuredWidth += size.Width + gap; + measuredHeight = Math.Max(measuredHeight, size.Height); + } } + + return new Size(measuredWidth, measuredHeight); } - return new Size(measuredWidth, measuredHeight); + return new Size(); } protected override Size ArrangeContent(Size finalSize) diff --git a/Perspex/Controls/TextBlock.cs b/Perspex/Controls/TextBlock.cs index 3467a2dd35..ec387f93b1 100644 --- a/Perspex/Controls/TextBlock.cs +++ b/Perspex/Controls/TextBlock.cs @@ -6,6 +6,7 @@ namespace Perspex.Controls { + using System; using Perspex.Media; using Splat; @@ -20,6 +21,11 @@ namespace Perspex.Controls public static readonly PerspexProperty TextProperty = PerspexProperty.Register("Text"); + public TextBlock() + { + this.GetObservable(TextProperty).Subscribe(_ => this.InvalidateVisual()); + } + public double FontSize { get { return this.GetValue(FontSizeProperty); } @@ -47,20 +53,32 @@ namespace Perspex.Controls public override void Render(IDrawingContext context) { - Brush background = this.Background; - - if (background != null) + if (this.Visibility == Visibility.Visible) { - context.FillRectange(background, this.Bounds); - } + Brush background = this.Background; + + if (background != null) + { + context.FillRectange(background, this.Bounds); + } - context.DrawText(this.Foreground, new Rect(this.Bounds.Size), this.FormattedText); + context.DrawText(this.Foreground, new Rect(this.Bounds.Size), this.FormattedText); + } } protected override Size MeasureContent(Size availableSize) { - ITextService service = Locator.Current.GetService(); - return service.Measure(this.FormattedText); + if (this.Visibility != Visibility.Collapsed) + { + ITextService service = Locator.Current.GetService(); + + if (!string.IsNullOrEmpty(this.Text)) + { + return service.Measure(this.FormattedText); + } + } + + return new Size(); } } } diff --git a/Perspex/Controls/ToggleButton.cs b/Perspex/Controls/ToggleButton.cs new file mode 100644 index 0000000000..c26a6517e1 --- /dev/null +++ b/Perspex/Controls/ToggleButton.cs @@ -0,0 +1,39 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + + public class ToggleButton : Button + { + public static readonly PerspexProperty IsCheckedProperty = + PerspexProperty.Register("IsChecked"); + + public ToggleButton() + { + this.Click += (s, e) => this.IsChecked = !this.IsChecked; + + this.GetObservable(IsCheckedProperty).Subscribe(x => + { + if (x) + { + this.Classes.Add(":checked"); + } + else + { + this.Classes.Remove(":checked"); + } + }); + } + + public bool IsChecked + { + get { return this.GetValue(IsCheckedProperty); } + set { this.SetValue(IsCheckedProperty, value); } + } + } +} diff --git a/Perspex/Perspex.csproj b/Perspex/Perspex.csproj index a020101356..ab1013d402 100644 --- a/Perspex/Perspex.csproj +++ b/Perspex/Perspex.csproj @@ -70,6 +70,8 @@ + + @@ -131,6 +133,7 @@ + diff --git a/Perspex/Styling/Selectors.cs b/Perspex/Styling/Selectors.cs index a41529e48d..ed0a9e0685 100644 --- a/Perspex/Styling/Selectors.cs +++ b/Perspex/Styling/Selectors.cs @@ -71,7 +71,7 @@ namespace Perspex.Styling return new Selector(previous) { - GetObservable = control => Observable.Return(control is T), + GetObservable = control => Observable.Return(control.GetType() == typeof(T)), SelectorString = typeof(T).Name, }; } diff --git a/Perspex/Themes/Default/ButtonStyle.cs b/Perspex/Themes/Default/ButtonStyle.cs index 28036ec36d..d2d554fab4 100644 --- a/Perspex/Themes/Default/ButtonStyle.cs +++ b/Perspex/Themes/Default/ButtonStyle.cs @@ -61,15 +61,15 @@ namespace Perspex.Themes.Default private Control Template(Button control) { - Border border = new Border(); - border.Id = "border"; - border.Padding = new Thickness(3); - border.TemplateBinding(control, Border.BackgroundProperty); - - ContentPresenter contentPresenter = new ContentPresenter(); - contentPresenter.TemplateBinding(control, ContentPresenter.ContentProperty); + Border border = new Border + { + Id = "border", + Padding = new Thickness(3), + Content = new ContentPresenter(), + }; - border.Content = contentPresenter; + border.TemplateBinding(control, Border.BackgroundProperty); + border.Content.TemplateBinding(control, ContentPresenter.ContentProperty); return border; } } diff --git a/Perspex/Themes/Default/CheckBoxStyle.cs b/Perspex/Themes/Default/CheckBoxStyle.cs new file mode 100644 index 0000000000..b419ce5576 --- /dev/null +++ b/Perspex/Themes/Default/CheckBoxStyle.cs @@ -0,0 +1,80 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Themes.Default +{ + using System.Linq; + using Perspex.Controls; + using Perspex.Media; + using Perspex.Styling; + + public class CheckBoxStyle : Styles + { + public CheckBoxStyle() + { + this.AddRange(new[] + { + new Style(x => x.OfType()) + { + Setters = new[] + { + new Setter(Button.TemplateProperty, ControlTemplate.Create(this.Template)), + }, + }, + new Style(x => x.OfType().Template().Id("checkMark")) + { + Setters = new[] + { + new Setter(TextBlock.VisibilityProperty, Visibility.Hidden), + }, + }, + new Style(x => x.OfType().Class(":checked").Template().Id("checkMark")) + { + Setters = new[] + { + new Setter(TextBlock.VisibilityProperty, Visibility.Visible), + }, + }, + }); + } + + private Control Template(CheckBox control) + { + Border result = new Border + { + Content = new StackPanel + { + Orientation = Orientation.Horizontal, + Gap = 8, + Children = new PerspexList + { + new Border + { + BorderThickness = 2.0, + BorderBrush = new SolidColorBrush(0xff000000), + Padding = new Thickness(2), + Content = new TextBlock + { + Id = "checkMark", + Text = "Y", + Background = null, + }, + }, + new ContentPresenter + { + }, + }, + }, + }; + + result.TemplateBinding(control, Border.BackgroundProperty); + StackPanel stack = (StackPanel)result.Content; + ContentPresenter cp = (ContentPresenter)stack.Children[1]; + cp.TemplateBinding(control, ContentPresenter.ContentProperty); + return result; + } + } +} diff --git a/Perspex/Themes/Default/DefaultTheme.cs b/Perspex/Themes/Default/DefaultTheme.cs index e0d94d0403..e092d9975a 100644 --- a/Perspex/Themes/Default/DefaultTheme.cs +++ b/Perspex/Themes/Default/DefaultTheme.cs @@ -13,6 +13,7 @@ namespace Perspex.Themes.Default public DefaultTheme() { this.Add(new ButtonStyle()); + this.Add(new CheckBoxStyle()); } } } diff --git a/Perspex/Visual.cs b/Perspex/Visual.cs index fb934b7d3d..8fb61289da 100644 --- a/Perspex/Visual.cs +++ b/Perspex/Visual.cs @@ -14,16 +14,37 @@ namespace Perspex using Perspex.Media; using Splat; + public enum Visibility + { + Visible, + Hidden, + Collapsed, + } + public abstract class Visual : PerspexObject, IVisual { + public static readonly PerspexProperty VisibilityProperty = + PerspexProperty.Register("Visibility"); + private IVisual visualParent; + public Visual() + { + this.GetObservable(VisibilityProperty).Subscribe(_ => this.InvalidateVisual()); + } + public Rect Bounds { get; protected set; } + public Visibility Visibility + { + get { return this.GetValue(VisibilityProperty); } + set { this.SetValue(VisibilityProperty, value); } + } + IEnumerable IVisual.ExistingVisualChildren { get { return ((IVisual)this).VisualChildren; } @@ -52,17 +73,23 @@ 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(); } } } } + public void InvalidateVisual() + { + ILayoutRoot root = this.GetVisualAncestorOrSelf(); + + if (root != null && root.LayoutManager != null) + { + // HACK HACK HACK! + root.LayoutManager.InvalidateArrange((ILayoutable)this); + } + } + public virtual void Render(IDrawingContext context) { Contract.Requires(context != null); @@ -70,6 +97,11 @@ namespace Perspex protected virtual void AttachedToVisualTree() { + this.Log().Debug(string.Format( + "Attached {0} (#{1:x8}) to visual tree", + this.GetType().Name, + this.GetHashCode())); + foreach (Visual child in ((IVisual)this).ExistingVisualChildren.OfType()) { child.AttachedToVisualTree(); diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index 1b45b30206..13acad45ca 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -71,7 +71,11 @@ namespace TestApplication { Content = "Explict Background", Background = new SolidColorBrush(0xffa0a0ff), - } + }, + new CheckBox + { + Content = "Checkbox", + }, } } };