Ported ImmediateRenderer from scenegraph branch.

This commit is contained in:
Steven Kirk 2017-04-01 20:12:14 +02:00
Родитель efce48b604
Коммит 1b9d61416f
56 изменённых файлов: 1736 добавлений и 1509 удалений

Просмотреть файл

@ -7,7 +7,6 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using RenderTest.ViewModels;
using ReactiveUI;
using Avalonia.Rendering;
namespace RenderTest
{
@ -19,7 +18,8 @@ namespace RenderTest
this.AttachDevTools();
var vm = new MainWindowViewModel();
vm.WhenAnyValue(x => x.DrawFps).Subscribe(x => RendererMixin.DrawFpsCounter = x);
vm.WhenAnyValue(x => x.DrawDirtyRects).Subscribe(x => Renderer.DrawDirtyRects = x);
vm.WhenAnyValue(x => x.DrawFps).Subscribe(x => Renderer.DrawFps = x);
this.DataContext = vm;
}

Просмотреть файл

@ -17,7 +17,9 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -33,7 +35,7 @@ namespace Avalonia.Controls
/// - Implements <see cref="IStyleable"/> to allow styling to work on the control.
/// - Implements <see cref="ILogical"/> to form part of a logical tree.
/// </remarks>
public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize
public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize, IVisualBrushInitialize
{
/// <summary>
/// Defines the <see cref="DataContext"/> property.
@ -460,6 +462,38 @@ namespace Avalonia.Controls
InheritanceParent = parent;
}
/// <inheritdoc/>
void IVisualBrushInitialize.EnsureInitialized()
{
if (VisualRoot == null)
{
if (!IsInitialized)
{
foreach (var i in this.GetSelfAndVisualDescendents())
{
var c = i as IControl;
if (c?.IsInitialized == false)
{
var init = c as ISupportInitialize;
if (init != null)
{
init.BeginInit();
init.EndInit();
}
}
}
}
if (!IsArrangeValid)
{
Measure(Size.Infinity);
Arrange(new Rect(DesiredSize));
}
}
}
/// <summary>
/// Adds a pseudo-class to be set when a property is true.
/// </summary>

Просмотреть файл

@ -96,10 +96,10 @@ namespace Avalonia.Controls
PlatformImpl.Closed = HandleClosed;
PlatformImpl.Input = HandleInput;
PlatformImpl.Paint = Renderer != null ? (Action<Rect>)Renderer.Render : null;
PlatformImpl.Paint = HandlePaint;
PlatformImpl.Resized = HandleResized;
PlatformImpl.ScalingChanged = HandleScalingChanged;
_keyboardNavigationHandler?.SetOwner(this);
_accessKeyHandler?.SetOwner(this);
@ -208,6 +208,15 @@ namespace Avalonia.Controls
return PlatformImpl.PointToScreen(p);
}
/// <summary>
/// Handles a paint notification from <see cref="ITopLevelImpl.Resized"/>.
/// </summary>
/// <param name="rect">The dirty area.</param>
protected virtual void HandlePaint(Rect rect)
{
Renderer?.Paint(rect);
}
/// <summary>
/// Handles a closed notification from <see cref="ITopLevelImpl.Closed"/>.
/// </summary>
@ -229,7 +238,7 @@ namespace Avalonia.Controls
Width = clientSize.Width;
Height = clientSize.Height;
LayoutManager.Instance.ExecuteLayoutPass();
PlatformImpl.Invalidate(new Rect(clientSize));
Renderer?.Resized(clientSize);
}
/// <summary>

Просмотреть файл

@ -16,10 +16,12 @@ namespace Avalonia.Media.Imaging
/// <summary>
/// Initializes a new instance of the <see cref="RenderTargetBitmap"/> class.
/// </summary>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
public RenderTargetBitmap(int width, int height)
: base(CreateImpl(width, height))
/// <param name="pixelWidth">The width of the bitmap in pixels.</param>
/// <param name="pixelHeight">The height of the bitmap in pixels.</param>
/// <param name="dpiX">The horizontal DPI of the bitmap.</param>
/// <param name="dpiY">The vertical DPI of the bitmap.</param>
public RenderTargetBitmap(int pixelWidth, int pixelHeight, double dpiX = 96, double dpiY = 96)
: base(CreateImpl(pixelWidth, pixelHeight, dpiX, dpiY))
{
}
@ -36,18 +38,27 @@ namespace Avalonia.Media.Imaging
PlatformImpl.Dispose();
}
/// <summary>
/// Renders a visual to the <see cref="RenderTargetBitmap"/>.
/// </summary>
/// <param name="visual">The visual to render.</param>
public void Render(IVisual visual) => ImmediateRenderer.Render(visual, this);
/// <summary>
/// Creates a platform-specific imlementation for a <see cref="RenderTargetBitmap"/>.
/// </summary>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="dpiX">The horizontal DPI of the bitmap.</param>
/// <param name="dpiY">The vertical DPI of the bitmap.</param>
/// <returns>The platform-specific implementation.</returns>
private static IBitmapImpl CreateImpl(int width, int height)
private static IBitmapImpl CreateImpl(int width, int height, double dpiX, double dpiY)
{
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return factory.CreateRenderTargetBitmap(width, height);
return factory.CreateRenderTargetBitmap(width, height, dpiX, dpiY);
}
public DrawingContext CreateDrawingContext() => PlatformImpl.CreateDrawingContext();
/// <inheritdoc/>
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer vbr) => PlatformImpl.CreateDrawingContext(vbr);
}
}

Просмотреть файл

@ -51,8 +51,14 @@ namespace Avalonia.Platform
/// </summary>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="dpiX">The horizontal DPI of the bitmap.</param>
/// <param name="dpiY">The vertical DPI of the bitmap.</param>
/// <returns>An <see cref="IRenderTargetBitmapImpl"/>.</returns>
IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height);
IRenderTargetBitmapImpl CreateRenderTargetBitmap(
int width,
int height,
double dpiX,
double dpiY);
/// <summary>
/// Creates a writable bitmap implementation.

Просмотреть файл

@ -2,7 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Media;
using Avalonia.Rendering;
namespace Avalonia.Platform
{
@ -15,8 +15,12 @@ namespace Avalonia.Platform
public interface IRenderTarget : IDisposable
{
/// <summary>
/// Creates an <see cref="DrawingContext"/> for a rendering session.
/// Creates an <see cref="IDrawingContextImpl"/> for a rendering session.
/// </summary>
DrawingContext CreateDrawingContext();
/// <param name="visualBrushRenderer">
/// A render to be used to render visual brushes. May be null if no visual brushes are
/// to be drawn.
/// </param>
IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer);
}
}

Просмотреть файл

@ -3,13 +3,50 @@
using System;
using Avalonia.VisualTree;
using System.Collections.Generic;
namespace Avalonia.Rendering
{
/// <summary>
/// Defines the interface for a renderer.
/// </summary>
public interface IRenderer : IDisposable
{
/// <summary>
/// Gets or sets a value indicating whether the renderer should draw an FPS counter.
/// </summary>
bool DrawFps { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the renderer should a visual representation
/// of its dirty rectangles.
/// </summary>
bool DrawDirtyRects { get; set; }
/// <summary>
/// Mark a visual as dirty and needing re-rendering.
/// </summary>
/// <param name="visual">The visual.</param>
void AddDirty(IVisual visual);
void Render(Rect rect);
/// <summary>
/// Hit tests a location to find the visuals at the specified point.
/// </summary>
/// <param name="p">The point, in client coordinates.</param>
/// <param name="filter">An optional filter.</param>
/// <returns>The visuals at the specified point, topmost first.</returns>
IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter);
/// <summary>
/// Called when a resize notification is received by the control being rendered.
/// </summary>
/// <param name="size">The new size of the window.</param>
void Resized(Size size);
/// <summary>
/// Called when a paint notification is received by the control being rendered.
/// </summary>
/// <param name="rect">The dirty rectangle.</param>
void Paint(Rect rect);
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// 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 Avalonia.Media;
namespace Avalonia.Rendering
{
/// <summary>
/// Internal interface for initializing controls that are to be used as the viusal in a
/// <see cref="VisualBrush"/>.
/// </summary>
public interface IVisualBrushInitialize
{
/// <summary>
/// Ensures that the control is ready to use as the visual in a visual brush.
/// </summary>
void EnsureInitialized();
}
}

Просмотреть файл

@ -0,0 +1,31 @@
// 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 Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.Rendering
{
/// <summary>
/// Defines a renderer used to render a visual brush to a bitmap.
/// </summary>
public interface IVisualBrushRenderer
{
/// <summary>
/// Gets the size of the intermediate render target to which the visual brush should be
/// drawn.
/// </summary>
/// <param name="brush">The visual brush.</param>
/// <returns>The size of the intermediate render target to create.</returns>
Size GetRenderTargetSize(IVisualBrush brush);
/// <summary>
/// Renders a visual brush to a bitmap.
/// </summary>
/// <param name="context">The drawing context to render to.</param>
/// <param name="brush">The visual brush.</param>
/// <returns>A bitmap containing the rendered brush.</returns>
void RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush);
}
}

Просмотреть файл

@ -0,0 +1,278 @@
// 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 Avalonia.Platform;
using Avalonia.VisualTree;
using System.Collections.Generic;
using Avalonia.Media;
using System.Linq;
namespace Avalonia.Rendering
{
/// <summary>
/// A renderer which renders the state of the visual tree without an intermediate scene graph
/// representation.
/// </summary>
/// <remarks>
/// The immediate renderer supports only clip-bound-based hit testing; a control's geometry is
/// not taken into account.
/// </remarks>
public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer
{
private readonly IVisual _root;
private readonly IRenderRoot _renderRoot;
private IRenderTarget _renderTarget;
/// <summary>
/// Initializes a new instance of the <see cref="ImmediateRenderer"/> class.
/// </summary>
/// <param name="root">The control to render.</param>
public ImmediateRenderer(IVisual root)
{
Contract.Requires<ArgumentNullException>(root != null);
_root = root;
_renderRoot = root as IRenderRoot;
}
/// <inheritdoc/>
public bool DrawFps { get; set; }
/// <inheritdoc/>
public bool DrawDirtyRects { get; set; }
/// <inheritdoc/>
public void Paint(Rect rect)
{
if (_renderTarget == null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
try
{
using (var context = new DrawingContext(_renderTarget.CreateDrawingContext(this)))
{
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, true);
}
}
}
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget.Dispose();
_renderTarget = null;
}
}
/// <inheritdoc/>
public void Resized(Size size)
{
}
/// <summary>
/// Renders a visual to a render target.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="target">The render target.</param>
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);
}
}
/// <summary>
/// Renders a visual to a drawing context.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="context">The drawing context.</param>
public static void Render(IVisual visual, DrawingContext context)
{
using (var renderer = new ImmediateRenderer(visual))
{
renderer.Render(context, visual, visual.Bounds);
}
}
/// <inheritdoc/>
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);
_renderRoot?.Invalidate(bounds);
}
}
}
/// <summary>
/// Ends the operation of the renderer.
/// </summary>
public void Dispose()
{
}
/// <inheritdoc/>
public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter)
{
return HitTest(_root, p, filter);
}
/// <inheritdoc/>
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
{
(brush.Visual as IVisualBrushInitialize)?.EnsureInitialized();
return brush.Visual?.Bounds.Size ?? Size.Empty;
}
/// <inheritdoc/>
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.GetSelfAndVisualDescendents())
{
BoundsTracker.SetTransformedBounds((Visual)visual, 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);
}
}
static IEnumerable<IVisual> HitTest(
IVisual visual,
Point p,
Func<IVisual, bool> filter)
{
Contract.Requires<ArgumentNullException>(visual != null);
if (filter?.Invoke(visual) != false)
{
bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)visual)?.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)
{
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
if (visual is Visual)
{
BoundsTracker.SetTransformedBounds((Visual)visual, 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);
}
}
}
}

Просмотреть файл

@ -1,67 +0,0 @@
// 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 Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class Renderer : IDisposable, IRenderer
{
private readonly IRenderLoop _renderLoop;
private readonly IRenderRoot _root;
private IRenderTarget _renderTarget;
private bool _dirty;
public Renderer(IRenderRoot root, IRenderLoop renderLoop)
{
Contract.Requires<ArgumentNullException>(root != null);
_root = root;
_renderLoop = renderLoop;
_renderLoop.Tick += OnRenderLoopTick;
}
public void AddDirty(IVisual visual)
{
_dirty = true;
}
public void Dispose()
{
_renderLoop.Tick -= OnRenderLoopTick;
}
public void Render(Rect rect)
{
if (_renderTarget == null)
{
_renderTarget = _root.CreateRenderTarget();
}
try
{
_renderTarget.Render(_root);
}
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget.Dispose();
_renderTarget = null;
}
finally
{
_dirty = false;
}
}
private void OnRenderLoopTick(object sender, EventArgs e)
{
if (_dirty)
{
_root.Invalidate(new Rect(_root.ClientSize));
}
}
}
}

Просмотреть файл

@ -0,0 +1,51 @@
using System;
using System.Diagnostics;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.Rendering
{
public class RendererBase
{
private static readonly Typeface s_fpsTypeface = new Typeface("Arial", 18);
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
private int _framesThisSecond;
private int _fps;
private FormattedText _fpsText;
private TimeSpan _lastFpsUpdate;
public RendererBase()
{
_fpsText = new FormattedText
{
Typeface = new Typeface(null, 18),
};
}
protected void RenderFps(IDrawingContextImpl context, Rect clientRect, bool incrementFrameCount)
{
var now = _stopwatch.Elapsed;
var elapsed = now - _lastFpsUpdate;
if (incrementFrameCount)
{
++_framesThisSecond;
}
if (elapsed.TotalSeconds > 1)
{
_fps = (int)(_framesThisSecond / elapsed.TotalSeconds);
_framesThisSecond = 0;
_lastFpsUpdate = now;
}
_fpsText.Text = string.Format("FPS: {0:000}", _fps);
var size = _fpsText.Measure();
var rect = new Rect(clientRect.Right - size.Width, 0, size.Width, size.Height);
context.Transform = Matrix.Identity;
context.FillRectangle(Brushes.Black, rect);
context.DrawText(Brushes.White, rect.TopLeft, _fpsText.PlatformImpl);
}
}
}

Просмотреть файл

@ -1,205 +0,0 @@
// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
/// <summary>
/// Extension methods for rendering.
/// </summary>
/// <remarks>
/// This class provides implements the platform-independent parts of <see cref="IRenderTarget"/>.
/// </remarks>
[SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")]
[SuppressMessage("ReSharper", "ForCanBeConvertedToForeach")]
public static class RendererMixin
{
private static int s_frameNum;
private static int s_fps;
private static int s_currentFrames;
private static TimeSpan s_lastMeasure;
private static readonly Stopwatch s_stopwatch = Stopwatch.StartNew();
private static readonly Stack<List<IVisual>> s_listPool = new Stack<List<IVisual>>();
private static readonly ZIndexComparer s_visualComparer = new ZIndexComparer();
/// <summary>
/// Gets or sets a value which determines whether an FPS counted will be drawn on each
/// rendered frame.
/// </summary>
public static bool DrawFpsCounter { get; set; }
/// <summary>
/// Renders the specified visual.
/// </summary>
/// <param name="renderTarget">IRenderer instance</param>
/// <param name="visual">The visual to render.</param>
public static void Render(this IRenderTarget renderTarget, IVisual visual)
{
using (var ctx = renderTarget.CreateDrawingContext())
{
ctx.Render(visual);
s_frameNum++;
if (DrawFpsCounter)
{
s_currentFrames++;
var now = s_stopwatch.Elapsed;
var elapsed = now - s_lastMeasure;
if (elapsed.TotalSeconds > 1)
{
s_fps = (int) (s_currentFrames/elapsed.TotalSeconds);
s_currentFrames = 0;
s_lastMeasure = now;
}
var pt = new Point(40, 40);
var txt = new FormattedText
{
Text = "Frame #" + s_frameNum + " FPS: " + s_fps,
Typeface = new Typeface("Arial", 18)
};
ctx.FillRectangle(Brushes.White, new Rect(pt, txt.Measure()));
ctx.DrawText(Brushes.Black, pt, txt);
}
}
}
/// <summary>
/// Renders the specified visual.
/// </summary>
/// <param name="visual">The visual to render.</param>
/// <param name="context">The drawing context.</param>
public static void Render(this DrawingContext context, IVisual visual)
{
context.Render(visual, visual.Bounds);
}
/// <summary>
/// Renders the specified visual.
/// </summary>
/// <param name="visual">The visual to render.</param>
/// <param name="context">The drawing context.</param>
/// <param name="clipRect">
/// The current clip rect, in coordinates relative to <paramref name="visual"/>.
/// </param>
private static void Render(this 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)
{
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
if (visual is Visual)
{
BoundsTracker.SetTransformedBounds((Visual)visual, transformed);
}
var lst = GetSortedVisualList(visual.VisualChildren);
foreach (var child in lst)
{
var childBounds = GetTransformedBounds(child);
if (!child.ClipToBounds || clipRect.Intersects(childBounds))
{
var childClipRect = clipRect.Translate(-childBounds.Position);
context.Render(child, childClipRect);
}
else
{
ClearTransformedBounds(child);
}
}
ReturnListToPool(lst);
}
}
if (!visual.IsVisible)
{
ClearTransformedBounds(visual);
}
}
private static void ClearTransformedBounds(IVisual visual)
{
foreach (var e in visual.GetSelfAndVisualDescendents())
{
BoundsTracker.SetTransformedBounds((Visual)visual, null);
}
}
private static void ReturnListToPool(List<IVisual> lst)
{
lst.Clear();
s_listPool.Push(lst);
}
private static List<IVisual> GetSortedVisualList(IReadOnlyList<IVisual> source)
{
var lst = s_listPool.Count == 0 ? new List<IVisual>() : s_listPool.Pop();
for (var c = 0; c < source.Count; c++)
lst.Add(source[c]);
lst.Sort(s_visualComparer);
return lst;
}
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);
}
}
class ZIndexComparer : IComparer<IVisual>
{
public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex);
}
}
}

Просмотреть файл

@ -0,0 +1,193 @@
// 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 Avalonia.Media;
namespace Avalonia.Rendering.Utilities
{
public class TileBrushCalculator
{
private readonly Size _imageSize;
private readonly Rect _drawRect;
public bool IsValid { get; }
/// <summary>
/// Initializes a new instance of the <see cref="TileBrushCalculator"/> class.
/// </summary>
/// <param name="brush">The brush to be rendered.</param>
/// <param name="contentSize">The size of the content of the tile brush.</param>
/// <param name="targetSize">The size of the control to which the brush is being rendered.</param>
public TileBrushCalculator(ITileBrush brush, Size contentSize, Size targetSize)
: this(
brush.TileMode,
brush.Stretch,
brush.AlignmentX,
brush.AlignmentY,
brush.SourceRect,
brush.DestinationRect,
contentSize,
targetSize)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TileBrushCalculator"/> class.
/// </summary>
/// <param name="tileMode">The brush's tile mode.</param>
/// <param name="stretch">The brush's stretch.</param>
/// <param name="alignmentX">The brush's horizontal alignment.</param>
/// <param name="alignmentY">The brush's vertical alignment.</param>
/// <param name="sourceRect">The brush's source rect</param>
/// <param name="destinationRect">The brush's destination rect.</param>
/// <param name="contentSize">The size of the content of the tile brush.</param>
/// <param name="targetSize">The size of the control to which the brush is being rendered.</param>
public TileBrushCalculator(
TileMode tileMode,
Stretch stretch,
AlignmentX alignmentX,
AlignmentY alignmentY,
RelativeRect sourceRect,
RelativeRect destinationRect,
Size contentSize,
Size targetSize)
{
_imageSize = contentSize;
SourceRect = sourceRect.ToPixels(_imageSize);
DestinationRect = destinationRect.ToPixels(targetSize);
var scale = stretch.CalculateScaling(DestinationRect.Size, SourceRect.Size);
var translate = CalculateTranslate(alignmentX, alignmentY, SourceRect, DestinationRect, scale);
IntermediateSize = tileMode == TileMode.None ? targetSize : DestinationRect.Size;
IntermediateTransform = CalculateIntermediateTransform(
tileMode,
SourceRect,
DestinationRect,
scale,
translate,
out _drawRect);
}
/// <summary>
/// Gets the rectangle on the destination control to which content should be rendered.
/// </summary>
/// <remarks>
/// If <see cref="TileMode"/> of the brush is repeating then this is describes rectangle
/// of a single repeat of the tiled content.
/// </remarks>
public Rect DestinationRect { get; }
/// <summary>
/// Gets the clip rectangle on the intermediate image with which the brush content should be
/// drawn when <see cref="NeedsIntermediate"/> is true.
/// </summary>
public Rect IntermediateClip => _drawRect;
/// <summary>
/// Gets the size of the intermediate image that should be created when
/// <see cref="NeedsIntermediate"/> is true.
/// </summary>
public Size IntermediateSize { get; }
/// <summary>
/// Gets the transform to be used when rendering to the intermediate image when
/// <see cref="NeedsIntermediate"/> is true.
/// </summary>
public Matrix IntermediateTransform { get; }
/// <summary>
/// Gets a value indicating whether an intermediate image should be created in order to
/// render the tile brush.
/// </summary>
/// <remarks>
/// Intermediate images are required when a brush's <see cref="TileMode"/> is not repeating
/// but the source and destination aspect ratios are unequal, as all of the currently
/// supported rendering backends do not support non-tiled image brushes.
/// </remarks>
public bool NeedsIntermediate
{
get
{
if (IntermediateTransform != Matrix.Identity)
return true;
if (SourceRect.Position != default(Point))
return true;
if (SourceRect.Size.AspectRatio == _imageSize.AspectRatio)
return false;
if ((int)SourceRect.Width != _imageSize.Width ||
(int)SourceRect.Height != _imageSize.Height)
return true;
return false;
}
}
/// <summary>
/// Gets the area of the source content to be rendered.
/// </summary>
public Rect SourceRect { get; }
public static Vector CalculateTranslate(
AlignmentX alignmentX,
AlignmentY alignmentY,
Rect sourceRect,
Rect destinationRect,
Vector scale)
{
var x = 0.0;
var y = 0.0;
var size = sourceRect.Size * scale;
switch (alignmentX)
{
case AlignmentX.Center:
x += (destinationRect.Width - size.Width) / 2;
break;
case AlignmentX.Right:
x += destinationRect.Width - size.Width;
break;
}
switch (alignmentY)
{
case AlignmentY.Center:
y += (destinationRect.Height - size.Height) / 2;
break;
case AlignmentY.Bottom:
y += destinationRect.Height - size.Height;
break;
}
return new Vector(x, y);
}
public static Matrix CalculateIntermediateTransform(
TileMode tileMode,
Rect sourceRect,
Rect destinationRect,
Vector scale,
Vector translate,
out Rect drawRect)
{
var transform = Matrix.CreateTranslation(-sourceRect.Position) *
Matrix.CreateScale(scale) *
Matrix.CreateTranslation(translate);
Rect dr;
if (tileMode == TileMode.None)
{
dr = destinationRect;
transform *= Matrix.CreateTranslation(destinationRect.Position);
}
else
{
dr = new Rect(destinationRect.Size);
}
drawRect = dr;
return transform;
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class ZIndexComparer : IComparer<IVisual>
{
public static readonly ZIndexComparer Instance = new ZIndexComparer();
public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex);
}
}

Просмотреть файл

@ -498,14 +498,14 @@ namespace Avalonia
if (VisualRoot != null)
{
var e = new VisualTreeAttachmentEventArgs(VisualRoot);
var e = new VisualTreeAttachmentEventArgs(old, VisualRoot);
OnDetachedFromVisualTreeCore(e);
}
if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true)
{
var root = this.GetVisualAncestors().OfType<IRenderRoot>().FirstOrDefault();
var e = new VisualTreeAttachmentEventArgs(root);
var e = new VisualTreeAttachmentEventArgs(_visualParent, root);
OnAttachedToVisualTreeCore(e);
}

Просмотреть файл

@ -3,6 +3,7 @@
using System;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia
{
@ -15,14 +16,22 @@ namespace Avalonia
/// <summary>
/// Initializes a new instance of the <see cref="VisualTreeAttachmentEventArgs"/> class.
/// </summary>
/// <param name="parent">The parent that the visual is being attached to or detached from.</param>
/// <param name="root">The root visual.</param>
public VisualTreeAttachmentEventArgs(IRenderRoot root)
public VisualTreeAttachmentEventArgs(IVisual parent, IRenderRoot root)
{
Contract.Requires<ArgumentNullException>(parent != null);
Contract.Requires<ArgumentNullException>(root != null);
Parent = parent;
Root = root;
}
/// <summary>
/// Gets the parent that the visual is being attached to or detached from.
/// </summary>
public IVisual Parent { get; }
/// <summary>
/// Gets the root of the visual tree that the visual is being attached to or detached from.
/// </summary>

Просмотреть файл

@ -53,7 +53,6 @@
<Compile Include="Media\Imaging\BitmapImpl.cs" />
<Compile Include="Media\Imaging\RenderTargetBitmapImpl.cs" />
<Compile Include="Media\RadialGradientBrushImpl.cs" />
<Compile Include="Media\TileBrushes.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RenderTarget.cs" />
<Compile Include="CairoExtensions.cs" />
@ -63,7 +62,6 @@
<Compile Include="Media\SolidColorBrushImpl.cs" />
<Compile Include="Media\LinearGradientBrushImpl.cs" />
<Compile Include="Media\ImageBrushImpl.cs" />
<Compile Include="Media\VisualBrushImpl.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj">

Просмотреть файл

@ -68,7 +68,7 @@ namespace Avalonia.Cairo
"Don't know how to create a Cairo renderer from any of the provided surfaces."));
}
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height)
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height, double dpiX, double dpiY)
{
return new RenderTargetBitmapImpl(new ImageSurface(Format.Argb32, width, height));
}

Просмотреть файл

@ -8,6 +8,7 @@ using System.Reactive.Disposables;
using Avalonia.Cairo.Media.Imaging;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Cairo.Media
{
@ -18,32 +19,30 @@ namespace Avalonia.Cairo.Media
/// </summary>
public class DrawingContext : IDrawingContextImpl, IDisposable
{
/// <summary>
/// The cairo context.
/// </summary>
private readonly Cairo.Context _context;
private readonly IVisualBrushRenderer _visualBrushRenderer;
private readonly Stack<BrushImpl> _maskStack = new Stack<BrushImpl>();
/// <summary>
/// Initializes a new instance of the <see cref="DrawingContext"/> class.
/// </summary>
/// <param name="surface">The target surface.</param>
public DrawingContext(Cairo.Surface surface)
public DrawingContext(Cairo.Surface surface, IVisualBrushRenderer visualBrushRenderer)
{
_context = new Cairo.Context(surface);
_visualBrushRenderer = visualBrushRenderer;
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawingContext"/> class.
/// </summary>
/// <param name="surface">The GDK drawable.</param>
public DrawingContext(Gdk.Drawable drawable)
public DrawingContext(Gdk.Drawable drawable, IVisualBrushRenderer visualBrushRenderer)
{
_context = Gdk.CairoHelper.Create(drawable);
_visualBrushRenderer = visualBrushRenderer;
}
private Matrix _transform = Matrix.Identity;
/// <summary>
/// Gets the current transform of the drawing context.
@ -55,7 +54,7 @@ namespace Avalonia.Cairo.Media
{
_transform = value;
_context.Matrix = value.ToCairo();
}
}
@ -82,42 +81,47 @@ namespace Avalonia.Cairo.Media
/// <param name="destRect">The rect in the output to draw to.</param>
public void DrawImage(IBitmapImpl bitmap, double opacity, Rect sourceRect, Rect destRect)
{
var impl = bitmap as BitmapImpl;
var size = new Size(impl.PixelWidth, impl.PixelHeight);
var pixbuf = bitmap as Gdk.Pixbuf;
var rtb = bitmap as RenderTargetBitmapImpl;
var size = new Size(pixbuf?.Width ?? rtb.PixelWidth, pixbuf?.Height ?? rtb.PixelHeight);
var scale = new Vector(destRect.Width / sourceRect.Width, destRect.Height / sourceRect.Height);
_context.Save();
_context.Scale(scale.X, scale.Y);
destRect /= scale;
if (opacityOverride < 1.0f) {
_context.PushGroup ();
Gdk.CairoHelper.SetSourcePixbuf (
_context,
impl,
-sourceRect.X + destRect.X,
-sourceRect.Y + destRect.Y);
_context.PushGroup();
_context.Rectangle (destRect.ToCairo ());
_context.Fill ();
_context.PopGroupToSource ();
_context.PaintWithAlpha (opacityOverride);
} else {
_context.PushGroup ();
Gdk.CairoHelper.SetSourcePixbuf (
_context,
impl,
-sourceRect.X + destRect.X,
-sourceRect.Y + destRect.Y);
_context.Rectangle (destRect.ToCairo ());
_context.Fill ();
_context.PopGroupToSource ();
_context.PaintWithAlpha (opacityOverride);
if (pixbuf != null)
{
Gdk.CairoHelper.SetSourcePixbuf(
_context,
pixbuf,
-sourceRect.X + destRect.X,
-sourceRect.Y + destRect.Y);
}
else
{
_context.SetSourceSurface(
rtb.Surface,
(int)(-sourceRect.X + destRect.X),
(int)(-sourceRect.Y + destRect.Y));
}
_context.Rectangle(destRect.ToCairo());
_context.Fill();
_context.PopGroupToSource();
_context.PaintWithAlpha(opacityOverride);
_context.Restore();
}
public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
PushOpacityMask(opacityMask, opacityMaskRect);
DrawImage(source, 1, new Rect(0, 0, source.PixelWidth, source.PixelHeight), destRect);
PopOpacityMask();
}
/// <summary>
/// Draws a line.
/// </summary>
@ -127,8 +131,8 @@ namespace Avalonia.Cairo.Media
public void DrawLine(Pen pen, Point p1, Point p2)
{
var size = new Rect(p1, p2).Size;
using (var p = SetPen(pen, size))
using (var p = SetPen(pen, size))
{
_context.MoveTo(p1.ToCairo());
_context.LineTo(p2.ToCairo());
@ -149,7 +153,7 @@ namespace Avalonia.Cairo.Media
var oldMatrix = Transform;
Transform = impl.Transform * Transform;
if (brush != null)
{
_context.AppendPath(impl.Path);
@ -184,9 +188,9 @@ namespace Avalonia.Cairo.Media
/// <param name="rect">The rectangle bounds.</param>
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius)
{
using (var p = SetPen(pen, rect.Size))
using (var p = SetPen(pen, rect.Size))
{
_context.Rectangle(rect.ToCairo ());
_context.Rectangle(rect.ToCairo());
_context.Stroke();
}
}
@ -202,7 +206,7 @@ namespace Avalonia.Cairo.Media
var layout = ((FormattedTextImpl)text).Layout;
_context.MoveTo(origin.X, origin.Y);
using (var b = SetBrush(foreground, new Size(0, 0)))
using (var b = SetBrush(foreground, new Size(0, 0)))
{
Pango.CairoHelper.ShowLayout(_context, layout);
}
@ -215,9 +219,9 @@ namespace Avalonia.Cairo.Media
/// <param name="rect">The rectangle bounds.</param>
public void FillRectangle(IBrush brush, Rect rect, float cornerRadius)
{
using (var b = SetBrush(brush, rect.Size))
using (var b = SetBrush(brush, rect.Size))
{
_context.Rectangle(rect.ToCairo ());
_context.Rectangle(rect.ToCairo());
_context.Fill();
}
}
@ -272,10 +276,10 @@ namespace Avalonia.Cairo.Media
return Disposable.Create(() =>
{
_context.Restore();
_context.Restore();
});
}
private double opacityOverride = 1.0f;
private IDisposable SetBrush(IBrush brush, Size destinationSize)
@ -315,11 +319,35 @@ namespace Avalonia.Cairo.Media
}
else if (imageBrush != null)
{
impl = new ImageBrushImpl(imageBrush, destinationSize);
impl = new ImageBrushImpl(imageBrush, (BitmapImpl)imageBrush.Source.PlatformImpl, destinationSize);
}
else if (visualBrush != null)
{
impl = new VisualBrushImpl(visualBrush, destinationSize);
if (_visualBrushRenderer != null)
{
var intermediateSize = _visualBrushRenderer.GetRenderTargetSize(visualBrush);
if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1)
{
using (var intermediate = new Cairo.ImageSurface(Cairo.Format.ARGB32, (int)intermediateSize.Width, (int)intermediateSize.Height))
{
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext(_visualBrushRenderer))
{
ctx.Clear(Colors.Transparent);
_visualBrushRenderer.RenderVisualBrush(ctx, visualBrush);
}
return new ImageBrushImpl(
visualBrush,
new RenderTargetBitmapImpl(intermediate),
destinationSize);
}
}
}
else
{
throw new NotSupportedException("No IVisualBrushRenderer was supplied to DrawingContextImpl.");
}
}
else
{
@ -351,7 +379,7 @@ namespace Avalonia.Cairo.Media
if (pen.Brush == null)
return Disposable.Empty;
return SetBrush(pen.Brush, destinationSize);
}
@ -377,10 +405,10 @@ namespace Avalonia.Cairo.Media
public void PopOpacityMask()
{
_context.PopGroupToSource();
var brushImpl = _maskStack.Pop ();
var brushImpl = _maskStack.Pop();
_context.Mask(brushImpl.PlatformBrush);
brushImpl.Dispose ();
brushImpl.Dispose();
}
}
}

Просмотреть файл

@ -1,15 +1,56 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.Utilities;
using global::Cairo;
namespace Avalonia.Cairo.Media
{
public class ImageBrushImpl : BrushImpl
{
public ImageBrushImpl(IImageBrush brush, Size destinationSize)
{
this.PlatformBrush = TileBrushes.CreateTileBrush(brush, destinationSize);
}
}
public class ImageBrushImpl : BrushImpl
{
public ImageBrushImpl(
ITileBrush brush,
IBitmapImpl bitmap,
Size targetSize)
{
var calc = new TileBrushCalculator(brush, new Size(bitmap.PixelWidth, bitmap.PixelHeight), targetSize);
using (var intermediate = new ImageSurface(Format.ARGB32, (int)calc.IntermediateSize.Width, (int)calc.IntermediateSize.Height))
{
using (var context = new RenderTarget(intermediate).CreateDrawingContext(null))
{
var rect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform;
context.DrawImage(bitmap, 1, rect, rect);
context.PopClip();
}
var result = new SurfacePattern(intermediate);
if ((brush.TileMode & TileMode.FlipXY) != 0)
{
// TODO: Currently always FlipXY as that's all cairo supports natively.
// Support separate FlipX and FlipY by drawing flipped images to intermediate
// surface.
result.Extend = Extend.Reflect;
}
else
{
result.Extend = Extend.Repeat;
}
if (brush.TileMode != TileMode.None)
{
var matrix = result.Matrix;
matrix.InitTranslate(-calc.DestinationRect.X, -calc.DestinationRect.Y);
result.Matrix = matrix;
}
PlatformBrush = result;
}
}
}
}

Просмотреть файл

@ -40,9 +40,9 @@ namespace Avalonia.Cairo.Media.Imaging
Surface.WriteToPng(fileName);
}
public Avalonia.Media.DrawingContext CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return _renderTarget.CreateDrawingContext();
return _renderTarget.CreateDrawingContext(visualBrushRenderer);
}
public void Save(Stream stream)

Просмотреть файл

@ -1,55 +0,0 @@
// 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 Cairo;
using Avalonia.Cairo.Media.Imaging;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.RenderHelpers;
namespace Avalonia.Cairo.Media
{
internal static class TileBrushes
{
public static SurfacePattern CreateTileBrush(ITileBrush brush, Size targetSize)
{
var helper = new TileBrushImplHelper(brush, targetSize);
if (!helper.IsValid)
return null;
using (var intermediate = new ImageSurface(Format.ARGB32, (int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height))
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext())
{
helper.DrawIntermediate(ctx);
var result = new SurfacePattern(intermediate);
if ((brush.TileMode & TileMode.FlipXY) != 0)
{
// TODO: Currently always FlipXY as that's all cairo supports natively.
// Support separate FlipX and FlipY by drawing flipped images to intermediate
// surface.
result.Extend = Extend.Reflect;
}
else
{
result.Extend = Extend.Repeat;
}
if (brush.TileMode != TileMode.None)
{
var matrix = result.Matrix;
matrix.InitTranslate(-helper.DestinationRect.X, -helper.DestinationRect.Y);
result.Matrix = matrix;
}
return result;
}
}
}
}

Просмотреть файл

@ -1,15 +0,0 @@
using System;
using Avalonia.Media;
using global::Cairo;
namespace Avalonia.Cairo.Media
{
public class VisualBrushImpl : BrushImpl
{
public VisualBrushImpl(IVisualBrush brush, Size destinationSize)
{
this.PlatformBrush = TileBrushes.CreateTileBrush(brush, destinationSize);
}
}
}

Просмотреть файл

@ -42,15 +42,14 @@ namespace Avalonia.Cairo
/// <summary>
/// Creates a cairo surface that targets a platform-specific resource.
/// </summary>
/// <param name="visualBrushRenderer">The visual brush renderer to use.</param>
/// <returns>A surface wrapped in an <see cref="Avalonia.Media.DrawingContext"/>.</returns>
public DrawingContext CreateDrawingContext() => new DrawingContext(CreateMediaDrawingContext());
public IDrawingContextImpl CreateMediaDrawingContext()
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
if (_drawableAccessor != null)
return new Media.DrawingContext(_drawableAccessor());
return new Media.DrawingContext(_drawableAccessor(), visualBrushRenderer);
if (_surface != null)
return new Media.DrawingContext(_surface);
return new Media.DrawingContext(_surface, visualBrushRenderer);
throw new InvalidOperationException("Unspecified render target");
}

Просмотреть файл

@ -115,7 +115,7 @@ namespace Avalonia.Gtk
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
return new Renderer(root, renderLoop);
return new ImmediateRenderer(root);
}
public IWindowIconImpl LoadIcon(string fileName)

Просмотреть файл

@ -15,7 +15,7 @@ using Avalonia.Gtk3;
namespace Avalonia.Gtk3
{
public class Gtk3Platform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
public class Gtk3Platform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface, IRendererFactory
{
internal static readonly Gtk3Platform Instance = new Gtk3Platform();
internal static readonly MouseDevice Mouse = new MouseDevice();
@ -52,7 +52,10 @@ namespace Avalonia.Gtk3
public IPopupImpl CreatePopup() => new PopupImpl();
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
return new ImmediateRenderer(root);
}
public Size DoubleClickSize => new Size(4, 4);

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
@ -11,6 +11,5 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)ArcToHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)QuadBezierHelper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TileBrushImplHelper.cs" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,209 +0,0 @@
// 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 Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia.RenderHelpers
{
internal class TileBrushImplHelper
{
public Size IntermediateSize { get; }
public Rect DestinationRect { get; }
private readonly TileMode _tileMode;
private readonly Rect _sourceRect;
private readonly Vector _scale;
private readonly Vector _translate;
private readonly Size _imageSize;
private readonly IVisualBrush _visualBrush;
private readonly IImageBrush _imageBrush;
private readonly Matrix _transform;
private readonly Rect _drawRect;
public bool IsValid { get; }
public TileBrushImplHelper(ITileBrush brush, Size targetSize)
{
_imageBrush = brush as IImageBrush;
_visualBrush = brush as IVisualBrush;
if (_imageBrush != null)
{
if (_imageBrush.Source == null)
return;
_imageSize = new Size(_imageBrush.Source.PixelWidth, _imageBrush.Source.PixelHeight);
IsValid = true;
}
else if (_visualBrush != null)
{
var control = _visualBrush.Visual as IControl;
if (control != null)
{
EnsureInitialized(control);
if (control.IsArrangeValid == false)
{
control.Measure(Size.Infinity);
control.Arrange(new Rect(control.DesiredSize));
}
_imageSize = control.Bounds.Size;
IsValid = true;
}
}
else
return;
_tileMode = brush.TileMode;
_sourceRect = brush.SourceRect.ToPixels(_imageSize);
DestinationRect = brush.DestinationRect.ToPixels(targetSize);
_scale = brush.Stretch.CalculateScaling(DestinationRect.Size, _sourceRect.Size);
_translate = CalculateTranslate(brush, _sourceRect, DestinationRect, _scale);
IntermediateSize = CalculateIntermediateSize(_tileMode, targetSize, DestinationRect.Size);
_transform = CalculateIntermediateTransform(
_tileMode,
_sourceRect,
DestinationRect,
_scale,
_translate,
out _drawRect);
}
public bool NeedsIntermediateSurface
{
get
{
if (_imageBrush == null)
return true;
if (_transform != Matrix.Identity)
return true;
if (_sourceRect.Position != default(Point))
return true;
if ((int) _sourceRect.Width != _imageBrush.Source.PixelWidth ||
(int) _sourceRect.Height != _imageBrush.Source.PixelHeight)
return true;
return false;
}
}
public T GetDirect<T>() => (T) _imageBrush?.Source.PlatformImpl;
public void DrawIntermediate(DrawingContext ctx)
{
using (ctx.PushClip(_drawRect))
using (ctx.PushPostTransform(_transform))
{
if (_imageBrush != null)
{
var bmpRc = new Rect(0, 0, _imageBrush.Source.PixelWidth, _imageBrush.Source.PixelHeight);
ctx.DrawImage(_imageBrush.Source, 1, bmpRc, bmpRc);
}
else if (_visualBrush != null)
{
using (ctx.PushPostTransform(Matrix.CreateTranslation(-_visualBrush.Visual.Bounds.Position)))
{
ctx.Render(_visualBrush.Visual);
}
}
}
}
/// <summary>
/// Calculates a translate based on an <see cref="ITileBrush"/>, a source and destination
/// rectangle and a scale.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="sourceRect">The source rectangle.</param>
/// <param name="destinationRect">The destination rectangle.</param>
/// <param name="scale">The _scale factor.</param>
/// <returns>A vector with the X and Y _translate.</returns>
public static Vector CalculateTranslate(
ITileBrush brush,
Rect sourceRect,
Rect destinationRect,
Vector scale)
{
var x = 0.0;
var y = 0.0;
var size = sourceRect.Size*scale;
switch (brush.AlignmentX)
{
case AlignmentX.Center:
x += (destinationRect.Width - size.Width)/2;
break;
case AlignmentX.Right:
x += destinationRect.Width - size.Width;
break;
}
switch (brush.AlignmentY)
{
case AlignmentY.Center:
y += (destinationRect.Height - size.Height)/2;
break;
case AlignmentY.Bottom:
y += destinationRect.Height - size.Height;
break;
}
return new Vector(x, y);
}
public static Matrix CalculateIntermediateTransform(
TileMode tileMode,
Rect sourceRect,
Rect destinationRect,
Vector scale,
Vector translate,
out Rect drawRect)
{
var transform = Matrix.CreateTranslation(-sourceRect.Position)*
Matrix.CreateScale(scale)*
Matrix.CreateTranslation(translate);
Rect dr;
if (tileMode == TileMode.None)
{
dr = destinationRect;
transform *= Matrix.CreateTranslation(destinationRect.Position);
}
else
{
dr = new Rect(destinationRect.Size);
}
drawRect = dr;
return transform;
}
private static Size CalculateIntermediateSize(
TileMode tileMode,
Size targetSize,
Size destinationSize) => tileMode == TileMode.None ? targetSize : destinationSize;
private static void EnsureInitialized(IControl control)
{
foreach (var i in control.GetSelfAndVisualDescendents())
{
var c = i as IControl;
if (c?.IsInitialized == false)
{
var init = c as ISupportInitialize;
if (init != null)
{
init.BeginInit();
init.EndInit();
}
}
}
}
}
}

Просмотреть файл

@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using SkiaSharp;
namespace Avalonia.Skia
@ -66,9 +67,10 @@ namespace Avalonia.Skia
{
private readonly SKSurface _surface;
public BitmapDrawingContext(SKBitmap bitmap) : this(CreateSurface(bitmap))
public BitmapDrawingContext(SKBitmap bitmap, IVisualBrushRenderer visualBrushRenderer)
: this(CreateSurface(bitmap), visualBrushRenderer)
{
}
private static SKSurface CreateSurface(SKBitmap bitmap)
@ -80,7 +82,8 @@ namespace Avalonia.Skia
return rv;
}
public BitmapDrawingContext(SKSurface surface) : base(surface.Canvas)
public BitmapDrawingContext(SKSurface surface, IVisualBrushRenderer visualBrushRenderer)
: base(surface.Canvas, visualBrushRenderer)
{
_surface = surface;
}
@ -92,10 +95,9 @@ namespace Avalonia.Skia
}
}
public DrawingContext CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContext(new BitmapDrawingContext(Bitmap));
return new BitmapDrawingContext(Bitmap, visualBrushRenderer);
}
public void Save(Stream stream)

Просмотреть файл

@ -1,11 +1,11 @@
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.RenderHelpers;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Utilities;
namespace Avalonia.Skia
{
@ -13,17 +13,22 @@ namespace Avalonia.Skia
{
private readonly Matrix? _postTransform;
private readonly IDisposable[] _disposables;
private readonly IVisualBrushRenderer _visualBrushRenderer;
private Stack<PaintWrapper> maskStack = new Stack<PaintWrapper>();
public SKCanvas Canvas { get; private set; }
public DrawingContextImpl(SKCanvas canvas, Matrix? postTransform = null, params IDisposable[] disposables)
public DrawingContextImpl(
SKCanvas canvas,
IVisualBrushRenderer visualBrushRenderer,
Matrix? postTransform = null,
params IDisposable[] disposables)
{
if (postTransform.HasValue && !postTransform.Value.IsIdentity)
_postTransform = postTransform;
_visualBrushRenderer = visualBrushRenderer;
_disposables = disposables;
Canvas = canvas;
Canvas.Clear();
Transform = Matrix.Identity;
}
@ -194,14 +199,56 @@ namespace Avalonia.Skia
}
var tileBrush = brush as ITileBrush;
if (tileBrush != null)
var visualBrush = brush as IVisualBrush;
var tileBrushImage = default(BitmapImpl);
if (visualBrush != null)
{
var helper = new TileBrushImplHelper(tileBrush, targetSize);
var bitmap = new BitmapImpl((int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height);
if (_visualBrushRenderer != null)
{
var intermediateSize = _visualBrushRenderer.GetRenderTargetSize(visualBrush);
if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1)
{
var intermediate = new BitmapImpl((int)intermediateSize.Width, (int)intermediateSize.Height);
using (var ctx = intermediate.CreateDrawingContext(_visualBrushRenderer))
{
ctx.Clear(Colors.Transparent);
_visualBrushRenderer.RenderVisualBrush(ctx, visualBrush);
}
rv.AddDisposable(tileBrushImage);
tileBrushImage = intermediate;
}
}
else
{
throw new NotSupportedException("No IVisualBrushRenderer was supplied to DrawingContextImpl.");
}
}
else
{
tileBrushImage = (BitmapImpl)((tileBrush as IImageBrush)?.Source?.PlatformImpl);
}
if (tileBrush != null && tileBrushImage != null)
{
var calc = new TileBrushCalculator(tileBrush, new Size(tileBrushImage.PixelWidth, tileBrushImage.PixelHeight), targetSize);
var bitmap = new BitmapImpl((int)calc.IntermediateSize.Width, (int)calc.IntermediateSize.Height);
rv.AddDisposable(bitmap);
using (var ctx = bitmap.CreateDrawingContext())
helper.DrawIntermediate(ctx);
SKMatrix translation = SKMatrix.MakeTranslation(-(float)helper.DestinationRect.X, -(float)helper.DestinationRect.Y);
using (var context = bitmap.CreateDrawingContext(null))
{
var rect = new Rect(0, 0, tileBrushImage.PixelWidth, tileBrushImage.PixelHeight);
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform;
context.DrawImage(tileBrushImage, 1, rect, rect);
context.PopClip();
}
SKMatrix translation = SKMatrix.MakeTranslation(-(float)calc.DestinationRect.X, -(float)calc.DestinationRect.Y);
SKShaderTileMode tileX =
tileBrush.TileMode == TileMode.None
? SKShaderTileMode.Clamp

Просмотреть файл

@ -4,6 +4,7 @@ using System.Text;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using SkiaSharp;
namespace Avalonia.Skia
@ -56,7 +57,7 @@ namespace Avalonia.Skia
}
public DrawingContext CreateDrawingContext()
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
var fb = _surface.Lock();
PixelFormatShim shim = null;
@ -69,15 +70,14 @@ namespace Avalonia.Skia
throw new Exception("Unable to create a surface for pixel format " + fb.Format +
" or pixel format translator");
var canvas = surface.Canvas;
canvas.RestoreToCount(0);
canvas.Save();
canvas.Clear(SKColors.Red);
canvas.ResetMatrix();
var scale = Matrix.CreateScale(fb.Dpi.Width / 96, fb.Dpi.Height / 96);
return new DrawingContext(new DrawingContextImpl(canvas, scale, canvas, surface, shim, fb));
return new DrawingContextImpl(canvas, visualBrushRenderer, scale, canvas, surface, shim, fb);
}
}
}

Просмотреть файл

@ -5,16 +5,15 @@ using System.Linq;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using SkiaSharp;
namespace Avalonia.Skia
{
public partial class PlatformRenderInterface : IPlatformRenderInterface, IRendererFactory
public partial class PlatformRenderInterface : IPlatformRenderInterface
{
public IBitmapImpl CreateBitmap(int width, int height)
{
return CreateRenderTargetBitmap(width, height);
return CreateRenderTargetBitmap(width, height, 96, 96);
}
public IFormattedTextImpl CreateFormattedText(
@ -67,12 +66,11 @@ namespace Avalonia.Skia
}
}
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
return new Renderer(root, renderLoop);
}
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height)
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(
int width,
int height,
double dpiX,
double dpiY)
{
if (width < 1)
throw new ArgumentException("Width can't be less than 1", nameof(width));

Просмотреть файл

@ -27,8 +27,7 @@ namespace Avalonia.Skia
{
var renderInterface = new PlatformRenderInterface();
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToConstant(renderInterface)
.Bind<IRendererFactory>().ToConstant(renderInterface);
.Bind<IPlatformRenderInterface>().ToConstant(renderInterface);
}
public static bool ForceSoftwareRendering

Просмотреть файл

@ -54,6 +54,7 @@
<Compile Include="Media\BrushImpl.cs" />
<Compile Include="Media\BrushWrapper.cs" />
<Compile Include="Media\DrawingContextImpl.cs" />
<Compile Include="Media\ImageBrushImpl.cs" />
<Compile Include="Media\Imaging\BitmapImpl.cs" />
<Compile Include="Media\Imaging\D2DBitmapImpl.cs" />
<Compile Include="Media\Imaging\RenderTargetBitmapImpl.cs" />
@ -62,7 +63,6 @@
<Compile Include="Media\RadialGradientBrushImpl.cs" />
<Compile Include="Media\LinearGradientBrushImpl.cs" />
<Compile Include="Media\AvaloniaTextRenderer.cs" />
<Compile Include="Media\TileBrushImpl.cs" />
<Compile Include="Media\SolidColorBrushImpl.cs" />
<Compile Include="Media\StreamGeometryContextImpl.cs" />
<Compile Include="Media\GeometryImpl.cs" />

Просмотреть файл

@ -27,7 +27,7 @@ namespace Avalonia
namespace Avalonia.Direct2D1
{
public class Direct2D1Platform : IPlatformRenderInterface, IRendererFactory
public class Direct2D1Platform : IPlatformRenderInterface
{
private static readonly Direct2D1Platform s_instance = new Direct2D1Platform();
@ -76,7 +76,6 @@ namespace Avalonia.Direct2D1
{
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToConstant(s_instance)
.Bind<IRendererFactory>().ToConstant(s_instance)
.BindToSelf(s_d2D1Factory)
.BindToSelf(s_dwfactory)
.BindToSelf(s_imagingFactory)
@ -107,11 +106,6 @@ namespace Avalonia.Direct2D1
spans);
}
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
return new Renderer(root, renderLoop);
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
var nativeWindow = surfaces?.OfType<IPlatformHandle>().FirstOrDefault();
@ -124,9 +118,20 @@ namespace Avalonia.Direct2D1
throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces");
}
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height)
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(
int width,
int height,
double dpiX,
double dpiY)
{
return new RenderTargetBitmapImpl(s_imagingFactory, s_d2D1Device.Factory, width, height);
return new RenderTargetBitmapImpl(
s_imagingFactory,
s_d2D1Factory,
s_dwfactory,
width,
height,
dpiX,
dpiY);
}
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = null)

Просмотреть файл

@ -6,6 +6,8 @@ using System.Collections;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.RenderHelpers;
using Avalonia.Rendering;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.Mathematics.Interop;
@ -18,30 +20,27 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
public class DrawingContextImpl : IDrawingContextImpl, IDisposable
{
/// <summary>
/// The Direct2D1 render target.
/// </summary>
private readonly IVisualBrushRenderer _visualBrushRenderer;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
/// <summary>
/// The DirectWrite factory.
/// </summary>
private readonly SharpDX.DXGI.SwapChain1 _swapChain;
private SharpDX.DirectWrite.Factory _directWriteFactory;
private SharpDX.DXGI.SwapChain1 _swapChain;
/// <summary>
/// Initializes a new instance of the <see cref="DrawingContextImpl"/> class.
/// </summary>
/// <param name="visualBrushRenderer">The visual brush renderer.</param>
/// <param name="renderTarget">The render target to draw to.</param>
/// <param name="directWriteFactory">The DirectWrite factory.</param>
/// <param name="swapChain">An optional swap chain associated with this drawing context.</param>
public DrawingContextImpl(
IVisualBrushRenderer visualBrushRenderer,
SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory,
SharpDX.DXGI.SwapChain1 swapChain = null)
{
_visualBrushRenderer = visualBrushRenderer;
_renderTarget = renderTarget;
_swapChain = swapChain;
_directWriteFactory = directWriteFactory;
_swapChain = swapChain;
_renderTarget.BeginDraw();
@ -72,10 +71,10 @@ namespace Avalonia.Direct2D1.Media
try
{
_renderTarget.EndDraw();
_swapChain?.Present(1, SharpDX.DXGI.PresentFlags.None);
}
catch (SharpDXException ex) when((uint)ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET
catch (SharpDXException ex) when ((uint)ex.HResult == 0x8899000C) // D2DERR_RECREATE_TARGET
{
throw new RenderTargetCorruptedException(ex);
}
@ -90,14 +89,38 @@ namespace Avalonia.Direct2D1.Media
/// <param name="destRect">The rect in the output to draw to.</param>
public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
{
var impl = (BitmapImpl)source;
Bitmap d2d = impl.GetDirect2DBitmap(_renderTarget);
_renderTarget.DrawBitmap(
d2d,
destRect.ToSharpDX(),
(float)opacity,
BitmapInterpolationMode.Linear,
sourceRect.ToSharpDX());
using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget))
{
_renderTarget.DrawBitmap(
d2d,
destRect.ToSharpDX(),
(float)opacity,
BitmapInterpolationMode.Linear,
sourceRect.ToSharpDX());
}
}
/// <summary>
/// Draws a bitmap image.
/// </summary>
/// <param name="source">The bitmap image.</param>
/// <param name="opacityMask">The opacity mask to draw with.</param>
/// <param name="opacityMaskRect">The destination rect for the opacity mask.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
using (var d2dSource = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget))
using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D()))
{
d2dOpacityMask.PlatformBrush.Transform = Matrix.CreateTranslation(opacityMaskRect.Position).ToDirect2D();
_renderTarget.FillGeometry(
geometry,
sourceBrush,
d2dOpacityMask.PlatformBrush);
}
}
/// <summary>
@ -283,7 +306,7 @@ namespace Avalonia.Direct2D1.Media
{
ContentBounds = PrimitiveExtensions.RectangleInfinite,
MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
Opacity = (float) opacity,
Opacity = (float)opacity,
};
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget);
@ -338,16 +361,46 @@ namespace Avalonia.Direct2D1.Media
}
else if (imageBrush != null)
{
return new TileBrushImpl(imageBrush, _renderTarget, destinationSize);
return new ImageBrushImpl(
imageBrush,
_renderTarget,
(BitmapImpl)imageBrush.Source.PlatformImpl,
destinationSize);
}
else if (visualBrush != null)
{
return new TileBrushImpl(visualBrush, _renderTarget, destinationSize);
}
else
{
return new SolidColorBrushImpl(null, _renderTarget);
if (_visualBrushRenderer != null)
{
var intermediateSize = _visualBrushRenderer.GetRenderTargetSize(visualBrush);
if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1)
{
using (var intermediate = new BitmapRenderTarget(
_renderTarget,
CompatibleRenderTargetOptions.None,
intermediateSize.ToSharpDX()))
{
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext(_visualBrushRenderer))
{
intermediate.Clear(null);
_visualBrushRenderer.RenderVisualBrush(ctx, visualBrush);
}
return new ImageBrushImpl(
visualBrush,
_renderTarget,
new D2DBitmapImpl(intermediate.Bitmap),
destinationSize);
}
}
}
else
{
throw new NotSupportedException("No IVisualBrushRenderer was supplied to DrawingContextImpl.");
}
}
return new SolidColorBrushImpl(null, _renderTarget);
}
public void PushGeometryClip(IGeometryImpl clip)

Просмотреть файл

@ -1,51 +1,48 @@
// 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 Avalonia.Media;
using Avalonia.RenderHelpers;
using Avalonia.Rendering.Utilities;
using SharpDX.Direct2D1;
namespace Avalonia.Direct2D1.Media
{
public sealed class TileBrushImpl : BrushImpl
public sealed class ImageBrushImpl : BrushImpl
{
public TileBrushImpl(
public ImageBrushImpl(
ITileBrush brush,
SharpDX.Direct2D1.RenderTarget target,
BitmapImpl bitmap,
Size targetSize)
{
var helper = new TileBrushImplHelper(brush, targetSize);
if (!helper.IsValid)
return;
var calc = new TileBrushCalculator(brush, new Size(bitmap.PixelWidth, bitmap.PixelHeight), targetSize);
using (var intermediate = new BitmapRenderTarget(target, CompatibleRenderTargetOptions.None, helper.IntermediateSize.ToSharpDX()))
if (!calc.NeedsIntermediate)
{
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext())
{
intermediate.Clear(null);
helper.DrawIntermediate(ctx);
}
PlatformBrush = new BitmapBrush(
target,
intermediate.Bitmap,
bitmap.GetDirect2DBitmap(target),
GetBitmapBrushProperties(brush),
GetBrushProperties(brush, helper.DestinationRect));
GetBrushProperties(brush, calc.DestinationRect));
}
else
{
using (var intermediate = RenderIntermediate(target, bitmap, calc))
{
PlatformBrush = new BitmapBrush(
target,
intermediate.Bitmap,
GetBitmapBrushProperties(brush),
GetBrushProperties(brush, calc.DestinationRect));
}
}
}
private static BrushProperties GetBrushProperties(ITileBrush brush, Rect destinationRect)
public override void Dispose()
{
var tileTransform =
brush.TileMode != TileMode.None ?
Matrix.CreateTranslation(destinationRect.X, destinationRect.Y) :
Matrix.Identity;
return new BrushProperties
{
Opacity = (float)brush.Opacity,
Transform = tileTransform.ToDirect2D(),
};
((BitmapBrush)PlatformBrush)?.Bitmap.Dispose();
base.Dispose();
}
private static BitmapBrushProperties GetBitmapBrushProperties(ITileBrush brush)
@ -59,6 +56,20 @@ namespace Avalonia.Direct2D1.Media
};
}
private static BrushProperties GetBrushProperties(ITileBrush brush, Rect destinationRect)
{
var tileTransform =
brush.TileMode != TileMode.None ?
Matrix.CreateTranslation(destinationRect.X, destinationRect.Y) :
Matrix.Identity;
return new BrushProperties
{
Opacity = (float)brush.Opacity,
Transform = tileTransform.ToDirect2D(),
};
}
private static ExtendMode GetExtendModeX(TileMode tileMode)
{
return (tileMode & TileMode.FlipX) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap;
@ -69,10 +80,28 @@ namespace Avalonia.Direct2D1.Media
return (tileMode & TileMode.FlipY) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap;
}
public override void Dispose()
private BitmapRenderTarget RenderIntermediate(
SharpDX.Direct2D1.RenderTarget target,
BitmapImpl bitmap,
TileBrushCalculator calc)
{
((BitmapBrush)PlatformBrush)?.Bitmap.Dispose();
base.Dispose();
var result = new BitmapRenderTarget(
target,
CompatibleRenderTargetOptions.None,
calc.IntermediateSize.ToSharpDX());
using (var context = new RenderTarget(result).CreateDrawingContext(null))
{
var rect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform;
context.DrawImage(bitmap, 1, rect, rect);
context.PopClip();
}
return result;
}
}
}

Просмотреть файл

@ -2,35 +2,41 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media
{
public class RenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly WicRenderTarget _target;
public RenderTargetBitmapImpl(
ImagingFactory imagingFactory,
Factory d2dFactory,
DirectWriteFactory dwriteFactory,
int width,
int height)
int height,
double dpiX,
double dpiY)
: base(imagingFactory, width, height)
{
var props = new RenderTargetProperties
{
DpiX = 96,
DpiY = 96,
DpiX = (float)dpiX,
DpiY = (float)dpiY,
};
_target = new WicRenderTarget(
d2dFactory,
WicImpl,
props);
_dwriteFactory = dwriteFactory;
}
public override void Dispose()
@ -39,7 +45,9 @@ namespace Avalonia.Direct2D1.Media
base.Dispose();
}
public Avalonia.Media.DrawingContext CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext();
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory);
}
}
}

Просмотреть файл

@ -116,14 +116,9 @@ namespace Avalonia.Direct2D1.Media
/// <returns>The Direct2D bitmap.</returns>
public override SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
{
if (_direct2D == null)
{
FormatConverter converter = new FormatConverter(_factory);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
_direct2D = SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
}
return _direct2D;
FormatConverter converter = new FormatConverter(_factory);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
}
/// <summary>

Просмотреть файл

@ -2,11 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Direct2D1.Media;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using SharpDX;
using Avalonia.Rendering;
using SharpDX.Direct2D1;
using DrawingContext = Avalonia.Media.DrawingContext;
using DwFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1
@ -48,10 +47,10 @@ namespace Avalonia.Direct2D1
/// <summary>
/// Creates a drawing context for a rendering session.
/// </summary>
/// <returns>An <see cref="Avalonia.Media.DrawingContext"/>.</returns>
public DrawingContext CreateDrawingContext()
/// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns>
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContext(new Media.DrawingContextImpl(_renderTarget, DirectWriteFactory));
return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory);
}
public void Dispose()

Просмотреть файл

@ -1,11 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.DXGI;
@ -14,6 +8,8 @@ using AlphaMode = SharpDX.Direct2D1.AlphaMode;
using Device = SharpDX.Direct2D1.Device;
using Factory = SharpDX.Direct2D1.Factory;
using Factory2 = SharpDX.DXGI.Factory2;
using Avalonia.Rendering;
using Avalonia.Direct2D1.Media;
namespace Avalonia.Direct2D1
{
@ -56,8 +52,8 @@ namespace Avalonia.Direct2D1
/// <summary>
/// Creates a drawing context for a rendering session.
/// </summary>
/// <returns>An <see cref="Avalonia.Media.DrawingContext"/>.</returns>
public DrawingContext CreateDrawingContext()
/// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns>
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
var size = GetWindowSize();
var dpi = GetWindowDpi();
@ -69,7 +65,11 @@ namespace Avalonia.Direct2D1
CreateSwapChain();
}
return new DrawingContext(new Media.DrawingContextImpl(_deviceContext, DirectWriteFactory, _swapChain));
return new DrawingContextImpl(
visualBrushRenderer,
_deviceContext,
DirectWriteFactory,
_swapChain);
}
public void Dispose()

Просмотреть файл

@ -35,7 +35,7 @@ namespace Avalonia
namespace Avalonia.Win32
{
partial class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader
partial class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IRendererFactory
{
private static readonly Win32Platform s_instance = new Win32Platform();
private static uint _uiThread;
@ -70,6 +70,7 @@ namespace Avalonia.Win32
.Bind<IPlatformSettings>().ToConstant(s_instance)
.Bind<IPlatformThreadingInterface>().ToConstant(s_instance)
.Bind<IRenderLoop>().ToConstant(new RenderLoop(60))
.Bind<IRendererFactory>().ToConstant(s_instance)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>()
.Bind<IWindowingPlatform>().ToConstant(s_instance)
.Bind<IPlatformIconLoader>().ToConstant(s_instance);
@ -197,5 +198,10 @@ namespace Avalonia.Win32
{
return new PopupImpl();
}
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
return new ImmediateRenderer(root);
}
}
}

Просмотреть файл

@ -1,509 +0,0 @@
// 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 Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
using System;
using System.Collections.Generic;
using System.IO;
using Xunit;
namespace Avalonia.Input.UnitTests
{
public class InputElement_HitTesting
{
[Fact]
public void InputHitTest_Should_Find_Control_At_Point()
{
using (var application = UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
{
var container = new Decorator
{
Width = 200,
Height = 200,
Child = new Border
{
Width = 100,
Height = 100,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
var result = container.InputHitTest(new Point(100, 100));
Assert.Equal(container.Child, result);
}
}
[Fact]
public void InputHitTest_Should_Not_Find_Control_Outside_Point()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
{
var container = new Decorator
{
Width = 200,
Height = 200,
Child = new Border
{
Width = 100,
Height = 100,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
var result = container.InputHitTest(new Point(10, 10));
Assert.Equal(container, result);
}
}
[Fact]
public void InputHitTest_Should_Find_Top_Control_At_Point()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
{
var container = new Panel
{
Width = 200,
Height = 200,
Children = new Controls.Controls
{
new Border
{
Width = 100,
Height = 100,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
},
new Border
{
Width = 50,
Height = 50,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
var result = container.InputHitTest(new Point(100, 100));
Assert.Equal(container.Children[1], result);
}
}
[Fact]
public void InputHitTest_Should_Find_Top_Control_At_Point_With_ZOrder()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
{
var container = new Panel
{
Width = 200,
Height = 200,
Children = new Controls.Controls
{
new Border
{
Width = 100,
Height = 100,
ZIndex = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
},
new Border
{
Width = 50,
Height = 50,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
var result = container.InputHitTest(new Point(100, 100));
Assert.Equal(container.Children[0], result);
}
}
[Fact]
public void InputHitTest_Should_Find_Control_Translated_Outside_Parent_Bounds()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
{
Border target;
var container = new Panel
{
Width = 200,
Height = 200,
ClipToBounds = false,
Children = new Controls.Controls
{
new Border
{
Width = 100,
Height = 100,
ZIndex = 1,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Child = target = new Border
{
Width = 50,
Height = 50,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
RenderTransform = new TranslateTransform(110, 110),
}
},
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
var result = container.InputHitTest(new Point(120, 120));
Assert.Equal(target, result);
}
}
[Fact]
public void InputHitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
{
Border target;
var container = new Panel
{
Width = 100,
Height = 200,
Children = new Controls.Controls
{
new Panel()
{
Width = 100,
Height = 100,
Margin = new Thickness(0, 100, 0, 0),
ClipToBounds = true,
Children = new Controls.Controls
{
(target = new Border()
{
Width = 100,
Height = 100,
Margin = new Thickness(0, -100, 0, 0)
})
}
}
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
var result = container.InputHitTest(new Point(50, 50));
Assert.NotEqual(target, result);
Assert.Equal(container, result);
}
}
[Fact]
public void InputHitTest_Should_Not_Find_Control_Outside_Scroll_ViewPort()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
{
Border target;
Border item1;
Border item2;
ScrollContentPresenter scroll;
var container = new Panel
{
Width = 100,
Height = 200,
Children = new Controls.Controls
{
(target = new Border()
{
Width = 100,
Height = 100
}),
new Border()
{
Width = 100,
Height = 100,
Margin = new Thickness(0, 100, 0, 0),
Child = scroll = new ScrollContentPresenter()
{
Content = new StackPanel()
{
Children = new Controls.Controls
{
(item1 = new Border()
{
Width = 100,
Height = 100,
}),
(item2 = new Border()
{
Width = 100,
Height = 100,
}),
}
}
}
}
}
};
scroll.UpdateChild();
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
var result = container.InputHitTest(new Point(50, 150));
Assert.Equal(item1, result);
result = container.InputHitTest(new Point(50, 50));
Assert.Equal(target, result);
scroll.Offset = new Vector(0, 100);
//we don't have setup LayoutManager so we will make it manually
scroll.Parent.InvalidateArrange();
container.InvalidateArrange();
container.Arrange(new Rect(container.DesiredSize));
context.Render(container);
result = container.InputHitTest(new Point(50, 150));
Assert.Equal(item2, result);
result = container.InputHitTest(new Point(50, 50));
Assert.NotEqual(item1, result);
Assert.Equal(target, result);
}
}
class MockRenderInterface : IPlatformRenderInterface
{
public IFormattedTextImpl CreateFormattedText(string text, Typeface typeface, TextAlignment textAlignment, TextWrapping wrapping, Size constraint, IReadOnlyList<FormattedTextStyleSpan> spans)
{
throw new NotImplementedException();
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
throw new NotImplementedException();
}
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height)
{
throw new NotImplementedException();
}
public IStreamGeometryImpl CreateStreamGeometry()
{
return new MockStreamGeometry();
}
public IBitmapImpl LoadBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(string fileName)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
{
throw new NotImplementedException();
}
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? fmt)
{
throw new NotImplementedException();
}
class MockStreamGeometry : Avalonia.Platform.IStreamGeometryImpl
{
private MockStreamGeometryContext _impl = new MockStreamGeometryContext();
public Rect Bounds
{
get
{
throw new NotImplementedException();
}
}
public Matrix Transform
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public IStreamGeometryImpl Clone()
{
return this;
}
public bool FillContains(Point point)
{
return _impl.FillContains(point);
}
public Rect GetRenderBounds(double strokeThickness)
{
throw new NotImplementedException();
}
public IGeometryImpl Intersect(IGeometryImpl geometry)
{
throw new NotImplementedException();
}
public IStreamGeometryContextImpl Open()
{
return _impl;
}
public bool StrokeContains(Pen pen, Point point)
{
throw new NotImplementedException();
}
public IGeometryImpl WithTransform(Matrix transform)
{
throw new NotImplementedException();
}
class MockStreamGeometryContext : IStreamGeometryContextImpl
{
private List<Point> points = new List<Point>();
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
throw new NotImplementedException();
}
public void BeginFigure(Point startPoint, bool isFilled)
{
points.Add(startPoint);
}
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
throw new NotImplementedException();
}
public void Dispose()
{
}
public void EndFigure(bool isClosed)
{
}
public void LineTo(Point point)
{
points.Add(point);
}
public void QuadraticBezierTo(Point control, Point endPoint)
{
throw new NotImplementedException();
}
public void SetFillRule(FillRule fillRule)
{
}
public bool FillContains(Point point)
{
// Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++)
{
var a = points[i];
var b = points[(i + 1) % points.Count];
var c = points[(i + 2) % points.Count];
Vector v0 = c - a;
Vector v1 = b - a;
Vector v2 = point - a;
var dot00 = v0 * v0;
var dot01 = v0 * v1;
var dot02 = v0 * v2;
var dot11 = v1 * v1;
var dot12 = v1 * v2;
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
if ((u >= 0) && (v >= 0) && (u + v < 1)) return true;
}
return false;
}
}
}
}
}
}

Просмотреть файл

@ -79,7 +79,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
var fb = new Framebuffer(fmt, 80, 80);
var r = Avalonia.AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
using (var target = r.CreateRenderTarget(new object[] { fb }))
using (var ctx = target.CreateDrawingContext())
using (var ctx = target.CreateDrawingContext(null))
{
ctx.PushOpacity(0.8);
ctx.FillRectangle(Brushes.Chartreuse, new Rect(0, 0, 20, 100));
@ -91,13 +91,13 @@ namespace Avalonia.Direct2D1.RenderTests.Media
fb.Deallocate();
using (var rtb = new RenderTargetBitmap(100, 100))
{
using (var ctx = rtb.CreateDrawingContext())
using (var ctx = rtb.CreateDrawingContext(null))
{
ctx.FillRectangle(Brushes.Blue, new Rect(0, 0, 100, 100));
ctx.FillRectangle(Brushes.Pink, new Rect(0, 20, 100, 10));
var rc = new Rect(0, 0, 60, 60);
ctx.DrawImage(bmp, 1, rc, rc);
ctx.DrawImage(bmp.PlatformImpl, 1, rc, rc);
}
rtb.Save(System.IO.Path.Combine(OutputPath, testName + ".out.png"));
}

Просмотреть файл

@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -31,18 +30,6 @@
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="InvariantCultureFixture.cs" />
<Compile Include="MockRendererFactory.cs" />
<Compile Include="NotifyingBase.cs" />
<Compile Include="TestLogSink.cs" />
<Compile Include="TestTemplatedRoot.cs" />
<Compile Include="TestRoot.cs" />
<Compile Include="TestServices.cs" />
<Compile Include="UnitTestApplication.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="MockWindowingPlatform.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Condition="'$(TargetFramework)' == 'net461'" Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
<ProjectReference Condition="'$(TargetFramework)' == 'netcoreapp1.1'" Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />

Просмотреть файл

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia.Media;
using Avalonia.Platform;
using Moq;
namespace Avalonia.UnitTests
{
public class MockPlatformRenderInterface : IPlatformRenderInterface
{
public IFormattedTextImpl CreateFormattedText(
string text,
Typeface typeface,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
IReadOnlyList<FormattedTextStyleSpan> spans)
{
return Mock.Of<IFormattedTextImpl>();
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
return Mock.Of<IRenderTarget>();
}
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(
int width,
int height,
double dpiX,
double dpiY)
{
return Mock.Of<IRenderTargetBitmapImpl>();
}
public IStreamGeometryImpl CreateStreamGeometry()
{
return new MockStreamGeometryImpl();
}
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = default(PixelFormat?))
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(Stream stream)
{
return Mock.Of<IBitmapImpl>();
}
public IBitmapImpl LoadBitmap(string fileName)
{
return Mock.Of<IBitmapImpl>();
}
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
{
throw new NotImplementedException();
}
}
}

Просмотреть файл

@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.UnitTests
{
public class MockStreamGeometryImpl : IStreamGeometryImpl
{
private MockStreamGeometryContext _context;
public MockStreamGeometryImpl()
{
Transform = Matrix.Identity;
_context = new MockStreamGeometryContext();
}
public MockStreamGeometryImpl(Matrix transform)
{
Transform = transform;
_context = new MockStreamGeometryContext();
}
private MockStreamGeometryImpl(Matrix transform, MockStreamGeometryContext context)
{
Transform = transform;
_context = context;
}
public Rect Bounds => _context.CalculateBounds();
public Matrix Transform { get; }
public IStreamGeometryImpl Clone()
{
return this;
}
public bool FillContains(Point point)
{
return _context.FillContains(point);
}
public bool StrokeContains(Pen pen, Point point)
{
return false;
}
public Rect GetRenderBounds(double strokeThickness) => Bounds;
public IGeometryImpl Intersect(IGeometryImpl geometry)
{
return new MockStreamGeometryImpl(Transform);
}
public IStreamGeometryContextImpl Open()
{
return _context;
}
public IGeometryImpl WithTransform(Matrix transform)
{
return new MockStreamGeometryImpl(transform, _context);
}
class MockStreamGeometryContext : IStreamGeometryContextImpl
{
private List<Point> points = new List<Point>();
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
}
public void BeginFigure(Point startPoint, bool isFilled)
{
points.Add(startPoint);
}
public Rect CalculateBounds()
{
var left = double.MaxValue;
var right = double.MinValue;
var top = double.MaxValue;
var bottom = double.MinValue;
foreach (var p in points)
{
left = Math.Min(p.X, left);
right = Math.Max(p.X, right);
top = Math.Min(p.Y, top);
bottom = Math.Max(p.Y, bottom);
}
return new Rect(new Point(left, top), new Point(right, bottom));
}
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
}
public void Dispose()
{
}
public void EndFigure(bool isClosed)
{
}
public void LineTo(Point point)
{
points.Add(point);
}
public void QuadraticBezierTo(Point control, Point endPoint)
{
throw new NotImplementedException();
}
public void SetFillRule(FillRule fillRule)
{
}
public bool FillContains(Point point)
{
// Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++)
{
var a = points[i];
var b = points[(i + 1) % points.Count];
var c = points[(i + 2) % points.Count];
Vector v0 = c - a;
Vector v1 = b - a;
Vector v2 = point - a;
var dot00 = v0 * v0;
var dot01 = v0 * v1;
var dot02 = v0 * v2;
var dot11 = v1 * v1;
var dot12 = v1 * v2;
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
if ((u >= 0) && (v >= 0) && (u + v < 1)) return true;
}
return false;
}
}
}
}

Просмотреть файл

@ -23,7 +23,7 @@ namespace Avalonia.UnitTests
assetLoader: new AssetLoader(),
layoutManager: new LayoutManager(),
platform: new AppBuilder().RuntimePlatform,
renderer: Mock.Of<IRenderer>(),
renderer: (_, __) => Mock.Of<IRenderer>(),
renderInterface: CreateRenderInterfaceMock(),
renderLoop: Mock.Of<IRenderLoop>(),
standardCursorFactory: Mock.Of<IStandardCursorFactory>(),
@ -65,7 +65,7 @@ namespace Avalonia.UnitTests
Func<IKeyboardDevice> keyboardDevice = null,
ILayoutManager layoutManager = null,
IRuntimePlatform platform = null,
IRenderer renderer = null,
Func<IRenderRoot, IRenderLoop, IRenderer> renderer = null,
IPlatformRenderInterface renderInterface = null,
IRenderLoop renderLoop = null,
IScheduler scheduler = null,
@ -98,7 +98,7 @@ namespace Avalonia.UnitTests
public Func<IKeyboardDevice> KeyboardDevice { get; }
public ILayoutManager LayoutManager { get; }
public IRuntimePlatform Platform { get; }
public IRenderer Renderer { get; }
public Func<IRenderRoot, IRenderLoop, IRenderer> Renderer { get; }
public IPlatformRenderInterface RenderInterface { get; }
public IRenderLoop RenderLoop { get; }
public IScheduler Scheduler { get; }
@ -115,7 +115,7 @@ namespace Avalonia.UnitTests
Func<IKeyboardDevice> keyboardDevice = null,
ILayoutManager layoutManager = null,
IRuntimePlatform platform = null,
IRenderer renderer = null,
Func<IRenderRoot, IRenderLoop, IRenderer> renderer = null,
IPlatformRenderInterface renderInterface = null,
IRenderLoop renderLoop = null,
IScheduler scheduler = null,

Просмотреть файл

@ -51,7 +51,7 @@ namespace Avalonia.UnitTests
.Bind<IKeyboardDevice>().ToConstant(Services.KeyboardDevice?.Invoke())
.Bind<ILayoutManager>().ToConstant(Services.LayoutManager)
.Bind<IRuntimePlatform>().ToConstant(Services.Platform)
.Bind<IRenderer>().ToConstant(Services.Renderer)
.Bind<IRendererFactory>().ToConstant(new RendererFactory(Services.Renderer))
.Bind<IPlatformRenderInterface>().ToConstant(Services.RenderInterface)
.Bind<IRenderLoop>().ToConstant(Services.RenderLoop)
.Bind<IPlatformThreadingInterface>().ToConstant(Services.ThreadingInterface)
@ -67,5 +67,20 @@ namespace Avalonia.UnitTests
Styles.AddRange(styles);
}
}
private class RendererFactory : IRendererFactory
{
Func<IRenderRoot, IRenderLoop, IRenderer> _func;
public RendererFactory(Func<IRenderRoot, IRenderLoop, IRenderer> func)
{
_func = func;
}
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
return _func?.Invoke(root, renderLoop);
}
}
}
}

Просмотреть файл

@ -163,7 +163,7 @@ namespace Avalonia.Visuals.UnitTests
var ctx = CreateDrawingContext();
control.Measure(Size.Infinity);
control.Arrange(new Rect(control.DesiredSize));
ctx.Render(control);
ImmediateRenderer.Render(control, ctx);
}
private DrawingContext CreateDrawingContext()

Просмотреть файл

@ -1,39 +0,0 @@
// 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 Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Visuals.UnitTests
{
public class TestRoot : TestVisual, IRenderRoot
{
public Size ClientSize { get; }
public IRenderTarget CreateRenderTarget()
{
throw new NotImplementedException();
}
public void Invalidate(Rect rect)
{
throw new NotImplementedException();
}
public IRenderer Renderer
{
get { throw new NotImplementedException(); }
}
public Point PointToClient(Point p)
{
throw new NotImplementedException();
}
public Point PointToScreen(Point p)
{
throw new NotImplementedException();
}
}
}

Просмотреть файл

@ -5,7 +5,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
namespace Avalonia.Visuals.UnitTests
@ -66,14 +69,25 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Adding_Children_Should_Fire_OnAttachedToVisualTree()
{
var child2 = new TestVisual();
var child1 = new TestVisual { Child = child2 };
var child2 = new Decorator();
var child1 = new Decorator { Child = child2 };
var root = new TestRoot();
var called1 = false;
var called2 = false;
child1.AttachedToVisualTree += (s, e) => called1 = true;
child2.AttachedToVisualTree += (s, e) => called2 = true;
child1.AttachedToVisualTree += (s, e) =>
{
Assert.Equal(e.Parent, root);
Assert.Equal(e.Root, root);
called1 = true;
};
child2.AttachedToVisualTree += (s, e) =>
{
Assert.Equal(e.Parent, root);
Assert.Equal(e.Root, root);
called2 = true;
};
root.Child = child1;
@ -84,31 +98,100 @@ namespace Avalonia.Visuals.UnitTests
[Fact]
public void Removing_Children_Should_Fire_OnDetachedFromVisualTree()
{
var child2 = new TestVisual();
var child1 = new TestVisual { Child = child2 };
var child2 = new Decorator();
var child1 = new Decorator { Child = child2 };
var root = new TestRoot();
var called1 = false;
var called2 = false;
root.Child = child1;
child1.DetachedFromVisualTree += (s, e) => called1 = true;
child2.DetachedFromVisualTree += (s, e) => called2 = true;
child1.DetachedFromVisualTree += (s, e) =>
{
Assert.Equal(e.Parent, root);
Assert.Equal(e.Root, root);
called1 = true;
};
child2.DetachedFromVisualTree += (s, e) =>
{
Assert.Equal(e.Parent, root);
Assert.Equal(e.Root, root);
called2 = true;
};
root.Child = null;
Assert.True(called1);
Assert.True(called2);
}
[Fact]
public void Root_Should_Retun_Self_As_VisualRoot()
{
var root = new TestRoot();
Assert.Same(root, ((IVisual)root).VisualRoot);
}
[Fact]
public void Descendents_Should_RetunVisualRoot()
{
var root = new TestRoot();
var child1 = new Decorator();
var child2 = new Decorator();
root.Child = child1;
child1.Child = child2;
Assert.Same(root, ((IVisual)child1).VisualRoot);
Assert.Same(root, ((IVisual)child2).VisualRoot);
}
[Fact]
public void Attaching_To_Visual_Tree_Should_Invalidate_Visual()
{
var renderer = new Mock<IRenderer>();
using (UnitTestApplication.Start(new TestServices(renderer: (root, loop) => renderer.Object)))
{
var child = new Decorator();
var root = new TestRoot();
root.Child = child;
renderer.Verify(x => x.AddDirty(child));
}
}
[Fact]
public void Detaching_From_Visual_Tree_Should_Invalidate_Visual()
{
var renderer = new Mock<IRenderer>();
using (UnitTestApplication.Start(new TestServices(renderer: (root, loop) => renderer.Object)))
{
var child = new Decorator();
var root = new TestRoot();
root.Child = child;
renderer.ResetCalls();
root.Child = null;
renderer.Verify(x => x.AddDirty(child));
}
}
[Fact]
public void Adding_Already_Parented_Control_Should_Throw()
{
var root1 = new TestRoot();
var root2 = new TestRoot();
var child = new TestVisual();
var child = new Canvas();
root1.AddChild(child);
root1.Child = child;
Assert.Throws<InvalidOperationException>(() => root2.AddChild(child));
Assert.Throws<InvalidOperationException>(() => root2.Child = child);
Assert.Equal(0, root2.GetVisualChildren().Count());
}
}

Просмотреть файл

@ -44,7 +44,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
tree.Measure(Size.Infinity);
tree.Arrange(new Rect(0, 0, 100, 100));
context.Render(tree);
ImmediateRenderer.Render(tree, context);
var track = target.Track(control);
var results = new List<TransformedBounds?>();

Просмотреть файл

@ -24,7 +24,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException();
}
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height)
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height, double dpiX, double dpiY)
{
throw new NotImplementedException();
}

Просмотреть файл

@ -12,6 +12,8 @@ using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
using System;
using Avalonia.Controls.Shapes;
namespace Avalonia.Visuals.UnitTests.VisualTree
{
@ -20,9 +22,37 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
[Fact]
public void GetVisualsAt_Should_Find_Controls_At_Point()
{
using (var application = UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
using (TestApplication())
{
var container = new Decorator
var container = new TestRoot
{
Width = 200,
Height = 200,
Child = new Border
{
Width = 100,
Height = 100,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var result = container.GetVisualsAt(new Point(100, 100));
Assert.Equal(new[] { container.Child }, result);
}
}
[Fact]
public void GetVisualsAt_Should_Not_Find_Empty_Controls_At_Point()
{
using (TestApplication())
{
var container = new TestRoot
{
Width = 200,
Height = 200,
@ -38,21 +68,19 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
var result = container.GetVisualsAt(new Point(100, 100));
Assert.Equal(new[] { container.Child, container }, result);
Assert.Empty(result);
}
}
[Fact]
public void GetVisualsAt_Should_Not_Find_Invisible_Controls_At_Point()
{
using (var application = UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
using (TestApplication())
{
var container = new Decorator
Border visible;
var container = new TestRoot
{
Width = 200,
Height = 200,
@ -60,11 +88,13 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
{
Width = 100,
Height = 100,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
IsVisible = false,
Child = new Border
Child = visible = new Border
{
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
}
@ -74,21 +104,18 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
var result = container.GetVisualsAt(new Point(100, 100));
Assert.Equal(new[] { container }, result);
Assert.Empty(result);
}
}
[Fact]
public void GetVisualsAt_Should_Not_Find_Control_Outside_Point()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
using (TestApplication())
{
var container = new Decorator
var container = new TestRoot
{
Width = 200,
Height = 200,
@ -96,6 +123,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
{
Width = 100,
Height = 100,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
@ -104,142 +132,150 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
var result = container.GetVisualsAt(new Point(10, 10));
Assert.Equal(new[] { container }, result);
Assert.Empty(result);
}
}
[Fact]
public void GetVisualsAt_Should_Return_Top_Controls_First()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
using (TestApplication())
{
var container = new Panel
Panel container;
var root = new TestRoot
{
Width = 200,
Height = 200,
Children = new Controls.Controls
Child = container = new Panel
{
new Border
Width = 200,
Height = 200,
Children = new Controls.Controls
{
Width = 100,
Height = 100,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
},
new Border
{
Width = 50,
Height = 50,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
new Border
{
Width = 100,
Height = 100,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
},
new Border
{
Width = 50,
Height = 50,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
}
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
var result = container.GetVisualsAt(new Point(100, 100));
Assert.Equal(new[] { container.Children[1], container.Children[0], container }, result);
Assert.Equal(new[] { container.Children[1], container.Children[0] }, result);
}
}
[Fact]
public void GetVisualsAt_Should_Return_Top_Controls_First_With_ZIndex()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
using (TestApplication())
{
var container = new Panel
Panel container;
var root = new TestRoot
{
Width = 200,
Height = 200,
Children = new Controls.Controls
Child = container = new Panel
{
new Border
Width = 200,
Height = 200,
Children = new Controls.Controls
{
Width = 100,
Height = 100,
ZIndex = 1,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
},
new Border
{
Width = 50,
Height = 50,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
},
new Border
{
Width = 75,
Height = 75,
ZIndex = 2,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
new Border
{
Width = 100,
Height = 100,
ZIndex = 1,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
},
new Border
{
Width = 50,
Height = 50,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
},
new Border
{
Width = 75,
Height = 75,
ZIndex = 2,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
}
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
var result = container.GetVisualsAt(new Point(100, 100));
Assert.Equal(new[] { container.Children[2], container.Children[0], container.Children[1], container }, result);
Assert.Equal(new[] { container.Children[2], container.Children[0], container.Children[1] }, result);
}
}
[Fact]
public void GetVisualsAt_Should_Find_Control_Translated_Outside_Parent_Bounds()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
using (TestApplication())
{
Border target;
var container = new Panel
Panel container;
var root = new TestRoot
{
Width = 200,
Height = 200,
ClipToBounds = false,
Children = new Controls.Controls
Child = container = new Panel
{
new Border
Width = 200,
Height = 200,
Background = Brushes.Red,
ClipToBounds = false,
Children = new Controls.Controls
{
Width = 100,
Height = 100,
ZIndex = 1,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Child = target = new Border
new Border
{
Width = 50,
Height = 50,
Width = 100,
Height = 100,
ZIndex = 1,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
RenderTransform = new TranslateTransform(110, 110),
}
},
Child = target = new Border
{
Width = 50,
Height = 50,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
RenderTransform = new TranslateTransform(110, 110),
}
},
}
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
var result = container.GetVisualsAt(new Point(120, 120));
Assert.Equal(new IVisual[] { target, container }, result);
@ -249,40 +285,43 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
[Fact]
public void GetVisualsAt_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
using (TestApplication())
{
Border target;
var container = new Panel
Panel container;
var root = new TestRoot
{
Width = 100,
Height = 200,
Children = new Controls.Controls
Child = container = new Panel
{
new Panel()
Width = 100,
Height = 200,
Background = Brushes.Red,
Children = new Controls.Controls
{
Width = 100,
Height = 100,
Margin = new Thickness(0, 100, 0, 0),
ClipToBounds = true,
Children = new Controls.Controls
new Panel()
{
(target = new Border()
Width = 100,
Height = 100,
Background = Brushes.Red,
Margin = new Thickness(0, 100, 0, 0),
ClipToBounds = true,
Children = new Controls.Controls
{
Width = 100,
Height = 100,
Margin = new Thickness(0, -100, 0, 0)
})
(target = new Border()
{
Width = 100,
Height = 100,
Background = Brushes.Red,
Margin = new Thickness(0, -100, 0, 0)
})
}
}
}
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
var result = container.GetVisualsAt(new Point(50, 50));
@ -293,45 +332,57 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
[Fact]
public void GetVisualsAt_Should_Not_Find_Control_Outside_Scroll_Viewport()
{
using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface())))
using (TestApplication())
{
Border target;
Border item1;
Border item2;
ScrollContentPresenter scroll;
var container = new Panel
Panel container;
var root = new TestRoot
{
Width = 100,
Height = 200,
Children = new Controls.Controls
Child = container = new Panel
{
(target = new Border()
Width = 100,
Height = 200,
Background = Brushes.Red,
Children = new Controls.Controls
{
Width = 100,
Height = 100
}),
new Border()
{
Width = 100,
Height = 100,
Margin = new Thickness(0, 100, 0, 0),
Child = scroll = new ScrollContentPresenter()
(target = new Border()
{
Content = new StackPanel()
Name = "b1",
Width = 100,
Height = 100,
Background = Brushes.Red,
}),
new Border()
{
Name = "b2",
Width = 100,
Height = 100,
Background = Brushes.Red,
Margin = new Thickness(0, 100, 0, 0),
Child = scroll = new ScrollContentPresenter()
{
Children = new Controls.Controls
Content = new StackPanel()
{
(item1 = new Border()
Children = new Controls.Controls
{
Width = 100,
Height = 100,
}),
(item2 = new Border()
{
Width = 100,
Height = 100,
}),
(item1 = new Border()
{
Name = "b3",
Width = 100,
Height = 100,
Background = Brushes.Red,
}),
(item2 = new Border()
{
Name = "b4",
Width = 100,
Height = 100,
Background = Brushes.Red,
}),
}
}
}
}
@ -341,11 +392,8 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
scroll.UpdateChild();
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
context.Render(container);
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
var result = container.GetVisualsAt(new Point(50, 150)).First();
@ -357,22 +405,96 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
scroll.Offset = new Vector(0, 100);
//we don't have setup LayoutManager so we will make it manually
// We don't have LayoutManager set up so do the layout pass manually.
scroll.Parent.InvalidateArrange();
container.InvalidateArrange();
container.Arrange(new Rect(container.DesiredSize));
context.Render(container);
result = container.GetVisualsAt(new Point(50, 150)).First();
Assert.Equal(item2, result);
result = container.GetVisualsAt(new Point(50, 50)).First();
Assert.NotEqual(item1, result);
Assert.Equal(target, result);
}
}
[Fact]
public void GetVisualsAt_Should_Not_Find_Path_When_Outside_Fill()
{
using (TestApplication())
{
Path path;
var container = new TestRoot
{
Width = 200,
Height = 200,
Child = path = new Path
{
Width = 200,
Height = 200,
Fill = Brushes.Red,
Data = StreamGeometry.Parse("M100,0 L0,100 100,100")
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
var result = container.GetVisualsAt(new Point(100, 100));
Assert.Equal(new[] { path }, result);
result = container.GetVisualsAt(new Point(10, 10));
Assert.Empty(result);
}
}
[Fact]
public void GetVisualsAt_Should_Respect_Geometry_Clip()
{
using (TestApplication())
{
Border border;
Canvas canvas;
var container = new TestRoot
{
Width = 400,
Height = 400,
Child = border = new Border
{
Background = Brushes.Red,
Clip = StreamGeometry.Parse("M100,0 L0,100 100,100"),
Width = 200,
Height = 200,
Child = canvas = new Canvas
{
Background = Brushes.Yellow,
Margin = new Thickness(10),
}
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
Assert.Equal(new Rect(100, 100, 200, 200), border.Bounds);
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
var result = container.GetVisualsAt(new Point(200, 200));
Assert.Equal(new IVisual[] { canvas, border }, result);
result = container.GetVisualsAt(new Point(110, 110));
Assert.Empty(result);
}
}
private IDisposable TestApplication()
{
return UnitTestApplication.Start(
new TestServices(
renderInterface: new MockPlatformRenderInterface(),
renderer: (root, loop) => new ImmediateRenderer(root)));
}
}
}