зеркало из https://github.com/AvaloniaUI/Avalonia.git
Ported ImmediateRenderer from scenegraph branch.
This commit is contained in:
Родитель
efce48b604
Коммит
1b9d61416f
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче