// 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;
}
}
}
}