// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Specialized; using System.Linq; using System.Reactive.Linq; using Avalonia.Animation; using Avalonia.Collections; using Avalonia.Data; using Avalonia.Logging; using Avalonia.Media; using Avalonia.Rendering; using Avalonia.VisualTree; namespace Avalonia { /// /// Base class for controls that provides rendering and related visual properties. /// /// /// The class represents elements that have a visual on-screen /// representation and stores all the information needed for an /// to render the control. To traverse the visual tree, use the /// extension methods defined in . /// public class Visual : Animatable, IVisual { /// /// Defines the property. /// public static readonly DirectProperty BoundsProperty = AvaloniaProperty.RegisterDirect(nameof(Bounds), o => o.Bounds); /// /// Defines the property. /// public static readonly StyledProperty ClipToBoundsProperty = AvaloniaProperty.Register(nameof(ClipToBounds)); /// /// Defines the property. /// public static readonly StyledProperty ClipProperty = AvaloniaProperty.Register(nameof(Clip)); /// /// Defines the property. /// public static readonly StyledProperty IsVisibleProperty = AvaloniaProperty.Register(nameof(IsVisible), true); /// /// Defines the property. /// public static readonly StyledProperty OpacityProperty = AvaloniaProperty.Register(nameof(Opacity), 1); /// /// Defines the property. /// public static readonly StyledProperty OpacityMaskProperty = AvaloniaProperty.Register(nameof(OpacityMask)); /// /// Defines the property. /// public static readonly StyledProperty RenderTransformProperty = AvaloniaProperty.Register(nameof(RenderTransform)); /// /// Defines the property. /// public static readonly StyledProperty RenderTransformOriginProperty = AvaloniaProperty.Register(nameof(RenderTransformOrigin), defaultValue: RelativePoint.Center); /// /// Defines the property. /// public static readonly DirectProperty VisualParentProperty = AvaloniaProperty.RegisterDirect("VisualParent", o => o._visualParent); /// /// Defines the property. /// public static readonly StyledProperty ZIndexProperty = AvaloniaProperty.Register(nameof(ZIndex)); private Rect _bounds; private IRenderRoot _visualRoot; private IVisual _visualParent; /// /// Initializes static members of the class. /// static Visual() { AffectsRender( BoundsProperty, ClipProperty, ClipToBoundsProperty, IsVisibleProperty, OpacityProperty); RenderTransformProperty.Changed.Subscribe(RenderTransformChanged); } /// /// Initializes a new instance of the class. /// public Visual() { var visualChildren = new AvaloniaList(); visualChildren.ResetBehavior = ResetBehavior.Remove; visualChildren.Validate = ValidateVisualChild; visualChildren.CollectionChanged += VisualChildrenChanged; VisualChildren = visualChildren; } /// /// Raised when the control is attached to a rooted visual tree. /// public event EventHandler AttachedToVisualTree; /// /// Raised when the control is detached from a rooted visual tree. /// public event EventHandler DetachedFromVisualTree; /// /// Gets the bounds of the control relative to its parent. /// public Rect Bounds { get { return _bounds; } protected set { SetAndRaise(BoundsProperty, ref _bounds, value); } } /// /// Gets a value indicating whether the control should be clipped to its bounds. /// public bool ClipToBounds { get { return GetValue(ClipToBoundsProperty); } set { SetValue(ClipToBoundsProperty, value); } } /// /// Gets or sets the geometry clip for this visual. /// public Geometry Clip { get { return GetValue(ClipProperty); } set { SetValue(ClipProperty, value); } } /// /// Gets a value indicating whether this control and all its parents are visible. /// public bool IsEffectivelyVisible { get { return this.GetSelfAndVisualAncestors().All(x => x.IsVisible); } } /// /// Gets a value indicating whether this control is visible. /// public bool IsVisible { get { return GetValue(IsVisibleProperty); } set { SetValue(IsVisibleProperty, value); } } /// /// Gets the opacity of the control. /// public double Opacity { get { return GetValue(OpacityProperty); } set { SetValue(OpacityProperty, value); } } /// /// Gets the opacity mask of the control. /// public IBrush OpacityMask { get { return GetValue(OpacityMaskProperty); } set { SetValue(OpacityMaskProperty, value); } } /// /// Gets the render transform of the control. /// public Transform RenderTransform { get { return GetValue(RenderTransformProperty); } set { SetValue(RenderTransformProperty, value); } } /// /// Gets the transform origin of the control. /// public RelativePoint RenderTransformOrigin { get { return GetValue(RenderTransformOriginProperty); } set { SetValue(RenderTransformOriginProperty, value); } } /// /// Gets the Z index of the control. /// /// /// Controls with a higher will appear in front of controls with /// a lower ZIndex. If two controls have the same ZIndex then the control that appears /// later in the containing element's children collection will appear on top. /// public int ZIndex { get { return GetValue(ZIndexProperty); } set { SetValue(ZIndexProperty, value); } } /// /// Gets the control's child visuals. /// protected IAvaloniaList VisualChildren { get; private set; } /// /// Gets the root of the visual tree, if the control is attached to a visual tree. /// protected IRenderRoot VisualRoot => _visualRoot ?? (this as IRenderRoot); /// /// Gets a value indicating whether this control is attached to a visual root. /// bool IVisual.IsAttachedToVisualTree => VisualRoot != null; /// /// Gets the control's child controls. /// IAvaloniaReadOnlyList IVisual.VisualChildren => VisualChildren; /// /// Gets the control's parent visual. /// IVisual IVisual.VisualParent => _visualParent; /// /// Gets the root of the visual tree, if the control is attached to a visual tree. /// IRenderRoot IVisual.VisualRoot => VisualRoot; /// /// Invalidates the visual and queues a repaint. /// public void InvalidateVisual() { VisualRoot?.Renderer?.AddDirty(this); } /// /// Renders the visual to a . /// /// The drawing context. public virtual void Render(DrawingContext context) { Contract.Requires(context != null); } /// /// 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 or null if the visuals don't share a /// common ancestor. /// public Matrix? TransformToVisual(IVisual visual) { var common = this.FindCommonVisualAncestor(visual); if (common != null) { var thisOffset = GetOffsetFrom(common, this); var thatOffset = GetOffsetFrom(common, visual); return Matrix.CreateTranslation(-thatOffset) * Matrix.CreateTranslation(thisOffset); } return null; } /// /// Indicates that a property change should cause to be /// called. /// /// The properties. /// /// This method should be called in a control's static constructor with 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(params AvaloniaProperty[] properties) { foreach (var property in properties) { property.Changed.Subscribe(AffectsRenderInvalidate); } } /// /// Calls the method /// for this control and all of its visual descendents. /// /// The event args. protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { Logger.Verbose(LogArea.Visual, this, "Attached to visual tree"); _visualRoot = e.Root; if (RenderTransform != null) { RenderTransform.Changed += RenderTransformChanged; } OnAttachedToVisualTree(e); InvalidateVisual(); if (VisualChildren != null) { foreach (Visual child in VisualChildren.OfType()) { child.OnAttachedToVisualTreeCore(e); } } } /// /// Calls the method /// for this control and all of its visual descendents. /// /// The event args. protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { Logger.Verbose(LogArea.Visual, this, "Detached from visual tree"); _visualRoot = null; if (RenderTransform != null) { RenderTransform.Changed -= RenderTransformChanged; } OnDetachedFromVisualTree(e); e.Root?.Renderer?.AddDirty(this); if (VisualChildren != null) { foreach (Visual child in VisualChildren.OfType()) { child.OnDetachedFromVisualTreeCore(e); } } } /// /// Called when the control is added to a visual tree. /// /// The event args. protected virtual void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { AttachedToVisualTree?.Invoke(this, e); } /// /// Called when the control is removed from a visual tree. /// /// The event args. protected virtual void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { DetachedFromVisualTree?.Invoke(this, e); } /// /// Called when the control's visual parent changes. /// /// The old visual parent. /// The new visual parent. protected virtual void OnVisualParentChanged(IVisual oldParent, IVisual newParent) { RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue); } /// /// Called when a property changes that should invalidate the visual. /// /// The event args. private static void AffectsRenderInvalidate(AvaloniaPropertyChangedEventArgs e) { (e.Sender as Visual)?.InvalidateVisual(); } /// /// Gets the visual offset from the specified ancestor. /// /// The ancestor visual. /// The visual. /// The visual offset. private static Vector GetOffsetFrom(IVisual ancestor, IVisual visual) { var result = new Vector(); while (visual != ancestor) { result = new Vector(result.X + visual.Bounds.X, result.Y + visual.Bounds.Y); visual = visual.VisualParent; if (visual == null) { throw new ArgumentException("'visual' is not a descendent of 'ancestor'."); } } return result; } /// /// Called when a visual's changes. /// /// The event args. private static void RenderTransformChanged(AvaloniaPropertyChangedEventArgs e) { var sender = e.Sender as Visual; if (sender?.VisualRoot != 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(); } } /// /// Ensures a visual child is not null and not already parented. /// /// The visual child. private static void ValidateVisualChild(IVisual c) { if (c == null) { throw new ArgumentNullException("Cannot add null to VisualChildren."); } if (c.VisualParent != null) { throw new InvalidOperationException("The control already has a visual parent."); } } /// /// Called when the 's event /// is fired. /// /// The sender. /// The event args. private void RenderTransformChanged(object sender, EventArgs e) { InvalidateVisual(); } /// /// Sets the visual parent of the Visual. /// /// The visual parent. private void SetVisualParent(Visual value) { if (_visualParent == value) { return; } var old = _visualParent; _visualParent = value; if (_visualRoot != null) { var e = new VisualTreeAttachmentEventArgs(old, VisualRoot); OnDetachedFromVisualTreeCore(e); } if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true) { var root = this.GetVisualAncestors().OfType().FirstOrDefault(); var e = new VisualTreeAttachmentEventArgs(_visualParent, root); OnAttachedToVisualTreeCore(e); } OnVisualParentChanged(old, value); } /// /// 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.SetVisualParent(this); } break; case NotifyCollectionChangedAction.Remove: foreach (Visual v in e.OldItems) { v.SetVisualParent(null); } break; } } } }