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