// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex { using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using Perspex.Media; using Perspex.Rendering; using Splat; public class Visual : PerspexObject, IVisual { public static readonly PerspexProperty IsVisibleProperty = PerspexProperty.Register("IsVisible", true); public static readonly PerspexProperty OpacityProperty = PerspexProperty.Register("Opacity", 1); public static readonly PerspexProperty RenderTransformProperty = PerspexProperty.Register("RenderTransform"); public static readonly PerspexProperty TransformOriginProperty = PerspexProperty.Register("TransformOrigin", defaultValue: Origin.Default); private Rect bounds; private PerspexList visualChildren; private Visual visualParent; static Visual() { AffectsRender(IsVisibleProperty); } public bool IsVisible { get { return this.GetValue(IsVisibleProperty); } set { this.SetValue(IsVisibleProperty, value); } } public double Opacity { get { return this.GetValue(OpacityProperty); } set { this.SetValue(OpacityProperty, value); } } public ITransform RenderTransform { get { return this.GetValue(RenderTransformProperty); } set { this.SetValue(RenderTransformProperty, value); } } public Origin TransformOrigin { get { return this.GetValue(TransformOriginProperty); } set { this.SetValue(TransformOriginProperty, value); } } Rect IVisual.Bounds { get { return this.bounds; } } IEnumerable IVisual.VisualChildren { get { this.EnsureVisualChildrenCreated(); return this.visualChildren; } } IVisual IVisual.VisualParent { get { return this.visualParent; } } public void InvalidateVisual() { IRenderRoot root = this.GetSelfAndVisualAncestors() .OfType() .FirstOrDefault(); if (root != null && root.RenderManager != null) { root.RenderManager.InvalidateRender(this); } } public virtual void Render(IDrawingContext context) { Contract.Requires(context != null); } protected static void AffectsRender(PerspexProperty property) { property.Changed.Subscribe(AffectsRenderInvalidate); } protected void AddVisualChild(Visual visual) { Contract.Requires(visual != null); this.EnsureVisualChildrenCreated(); this.visualChildren.Add(visual); } protected void AddVisualChildren(IEnumerable visuals) { Contract.Requires(visuals != null); this.EnsureVisualChildrenCreated(); this.visualChildren.AddRange(visuals); } protected void ClearVisualChildren() { this.EnsureVisualChildrenCreated(); // TODO: Just call visualChildren.Clear() when we have a PerspexList that notifies of // the removed items. while (this.visualChildren.Count > 0) { this.visualChildren.RemoveAt(this.visualChildren.Count - 1); } } protected void RemoveVisualChild(Visual visual) { Contract.Requires(visual != null); this.EnsureVisualChildrenCreated(); this.visualChildren.Remove(visual); } protected void SetVisualBounds(Rect bounds) { this.bounds = bounds; } protected virtual void CreateVisualChildren() { } protected virtual void OnAttachedToVisualTree(IRenderRoot root) { } protected virtual void OnDetachedFromVisualTree(IRenderRoot oldRoot) { } protected virtual void OnVisualParentChanged(Visual oldParent) { } private static void AffectsRenderInvalidate(PerspexPropertyChangedEventArgs e) { Visual visual = e.Sender as Visual; if (visual != null) { visual.InvalidateVisual(); } } private void EnsureVisualChildrenCreated() { if (this.visualChildren == null) { this.visualChildren = new PerspexList(); this.visualChildren.CollectionChanged += VisualChildrenChanged; this.CreateVisualChildren(); } } private void SetVisualParent(Visual value) { if (this.visualParent != value) { var old = this.visualParent; var oldRoot = this.GetVisualAncestors().OfType().FirstOrDefault(); var newRoot = default(IRenderRoot); if (value != null) { newRoot = value.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); } this.visualParent = value; this.OnVisualParentChanged(old); if (oldRoot != null) { this.NotifyDetachedFromVisualTree(oldRoot); } if (newRoot != null) { this.NotifyAttachedToVisualTree(newRoot); } } } private void VisualChildrenChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (Visual v in e.NewItems) { v.InheritanceParent = this; v.SetVisualParent(this); } break; case NotifyCollectionChangedAction.Remove: foreach (Visual v in e.OldItems) { v.InheritanceParent = null; v.SetVisualParent(null); } break; } } private void NotifyAttachedToVisualTree(IRenderRoot root) { this.Log().Debug(string.Format( "Attached {0} (#{1:x8}) to visual tree", this.GetType().Name, this.GetHashCode())); this.OnAttachedToVisualTree(root); if (this.visualChildren != null) { foreach (Visual child in this.visualChildren.OfType()) { child.NotifyAttachedToVisualTree(root); } } } private void NotifyDetachedFromVisualTree(IRenderRoot oldRoot) { this.Log().Debug(string.Format( "Detached {0} (#{1:x8}) from visual tree", this.GetType().Name, this.GetHashCode())); this.OnDetachedFromVisualTree(oldRoot); if (this.visualChildren != null) { foreach (Visual child in this.visualChildren.OfType()) { child.NotifyDetachedFromVisualTree(oldRoot); } } } } }