// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Controls { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Linq; using Perspex.Input; using Perspex.Layout; using Perspex.Media; 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 static readonly PerspexProperty BackgroundProperty = PerspexProperty.Register("Background", inherits: true); public static readonly PerspexProperty BorderBrushProperty = PerspexProperty.Register("BorderBrush"); public static readonly PerspexProperty BorderThicknessProperty = PerspexProperty.Register("BorderThickness"); public static readonly PerspexProperty FocusableProperty = PerspexProperty.Register("Focusable"); public static readonly PerspexProperty FontSizeProperty = PerspexProperty.Register( "FontSize", defaultValue: 12.0, inherits: true); 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); public static readonly RoutedEvent LostFocusEvent = RoutedEvent.Register("LostFocus", RoutingStrategy.Bubble); public static readonly RoutedEvent KeyDownEvent = RoutedEvent.Register("KeyDown", RoutingStrategy.Bubble); public static readonly RoutedEvent PointerEnterEvent = RoutedEvent.Register("PointerEnter", RoutingStrategy.Direct); public static readonly RoutedEvent PointerLeaveEvent = RoutedEvent.Register("PointerLeave", RoutingStrategy.Direct); public static readonly RoutedEvent PointerPressedEvent = RoutedEvent.Register("PointerPressed", RoutingStrategy.Bubble); public static readonly RoutedEvent PointerReleasedEvent = RoutedEvent.Register("PointerReleased", RoutingStrategy.Bubble); private Classes classes; private string id; private Styles styles; public Control() { this.classes = new Classes(); this.GotFocus += (s, e) => this.IsFocused = true; this.LostFocus += (s, e) => this.IsFocused = false; this.PointerEnter += (s, e) => this.IsPointerOver = true; this.PointerLeave += (s, e) => this.IsPointerOver = false; this.AddPseudoClass(IsPointerOverProperty, ":pointerover"); this.AddPseudoClass(IsFocusedProperty, ":focus"); } public event EventHandler GotFocus { add { this.AddHandler(GotFocusEvent, value); } remove { this.RemoveHandler(GotFocusEvent, value); } } public event EventHandler LostFocus { add { this.AddHandler(LostFocusEvent, value); } remove { this.RemoveHandler(LostFocusEvent, value); } } public event EventHandler KeyDown { add { this.AddHandler(KeyDownEvent, value); } remove { this.RemoveHandler(KeyDownEvent, value); } } public event EventHandler PointerEnter { add { this.AddHandler(PointerEnterEvent, value); } remove { this.RemoveHandler(PointerEnterEvent, value); } } public event EventHandler PointerLeave { add { this.AddHandler(PointerLeaveEvent, value); } remove { this.RemoveHandler(PointerLeaveEvent, value); } } public event EventHandler PointerPressed { add { this.AddHandler(PointerPressedEvent, value); } remove { this.RemoveHandler(PointerPressedEvent, value); } } public event EventHandler PointerReleased { add { this.AddHandler(PointerReleasedEvent, value); } remove { this.RemoveHandler(PointerReleasedEvent, value); } } public Size ActualSize { get { return ((IVisual)this).Bounds.Size; } } public Brush Background { get { return this.GetValue(BackgroundProperty); } set { this.SetValue(BackgroundProperty, value); } } public Brush BorderBrush { get { return this.GetValue(BorderBrushProperty); } set { this.SetValue(BorderBrushProperty, value); } } public double BorderThickness { get { return this.GetValue(BorderThicknessProperty); } set { this.SetValue(BorderThicknessProperty, value); } } public Classes Classes { get { return this.classes; } set { if (this.classes != value) { this.classes.Clear(); this.classes.Add(value); } } } public Size? DesiredSize { get; set; } public double FontSize { get { return this.GetValue(FontSizeProperty); } set { this.SetValue(FontSizeProperty, value); } } public bool Focusable { get { return this.GetValue(FocusableProperty); } set { this.SetValue(FocusableProperty, value); } } public Brush Foreground { get { return this.GetValue(ForegroundProperty); } 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); } private set { this.SetValue(IsFocusedProperty, value); } } public string Id { get { return this.id; } set { if (this.id != null) { throw new InvalidOperationException("ID already set."); } if (((IVisual)this).VisualParent != null) { throw new InvalidOperationException("Cannot set ID : control already added to tree."); } this.id = value; } } public bool IsPointerOver { get { return this.GetValue(IsPointerOverProperty); } 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); } protected set { this.SetValue(ParentProperty, value); } } public Styles Styles { get { if (this.styles == null) { this.styles = new Styles(); } return this.styles; } set { this.styles = value; } } public ITemplatedControl TemplatedParent { get; 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; } set { this.Parent = (Control)value; } } IEnumerable ILogical.LogicalChildren { get { return Enumerable.Empty(); } } 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.InvalidateMeasure(this); } } protected void AddPseudoClass(PerspexProperty property, string className) { this.GetObservable(property).Subscribe(x => { if (x) { this.classes.Add(className); } else { this.classes.Remove(className); } }); } 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) { 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); } protected virtual Size MeasureOverride(Size availableSize) { return new Size(); } protected override void AttachedToVisualTree() { IStyler styler = Locator.Current.GetService(); styler.ApplyStyles(this); base.AttachedToVisualTree(); } } }