// ----------------------------------------------------------------------- // // 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 System.Reactive.Linq; using Perspex.Animation; using Perspex.Collections; using Perspex.Media; using Perspex.Platform; using Perspex.Rendering; using Perspex.VisualTree; using Serilog; using Serilog.Core.Enrichers; /// /// Base class for controls that provides rendering and related visual properties. /// /// /// The class acts as a node in the Perspex scene graph and holds /// all the information needed for an to render the control. /// To traverse the scene graph (aka Visual Tree), use the extension methods defined /// in . /// public class Visual : Animatable, IVisual { /// /// Defines the property. /// public static readonly PerspexProperty BoundsProperty = PerspexProperty.Register(nameof(Bounds)); /// /// Defines the property. /// public static readonly PerspexProperty ClipToBoundsProperty = PerspexProperty.Register(nameof(ClipToBounds)); /// /// Defines the property. /// public static readonly PerspexProperty IsVisibleProperty = PerspexProperty.Register(nameof(IsVisible), true); /// /// Defines the property. /// public static readonly PerspexProperty OpacityProperty = PerspexProperty.Register(nameof(Opacity), 1); /// /// Defines the property. /// public static readonly PerspexProperty RenderTransformProperty = PerspexProperty.Register(nameof(RenderTransform)); /// /// Defines the property. /// public static readonly PerspexProperty TransformOriginProperty = PerspexProperty.Register(nameof(TransformOrigin), defaultValue: Origin.Default); /// /// Defines the property. /// public static readonly PerspexProperty ZIndexProperty = PerspexProperty.Register(nameof(ZIndex)); /// /// Holds the children of the visual. /// private PerspexList visualChildren; /// /// Holds the parent of the visual. /// private Visual visualParent; /// /// The logger for visual-level events. /// private ILogger visualLogger; /// /// Initializes static members of the class. /// static Visual() { AffectsRender(IsVisibleProperty); AffectsRender(OpacityProperty); RenderTransformProperty.Changed.Subscribe(RenderTransformChanged); } /// /// Initializes a new instance of the class. /// public Visual() { this.visualLogger = Log.ForContext(new[] { new PropertyEnricher("Area", "Visual"), new PropertyEnricher("SourceContext", this.GetType()), new PropertyEnricher("Id", this.GetHashCode()), }); this.visualChildren = new PerspexList(); this.visualChildren.CollectionChanged += this.VisualChildrenChanged; } /// /// Gets the bounds of the scene graph node. /// public Rect Bounds { get { return this.GetValue(BoundsProperty); } protected set { this.SetValue(BoundsProperty, value); } } /// /// Gets a value indicating whether the scene graph node should be clipped to its bounds. /// public bool ClipToBounds { get { return this.GetValue(ClipToBoundsProperty); } set { this.SetValue(ClipToBoundsProperty, value); } } /// /// Gets a value indicating whether this scene graph node and all its parents are visible. /// public bool IsEffectivelyVisible { get { return this.GetSelfAndVisualAncestors().All(x => x.IsVisible); } } /// /// Gets a value indicating whether this scene graph node is visible. /// public bool IsVisible { get { return this.GetValue(IsVisibleProperty); } set { this.SetValue(IsVisibleProperty, value); } } /// /// Gets the opacity of the scene graph node. /// public double Opacity { get { return this.GetValue(OpacityProperty); } set { this.SetValue(OpacityProperty, value); } } /// /// Gets the render transform of the scene graph node. /// public Transform RenderTransform { get { return this.GetValue(RenderTransformProperty); } set { this.SetValue(RenderTransformProperty, value); } } /// /// Gets the transform origin of the scene graph node. /// public Origin TransformOrigin { get { return this.GetValue(TransformOriginProperty); } set { this.SetValue(TransformOriginProperty, value); } } /// /// Gets the Z index of the node. /// public int ZIndex { get { return this.GetValue(ZIndexProperty); } set { this.SetValue(ZIndexProperty, value); } } /// /// Gets the scene graph node's child nodes. /// IPerspexReadOnlyList IVisual.VisualChildren { get { return this.visualChildren; } } /// /// Gets the scene graph node's parent node. /// IVisual IVisual.VisualParent { get { return this.visualParent; } } /// /// Invalidates the visual and queues a repaint. /// public void InvalidateVisual() { IRenderRoot root = this.GetSelfAndVisualAncestors() .OfType() .FirstOrDefault(); if (root != null && root.RenderManager != null) { root.RenderManager.InvalidateRender(this); } } /// /// Renders the visual to a . /// /// The drawing context. public virtual void Render(IDrawingContext context) { Contract.Requires(context != null); } /// /// Converts a point from control coordinates to screen coordinates. /// /// The point to convert. /// The point in screen coordinates. public Point PointToScreen(Point point) { var p = GetOffsetFromRoot(this); return p.Item1.TranslatePointToScreen(point + p.Item2); } /// /// Returns a transform that transforms the visual's coordinates into the coordinates /// of the specified . /// /// The visual to translate the coordinates to. /// A containing the transform. public Matrix TransformToVisual(IVisual visual) { var thisOffset = GetOffsetFromRoot(this).Item2; var thatOffset = GetOffsetFromRoot(visual).Item2; return Matrix.Translation(-thatOffset) * Matrix.Translation(thisOffset); } /// /// Indicates that a property change should cause to be /// called. /// /// The property. /// /// This method should be called in a control's static constructor for each property /// on the control which when changed should cause a redraw. This is similar to WPF's /// FrameworkPropertyMetadata.AffectsRender flag. /// protected static void AffectsRender(PerspexProperty property) { property.Changed.Subscribe(AffectsRenderInvalidate); } /// /// Adds a visual child to the control. /// /// The child to add. protected void AddVisualChild(Visual visual) { Contract.Requires(visual != null); this.visualChildren.Add(visual); } /// /// Adds visual children to the control. /// /// The children to add. protected void AddVisualChildren(IEnumerable visuals) { Contract.Requires(visuals != null); this.visualChildren.AddRange(visuals); } /// /// Removes all visual children from the control. /// protected void ClearVisualChildren() { this.visualChildren.Clear(); } /// /// Removes a visual child from the control; /// /// The child to remove. protected void RemoveVisualChild(Visual visual) { Contract.Requires(visual != null); this.visualChildren.Remove(visual); } /// /// Removes a visual children from the control; /// /// The children to remove. protected void RemoveVisualChildren(IEnumerable visuals) { Contract.Requires(visuals != null); foreach (var v in visuals) { this.visualChildren.Remove(v); } } /// /// Called when the control is added to a visual tree. /// /// The root of the visual tree. protected virtual void OnAttachedToVisualTree(IRenderRoot root) { } /// /// Called when the control is removed from a visual tree. /// /// The root of the visual tree. protected virtual void OnDetachedFromVisualTree(IRenderRoot root) { } [Obsolete("Use OnAttachedToVisualTree instead")] protected virtual void OnVisualParentChanged(Visual oldParent) { } /// /// Called when a property changes that should invalidate the visual. /// /// The event args. private static void AffectsRenderInvalidate(PerspexPropertyChangedEventArgs e) { Visual visual = e.Sender as Visual; if (visual != null) { visual.InvalidateVisual(); } } /// /// Gets the root of the controls visual tree and the distance from the root. /// /// The visual. /// A tuple containing the root and the distance from the root private static Tuple GetOffsetFromRoot(IVisual v) { var result = new Vector(); while (!(v is IRenderRoot)) { result = new Vector(result.X + v.Bounds.X, result.Y + v.Bounds.Y); v = v.VisualParent; if (v == null) { throw new InvalidOperationException("Control is not attached to visual tree."); } } return Tuple.Create((IRenderRoot)v, result); } /// /// Called when a visual's changes. /// /// The event args. private static void RenderTransformChanged(PerspexPropertyChangedEventArgs e) { var sender = e.Sender as Visual; if (sender != null) { var oldValue = e.OldValue as Transform; var newValue = e.NewValue as Transform; if (oldValue != null) { oldValue.Changed -= sender.RenderTransformChanged; } if (newValue != null) { newValue.Changed += sender.RenderTransformChanged; } sender.InvalidateVisual(); } } /// /// Called when the event is fired. /// /// The sender. /// The event args. private void RenderTransformChanged(object sender, EventArgs e) { this.InvalidateVisual(); } /// /// Sets the visual parent of the Visual. /// /// The visual parent. 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); } } } /// /// Called when the collection changes. /// /// The sender. /// The event args. 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; } } /// /// Calls the method for this control /// and all of its visual descendents. /// /// The root of the visual tree. private void NotifyAttachedToVisualTree(IRenderRoot root) { this.visualLogger.Verbose("Attached to visual tree"); this.OnAttachedToVisualTree(root); if (this.visualChildren != null) { foreach (Visual child in this.visualChildren.OfType()) { child.NotifyAttachedToVisualTree(root); } } } /// /// Calls the method for this control /// and all of its visual descendents. /// /// The root of the visual tree. private void NotifyDetachedFromVisualTree(IRenderRoot root) { this.visualLogger.Verbose("Detached from visual tree"); this.OnDetachedFromVisualTree(root); if (this.visualChildren != null) { foreach (Visual child in this.visualChildren.OfType()) { child.NotifyDetachedFromVisualTree(root); } } } } }