// 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.Generic;
using System.Linq;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
///
/// A renderer which renders the state of the visual tree without an intermediate scene graph
/// representation.
///
///
/// The immediate renderer supports only clip-bound-based hit testing; a control's geometry is
/// not taken into account.
///
public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer
{
private readonly IVisual _root;
private readonly IRenderRoot _renderRoot;
private IRenderTarget _renderTarget;
///
/// Initializes a new instance of the class.
///
/// The control to render.
public ImmediateRenderer(IVisual root)
{
Contract.Requires(root != null);
_root = root;
_renderRoot = root as IRenderRoot;
}
///
public bool DrawFps { get; set; }
///
public bool DrawDirtyRects { get; set; }
///
public event EventHandler SceneInvalidated;
///
public void Paint(Rect rect)
{
if (_renderTarget == null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
try
{
using (var context = new DrawingContext(_renderTarget.CreateDrawingContext(this)))
{
context.PlatformImpl.Clear(Colors.Transparent);
using (context.PushTransformContainer())
{
Render(context, _root, _root.Bounds);
}
if (DrawDirtyRects)
{
var color = (uint)new Random().Next(0xffffff) | 0x44000000;
context.FillRectangle(
new SolidColorBrush(color),
rect);
}
if (DrawFps)
{
RenderFps(context.PlatformImpl, _root.Bounds, null);
}
}
}
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget.Dispose();
_renderTarget = null;
}
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
}
///
public void Resized(Size size)
{
}
///
/// Renders a visual to a render target.
///
/// The visual.
/// The render target.
public static void Render(IVisual visual, IRenderTarget target)
{
using (var renderer = new ImmediateRenderer(visual))
using (var context = new DrawingContext(target.CreateDrawingContext(renderer)))
{
renderer.Render(context, visual, visual.Bounds);
}
}
///
/// Renders a visual to a drawing context.
///
/// The visual.
/// The drawing context.
public static void Render(IVisual visual, DrawingContext context)
{
using (var renderer = new ImmediateRenderer(visual))
{
renderer.Render(context, visual, visual.Bounds);
}
}
///
public void AddDirty(IVisual visual)
{
if (visual.Bounds != Rect.Empty)
{
var m = visual.TransformToVisual(_root);
if (m.HasValue)
{
var bounds = new Rect(visual.Bounds.Size).TransformToAABB(m.Value);
//use transformedbounds as previous render state of the visual bounds
//so we can invalidate old and new bounds of a control in case it moved/shrinked
if (visual.TransformedBounds.HasValue)
{
var trb = visual.TransformedBounds.Value;
var trBounds = trb.Bounds.TransformToAABB(trb.Transform);
if (trBounds != bounds)
{
_renderRoot?.Invalidate(trBounds);
}
}
_renderRoot?.Invalidate(bounds);
}
}
}
///
/// Ends the operation of the renderer.
///
public void Dispose()
{
_renderTarget?.Dispose();
}
///
public IEnumerable HitTest(Point p, IVisual root, Func filter)
{
return HitTest(root, p, filter);
}
///
public void RecalculateChildren(IVisual visual) => AddDirty(visual);
///
public void Start()
{
}
///
public void Stop()
{
}
///
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
{
(brush.Visual as IVisualBrushInitialize)?.EnsureInitialized();
return brush.Visual?.Bounds.Size ?? Size.Empty;
}
///
void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
{
var visual = brush.Visual;
Render(new DrawingContext(context), visual, visual.Bounds);
}
private static void ClearTransformedBounds(IVisual visual)
{
foreach (var e in visual.GetSelfAndVisualDescendants())
{
visual.TransformedBounds = null;
}
}
private static Rect GetTransformedBounds(IVisual visual)
{
if (visual.RenderTransform == null)
{
return visual.Bounds;
}
else
{
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(visual.Bounds.Position + origin);
var m = (-offset) * visual.RenderTransform.Value * (offset);
return visual.Bounds.TransformToAABB(m);
}
}
private static IEnumerable HitTest(
IVisual visual,
Point p,
Func filter)
{
Contract.Requires(visual != null);
if (filter?.Invoke(visual) != false)
{
bool containsPoint = false;
if (visual is ICustomSimpleHitTest custom)
{
containsPoint = custom.HitTest(p);
}
else
{
containsPoint = visual.TransformedBounds?.Contains(p) == true;
}
if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Count > 0)
{
foreach (var child in visual.VisualChildren.SortByZIndex())
{
foreach (var result in HitTest(child, p, filter))
{
yield return result;
}
}
}
if (containsPoint)
{
yield return visual;
}
}
}
private void Render(DrawingContext context, IVisual visual, Rect clipRect)
{
var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
var bounds = new Rect(visual.Bounds.Size);
if (visual.IsVisible && opacity > 0)
{
var m = Matrix.CreateTranslation(visual.Bounds.Position);
var renderTransform = Matrix.Identity;
if (visual.RenderTransform != null)
{
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin);
renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
}
m = renderTransform * m;
if (clipToBounds)
{
if (visual.RenderTransform != null)
{
clipRect = new Rect(visual.Bounds.Size);
}
else
{
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size));
}
}
using (context.PushPostTransform(m))
using (context.PushOpacity(opacity))
using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState))
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState))
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState))
using (context.PushTransformContainer())
{
visual.Render(context);
#pragma warning disable 0618
var transformed =
new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform);
#pragma warning restore 0618
visual.TransformedBounds = transformed;
foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance))
{
var childBounds = GetTransformedBounds(child);
if (!child.ClipToBounds || clipRect.Intersects(childBounds))
{
var childClipRect = clipRect.Translate(-childBounds.Position);
Render(context, child, childClipRect);
}
else
{
ClearTransformedBounds(child);
}
}
}
}
if (!visual.IsVisible)
{
ClearTransformedBounds(visual);
}
}
}
}