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