Browse Source

Merge remote-tracking branch 'upstream/master' into remote2

pull/1105/head
Nikita Tsukanov 9 years ago
parent
commit
0a53aabc35
  1. 5
      samples/interop/Direct3DInteropSample/MainWindow.cs
  2. 2
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  3. 3
      src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
  4. 13
      src/Avalonia.Base/Threading/Dispatcher.cs
  5. 18
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  6. 15
      src/Avalonia.Controls/MenuItem.cs
  7. 4
      src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
  8. 29
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  9. 34
      src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs
  10. 161
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  11. 11
      src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs
  12. 10
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  13. 9
      src/Avalonia.Visuals/Rendering/RenderLayers.cs
  14. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  15. 2
      src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
  16. 33
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  17. 6
      src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs
  18. 28
      src/Gtk/Avalonia.Gtk3/X11.cs
  19. 3
      src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs
  20. 43
      src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs
  21. 1
      src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs
  22. 87
      src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs
  23. 6
      src/Markup/Avalonia.Markup/DefaultValueConverter.cs
  24. 2
      src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs
  25. 6
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  26. 4
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  27. 28
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  28. 2
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  29. 10
      src/Windows/Avalonia.Direct2D1/ILayerFactory.cs
  30. 39
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  31. 7
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  32. 32
      src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
  33. 47
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  34. 68
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  35. 58
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  36. 6
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  37. 22
      src/Windows/Avalonia.Direct2D1/OptionalDispose.cs
  38. 34
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  39. 40
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  40. 2
      src/Windows/Avalonia.Win32/Win32Platform.cs
  41. 2
      src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs
  42. 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  43. 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs
  44. 2
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs
  45. 4
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  46. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  47. 8
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  48. 2
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  49. 22
      tests/Avalonia.LeakTests/ExpressionObserverTests.cs
  50. 87
      tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs
  51. 38
      tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs
  52. 105
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs
  53. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs
  54. 4
      tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs
  55. 2
      tests/Avalonia.RenderTests/TestBase.cs
  56. 2
      tests/Avalonia.UnitTests/TestRoot.cs
  57. 70
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

5
samples/interop/Direct3DInteropSample/MainWindow.cs

@ -253,8 +253,9 @@ namespace Direct3DInteropSample
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{ {
return new DrawingContextImpl(visualBrushRenderer, _window._d2dRenderTarget, return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget,
AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>()); AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>(),
AvaloniaLocator.Current.GetService<ImagingFactory>());
} }
} }

2
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@ -30,7 +30,7 @@ namespace Avalonia.Android
return; return;
} }
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{ {
if (interval.TotalMilliseconds < 10) if (interval.TotalMilliseconds < 10)
interval = TimeSpan.FromMilliseconds(10); interval = TimeSpan.FromMilliseconds(10);

3
src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs

@ -17,10 +17,11 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// Starts a timer. /// Starts a timer.
/// </summary> /// </summary>
/// <param name="priority"></param>
/// <param name="interval">The interval.</param> /// <param name="interval">The interval.</param>
/// <param name="tick">The action to call on each tick.</param> /// <param name="tick">The action to call on each tick.</param>
/// <returns>An <see cref="IDisposable"/> used to stop the timer.</returns> /// <returns>An <see cref="IDisposable"/> used to stop the timer.</returns>
IDisposable StartTimer(TimeSpan interval, Action tick); IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick);
void Signal(DispatcherPriority priority); void Signal(DispatcherPriority priority);

13
src/Avalonia.Base/Threading/Dispatcher.cs

@ -84,6 +84,19 @@ namespace Avalonia.Threading
_jobRunner?.Post(action, priority); _jobRunner?.Post(action, priority);
} }
/// <summary>
/// This is needed for platform backends that don't have internal priority system (e. g. win32)
/// To ensure that there are no jobs with higher priority
/// </summary>
/// <param name="currentPriority"></param>
internal void EnsurePriority(DispatcherPriority currentPriority)
{
if (currentPriority == DispatcherPriority.MaxValue)
return;
currentPriority += 1;
_jobRunner.RunJobs(currentPriority);
}
/// <summary> /// <summary>
/// Allows unit tests to change the platform threading interface. /// Allows unit tests to change the platform threading interface.
/// </summary> /// </summary>

18
src/Avalonia.Base/Threading/DispatcherTimer.cs

@ -17,13 +17,11 @@ namespace Avalonia.Threading
private readonly DispatcherPriority _priority; private readonly DispatcherPriority _priority;
private TimeSpan _interval; private TimeSpan _interval;
private readonly Action _raiseTickAction;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DispatcherTimer"/> class. /// Initializes a new instance of the <see cref="DispatcherTimer"/> class.
/// </summary> /// </summary>
public DispatcherTimer() : this(DispatcherPriority.Normal) public DispatcherTimer() : this(DispatcherPriority.Background)
{ {
} }
@ -34,7 +32,6 @@ namespace Avalonia.Threading
public DispatcherTimer(DispatcherPriority priority) public DispatcherTimer(DispatcherPriority priority)
{ {
_priority = priority; _priority = priority;
_raiseTickAction = RaiseTick;
} }
/// <summary> /// <summary>
@ -187,7 +184,7 @@ namespace Avalonia.Threading
throw new Exception("Could not start timer: IPlatformThreadingInterface is not registered."); throw new Exception("Could not start timer: IPlatformThreadingInterface is not registered.");
} }
_timer = threading.StartTimer(Interval, InternalTick); _timer = threading.StartTimer(_priority, Interval, InternalTick);
} }
} }
@ -210,14 +207,7 @@ namespace Avalonia.Threading
/// </summary> /// </summary>
private void InternalTick() private void InternalTick()
{ {
Dispatcher.UIThread.InvokeAsync(_raiseTickAction, _priority); Dispatcher.UIThread.EnsurePriority(_priority);
}
/// <summary>
/// Raises the <see cref="Tick"/> event.
/// </summary>
private void RaiseTick()
{
Tick?.Invoke(this, EventArgs.Empty); Tick?.Invoke(this, EventArgs.Empty);
} }
} }

15
src/Avalonia.Controls/MenuItem.cs

@ -307,6 +307,21 @@ namespace Avalonia.Controls
() => IsSubMenuOpen = true, () => IsSubMenuOpen = true,
TimeSpan.FromMilliseconds(400)); TimeSpan.FromMilliseconds(400));
} }
else
{
var parentItem = Parent as MenuItem;
if (parentItem != null)
{
foreach (var sibling in parentItem.Items
.OfType<MenuItem>()
.Where(x => x != this && x.IsSubMenuOpen))
{
sibling.CloseSubmenus();
sibling.IsSubMenuOpen = false;
sibling.IsSelected = false;
}
}
}
} }
/// <summary> /// <summary>

4
src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs

@ -14,7 +14,7 @@ namespace Avalonia.Controls.Platform
public InternalPlatformThreadingInterface() public InternalPlatformThreadingInterface()
{ {
TlsCurrentThreadIsLoopThread = true; TlsCurrentThreadIsLoopThread = true;
StartTimer(new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs())); StartTimer(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs()));
} }
private readonly AutoResetEvent _signaled = new AutoResetEvent(false); private readonly AutoResetEvent _signaled = new AutoResetEvent(false);
@ -72,7 +72,7 @@ namespace Avalonia.Controls.Platform
} }
} }
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{ {
return new WatTimer(new System.Threading.Timer(delegate return new WatTimer(new System.Threading.Timer(delegate
{ {

29
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -80,12 +80,29 @@ namespace Avalonia.Platform
/// <param name="cornerRadius">The corner radius.</param> /// <param name="cornerRadius">The corner radius.</param>
void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f); void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f);
/// <summary>
/// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer
/// for the current render target.
/// </summary>
/// <param name="size">The size of the layer in DIPs.</param>
/// <returns>An <see cref="IRenderTargetBitmapImpl"/></returns>
/// <remarks>
/// Depending on the rendering backend used, a layer created via this method may be more
/// performant than a standard render target bitmap. In particular the Direct2D backend
/// has to do a format conversion each time a standard render target bitmap is rendered,
/// but a layer created via this method has no such overhead.
/// </remarks>
IRenderTargetBitmapImpl CreateLayer(Size size);
/// <summary> /// <summary>
/// Pushes a clip rectange. /// Pushes a clip rectange.
/// </summary> /// </summary>
/// <param name="clip">The clip rectangle.</param> /// <param name="clip">The clip rectangle.</param>
void PushClip(Rect clip); void PushClip(Rect clip);
/// <summary>
/// Pops the latest pushed clip rectangle.
/// </summary>
void PopClip(); void PopClip();
/// <summary> /// <summary>
@ -94,10 +111,19 @@ namespace Avalonia.Platform
/// <param name="opacity">The opacity.</param> /// <param name="opacity">The opacity.</param>
void PushOpacity(double opacity); void PushOpacity(double opacity);
/// <summary>
/// Pops the latest pushed opacity value.
/// </summary>
void PopOpacity(); void PopOpacity();
/// <summary>
/// Pushes an opacity mask
/// </summary>
void PushOpacityMask(IBrush mask, Rect bounds); void PushOpacityMask(IBrush mask, Rect bounds);
/// <summary>
/// Pops the latest pushed opacity mask.
/// </summary>
void PopOpacityMask(); void PopOpacityMask();
/// <summary> /// <summary>
@ -106,6 +132,9 @@ namespace Avalonia.Platform
/// <param name="clip">The clip geometry.</param> /// <param name="clip">The clip geometry.</param>
void PushGeometryClip(IGeometryImpl clip); void PushGeometryClip(IGeometryImpl clip);
/// <summary>
/// Pops the latest pushed geometry clip.
/// </summary>
void PopGeometryClip(); void PopGeometryClip();
} }
} }

34
src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs

@ -1,34 +0,0 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class DefaultRenderLayerFactory : IRenderLayerFactory
{
private IPlatformRenderInterface _renderInterface;
public DefaultRenderLayerFactory()
: this(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>())
{
}
public DefaultRenderLayerFactory(IPlatformRenderInterface renderInterface)
{
_renderInterface = renderInterface;
}
public IRenderTargetBitmapImpl CreateLayer(
IVisual layerRoot,
Size size,
double dpiX,
double dpiY)
{
return _renderInterface.CreateRenderTargetBitmap(
(int)Math.Ceiling(size.Width),
(int)Math.Ceiling(size.Height),
dpiX,
dpiY);
}
}
}

161
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -26,7 +26,6 @@ namespace Avalonia.Rendering
private readonly IVisual _root; private readonly IVisual _root;
private readonly ISceneBuilder _sceneBuilder; private readonly ISceneBuilder _sceneBuilder;
private readonly RenderLayers _layers; private readonly RenderLayers _layers;
private readonly IRenderLayerFactory _layerFactory;
private bool _running; private bool _running;
private Scene _scene; private Scene _scene;
@ -45,13 +44,11 @@ namespace Avalonia.Rendering
/// <param name="root">The control to render.</param> /// <param name="root">The control to render.</param>
/// <param name="renderLoop">The render loop.</param> /// <param name="renderLoop">The render loop.</param>
/// <param name="sceneBuilder">The scene builder to use. Optional.</param> /// <param name="sceneBuilder">The scene builder to use. Optional.</param>
/// <param name="layerFactory">The layer factory to use. Optional.</param>
/// <param name="dispatcher">The dispatcher to use. Optional.</param> /// <param name="dispatcher">The dispatcher to use. Optional.</param>
public DeferredRenderer( public DeferredRenderer(
IRenderRoot root, IRenderRoot root,
IRenderLoop renderLoop, IRenderLoop renderLoop,
ISceneBuilder sceneBuilder = null, ISceneBuilder sceneBuilder = null,
IRenderLayerFactory layerFactory = null,
IDispatcher dispatcher = null) IDispatcher dispatcher = null)
{ {
Contract.Requires<ArgumentNullException>(root != null); Contract.Requires<ArgumentNullException>(root != null);
@ -59,8 +56,7 @@ namespace Avalonia.Rendering
_dispatcher = dispatcher ?? Dispatcher.UIThread; _dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root; _root = root;
_sceneBuilder = sceneBuilder ?? new SceneBuilder(); _sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); _layers = new RenderLayers();
_layers = new RenderLayers(_layerFactory);
_renderLoop = renderLoop; _renderLoop = renderLoop;
} }
@ -70,15 +66,13 @@ namespace Avalonia.Rendering
/// <param name="root">The control to render.</param> /// <param name="root">The control to render.</param>
/// <param name="renderTarget">The render target.</param> /// <param name="renderTarget">The render target.</param>
/// <param name="sceneBuilder">The scene builder to use. Optional.</param> /// <param name="sceneBuilder">The scene builder to use. Optional.</param>
/// <param name="layerFactory">The layer factory to use. Optional.</param>
/// <remarks> /// <remarks>
/// This constructor is intended to be used for unit testing. /// This constructor is intended to be used for unit testing.
/// </remarks> /// </remarks>
public DeferredRenderer( public DeferredRenderer(
IVisual root, IVisual root,
IRenderTarget renderTarget, IRenderTarget renderTarget,
ISceneBuilder sceneBuilder = null, ISceneBuilder sceneBuilder = null)
IRenderLayerFactory layerFactory = null)
{ {
Contract.Requires<ArgumentNullException>(root != null); Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(renderTarget != null); Contract.Requires<ArgumentNullException>(renderTarget != null);
@ -86,8 +80,7 @@ namespace Avalonia.Rendering
_root = root; _root = root;
_renderTarget = renderTarget; _renderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder(); _sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory(); _layers = new RenderLayers();
_layers = new RenderLayers(_layerFactory);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -180,38 +173,60 @@ namespace Avalonia.Rendering
bool renderOverlay = DrawDirtyRects || DrawFps; bool renderOverlay = DrawDirtyRects || DrawFps;
bool composite = false; bool composite = false;
if (_renderTarget == null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
if (renderOverlay) if (renderOverlay)
{ {
_dirtyRectsDisplay.Tick(); _dirtyRectsDisplay.Tick();
} }
if (scene != null && scene.Size != Size.Empty) try
{ {
if (scene.Generation != _lastSceneId) if (scene != null && scene.Size != Size.Empty)
{ {
_layers.Update(scene); IDrawingContextImpl context = null;
RenderToLayers(scene);
if (DebugFramesPath != null) if (scene.Generation != _lastSceneId)
{ {
SaveDebugFrames(scene.Generation); context = _renderTarget.CreateDrawingContext(this);
} _layers.Update(scene, context);
_lastSceneId = scene.Generation; RenderToLayers(scene);
composite = true; if (DebugFramesPath != null)
} {
SaveDebugFrames(scene.Generation);
}
if (renderOverlay) _lastSceneId = scene.Generation;
{
RenderOverlay(scene); composite = true;
RenderComposite(scene); }
}
else if(composite) if (renderOverlay)
{ {
RenderComposite(scene); context = context ?? _renderTarget.CreateDrawingContext(this);
RenderOverlay(scene, context);
RenderComposite(scene, context);
}
else if (composite)
{
context = context ?? _renderTarget.CreateDrawingContext(this);
RenderComposite(scene, context);
}
context?.Dispose();
} }
} }
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget?.Dispose();
_renderTarget = null;
}
} }
private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds) private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds)
@ -273,11 +288,11 @@ namespace Avalonia.Rendering
} }
} }
private void RenderOverlay(Scene scene) private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent)
{ {
if (DrawDirtyRects) if (DrawDirtyRects)
{ {
var overlay = GetOverlay(scene.Size, scene.Scaling); var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling);
using (var context = overlay.CreateDrawingContext(this)) using (var context = overlay.CreateDrawingContext(this))
{ {
@ -301,61 +316,44 @@ namespace Avalonia.Rendering
} }
} }
private void RenderComposite(Scene scene) private void RenderComposite(Scene scene, IDrawingContextImpl context)
{ {
try var clientRect = new Rect(scene.Size);
foreach (var layer in scene.Layers)
{ {
if (_renderTarget == null) var bitmap = _layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
if (layer.GeometryClip != null)
{ {
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget(); context.PushGeometryClip(layer.GeometryClip);
} }
using (var context = _renderTarget.CreateDrawingContext(this)) if (layer.OpacityMask == null)
{ {
var clientRect = new Rect(scene.Size); context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
}
foreach (var layer in scene.Layers) else
{ {
var bitmap = _layers[layer.LayerRoot].Bitmap; context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); }
if (layer.GeometryClip != null)
{
context.PushGeometryClip(layer.GeometryClip);
}
if (layer.OpacityMask == null)
{
context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
}
else
{
context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
}
if (layer.GeometryClip != null)
{
context.PopGeometryClip();
}
}
if (_overlay != null)
{
var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight);
context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
}
if (DrawFps) if (layer.GeometryClip != null)
{ {
RenderFps(context, clientRect, true); context.PopGeometryClip();
}
} }
} }
catch (RenderTargetCorruptedException ex)
if (_overlay != null)
{ {
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight);
_renderTarget?.Dispose(); context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
_renderTarget = null; }
if (DrawFps)
{
RenderFps(context, clientRect, true);
} }
} }
@ -422,16 +420,19 @@ namespace Avalonia.Rendering
} }
} }
private IRenderTargetBitmapImpl GetOverlay(Size size, double scaling) private IRenderTargetBitmapImpl GetOverlay(
IDrawingContextImpl parentContext,
Size size,
double scaling)
{ {
size = new Size(size.Width * scaling, size.Height * scaling); var pixelSize = size * scaling;
if (_overlay == null || if (_overlay == null ||
_overlay.PixelWidth != size.Width || _overlay.PixelWidth != pixelSize.Width ||
_overlay.PixelHeight != size.Height) _overlay.PixelHeight != pixelSize.Height)
{ {
_overlay?.Dispose(); _overlay?.Dispose();
_overlay = _layerFactory.CreateLayer(null, size, 96 * scaling, 96 * scaling); _overlay = parentContext.CreateLayer(size);
} }
return _overlay; return _overlay;

11
src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs

@ -1,11 +0,0 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public interface IRenderLayerFactory
{
IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size, double dpiX, double dpiY);
}
}

10
src/Avalonia.Visuals/Rendering/RenderLayer.cs

@ -7,16 +7,16 @@ namespace Avalonia.Rendering
{ {
public class RenderLayer public class RenderLayer
{ {
private readonly IRenderLayerFactory _factory; private readonly IDrawingContextImpl _drawingContext;
public RenderLayer( public RenderLayer(
IRenderLayerFactory factory, IDrawingContextImpl drawingContext,
Size size, Size size,
double scaling, double scaling,
IVisual layerRoot) IVisual layerRoot)
{ {
_factory = factory; _drawingContext = drawingContext;
Bitmap = factory.CreateLayer(layerRoot, size * scaling, 96 * scaling, 96 * scaling); Bitmap = drawingContext.CreateLayer(size);
Size = size; Size = size;
Scaling = scaling; Scaling = scaling;
LayerRoot = layerRoot; LayerRoot = layerRoot;
@ -31,7 +31,7 @@ namespace Avalonia.Rendering
{ {
if (Size != size || Scaling != scaling) if (Size != size || Scaling != scaling)
{ {
var resized = _factory.CreateLayer(LayerRoot, size * scaling, 96 * scaling, 96 * scaling); var resized = _drawingContext.CreateLayer(size);
using (var context = resized.CreateDrawingContext(null)) using (var context = resized.CreateDrawingContext(null))
{ {

9
src/Avalonia.Visuals/Rendering/RenderLayers.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -8,19 +9,17 @@ namespace Avalonia.Rendering
{ {
public class RenderLayers : IEnumerable<RenderLayer> public class RenderLayers : IEnumerable<RenderLayer>
{ {
private readonly IRenderLayerFactory _factory;
private List<RenderLayer> _inner = new List<RenderLayer>(); private List<RenderLayer> _inner = new List<RenderLayer>();
private Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>(); private Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>();
public RenderLayers(IRenderLayerFactory factory) public RenderLayers()
{ {
_factory = factory;
} }
public int Count => _inner.Count; public int Count => _inner.Count;
public RenderLayer this[IVisual layerRoot] => _index[layerRoot]; public RenderLayer this[IVisual layerRoot] => _index[layerRoot];
public void Update(Scene scene) public void Update(Scene scene, IDrawingContextImpl context)
{ {
for (var i = scene.Layers.Count - 1; i >= 0; --i) for (var i = scene.Layers.Count - 1; i >= 0; --i)
{ {
@ -29,7 +28,7 @@ namespace Avalonia.Rendering
if (!_index.TryGetValue(src.LayerRoot, out layer)) if (!_index.TryGetValue(src.LayerRoot, out layer))
{ {
layer = new RenderLayer(_factory, scene.Size, scene.Scaling, src.LayerRoot); layer = new RenderLayer(context, scene.Size, scene.Scaling, src.LayerRoot);
_inner.Add(layer); _inner.Add(layer);
_index.Add(src.LayerRoot, layer); _index.Add(src.LayerRoot, layer);
} }

5
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -194,6 +194,11 @@ namespace Avalonia.Rendering.SceneGraph
} }
} }
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
throw new NotSupportedException("Creating layers on a deferred drawing context not supported");
}
/// <inheritdoc/> /// <inheritdoc/>
public void PopClip() public void PopClip()
{ {

2
src/Gtk/Avalonia.Gtk3/FramebufferManager.cs

@ -40,7 +40,7 @@ namespace Avalonia.Gtk3
return new ImageSurfaceFramebuffer(_window, width, height, _window.LastKnownScaleFactor); return new ImageSurfaceFramebuffer(_window, width, height, _window.LastKnownScaleFactor);
} }
private static int X11ErrorHandler(IntPtr d, IntPtr e) private static int X11ErrorHandler(IntPtr d, ref X11.XErrorEvent e)
{ {
return 0; return 0;
} }

33
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@ -25,18 +25,27 @@ namespace Avalonia.Gtk3
internal static IntPtr App { get; set; } internal static IntPtr App { get; set; }
internal static string DisplayClassName; internal static string DisplayClassName;
public static bool UseDeferredRendering = true; public static bool UseDeferredRendering = true;
private static bool s_gtkInitialized;
public static void Initialize() public static void Initialize()
{ {
Resolver.Resolve(); if (!s_gtkInitialized)
Native.GtkInit(0, IntPtr.Zero); {
var disp = Native.GdkGetDefaultDisplay(); try
DisplayClassName = Utf8Buffer.StringFromPtr(Native.GTypeName(Marshal.ReadIntPtr(Marshal.ReadIntPtr(disp)))); {
X11.XInitThreads();
using (var utf = new Utf8Buffer("avalonia.app." + Guid.NewGuid())) }catch{}
App = Native.GtkApplicationNew(utf, 0); Resolver.Resolve();
//Mark current thread as UI thread Native.GtkInit(0, IntPtr.Zero);
s_tlsMarker = true; var disp = Native.GdkGetDefaultDisplay();
DisplayClassName =
Utf8Buffer.StringFromPtr(Native.GTypeName(Marshal.ReadIntPtr(Marshal.ReadIntPtr(disp))));
using (var utf = new Utf8Buffer("avalonia.app." + Guid.NewGuid()))
App = Native.GtkApplicationNew(utf, 0);
//Mark current thread as UI thread
s_tlsMarker = true;
s_gtkInitialized = true;
}
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>().ToConstant(Instance) AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>().ToConstant(Instance)
.Bind<IClipboard>().ToSingleton<ClipboardImpl>() .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactory()) .Bind<IStandardCursorFactory>().ToConstant(new CursorFactory())
@ -70,15 +79,13 @@ namespace Avalonia.Gtk3
Native.GtkMainIteration(); Native.GtkMainIteration();
} }
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{ {
var msec = interval.TotalMilliseconds; var msec = interval.TotalMilliseconds;
if (msec <= 0)
throw new ArgumentException("Don't know how to create a timer with zero or negative interval");
var imsec = (uint) msec; var imsec = (uint) msec;
if (imsec == 0) if (imsec == 0)
imsec = 1; imsec = 1;
return GlibTimeout.StarTimer(imsec, tick); return GlibTimeout.StartTimer(GlibPriority.FromDispatcherPriority(priority), imsec, tick);
} }
private bool[] _signaled = new bool[(int) DispatcherPriority.MaxValue + 1]; private bool[] _signaled = new bool[(int) DispatcherPriority.MaxValue + 1];

6
src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs

@ -48,12 +48,10 @@ namespace Avalonia.Gtk3.Interop
} }
} }
public static IDisposable StarTimer(uint interval, Action tick) public static IDisposable StartTimer(int priority, uint interval, Action tick)
{ {
if (interval == 0)
throw new ArgumentException("Don't know how to create a timer with zero or negative interval");
var timer = new Timer (); var timer = new Timer ();
GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Normal), interval, GlibTimeout.Add(priority, interval,
() => () =>
{ {
if (timer.Stopped) if (timer.Stopped)

28
src/Gtk/Avalonia.Gtk3/X11.cs

@ -5,9 +5,18 @@ namespace Avalonia.Gtk3
{ {
class X11 class X11
{ {
[DllImport("libX11.so.6")]
public static extern IntPtr XInitThreads();
[DllImport("libX11.so.6")] [DllImport("libX11.so.6")]
public static extern IntPtr XOpenDisplay(IntPtr name); public static extern IntPtr XOpenDisplay(IntPtr name);
[DllImport("libX11.so.6")]
public static extern IntPtr XLockDisplay(IntPtr display);
[DllImport("libX11.so.6")]
public static extern IntPtr XUnlockDisplay(IntPtr display);
[DllImport("libX11.so.6")] [DllImport("libX11.so.6")]
public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc); public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc);
@ -23,13 +32,28 @@ namespace Avalonia.Gtk3
[DllImport("libX11.so.6")] [DllImport("libX11.so.6")]
public static extern IntPtr XSetErrorHandler(XErrorHandler handler); public static extern IntPtr XSetErrorHandler(XErrorHandler handler);
public delegate int XErrorHandler(IntPtr display, IntPtr error); [DllImport("libX11.so.6")]
public static extern int XSync(IntPtr display, bool discard);
public delegate int XErrorHandler(IntPtr display, ref XErrorEvent error);
[DllImport("libX11.so.6")] [DllImport("libX11.so.6")]
public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image,
int srcx, int srcy, int destx, int desty, uint width, uint height); int srcx, int srcy, int destx, int desty, uint width, uint height);
[StructLayout(LayoutKind.Sequential)]
public unsafe struct XErrorEvent
{
public int type;
public IntPtr* display; /* Display the event was read from */
public ulong serial; /* serial number of failed request */
public byte error_code; /* error code of failed request */
public byte request_code; /* Major op-code of failed request */
public byte minor_code; /* Minor op-code of failed request */
public IntPtr resourceid; /* resource id */
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct XImage public unsafe struct XImage
{ {
public int width, height; /* size of image */ public int width, height; /* size of image */

3
src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs

@ -38,10 +38,13 @@ namespace Avalonia.Gtk3
image.depth = 24; image.depth = 24;
image.bytes_per_line = RowBytes - Width * 4; image.bytes_per_line = RowBytes - Width * 4;
image.bits_per_pixel = bitsPerPixel; image.bits_per_pixel = bitsPerPixel;
X11.XLockDisplay(_display);
X11.XInitImage(ref image); X11.XInitImage(ref image);
var gc = X11.XCreateGC(_display, _xid, 0, IntPtr.Zero); var gc = X11.XCreateGC(_display, _xid, 0, IntPtr.Zero);
X11.XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Width, (uint) Height); X11.XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Width, (uint) Height);
X11.XFreeGC(_display, gc); X11.XFreeGC(_display, gc);
X11.XSync(_display, true);
X11.XUnlockDisplay(_display);
_blob.Dispose(); _blob.Dispose();
} }

43
src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs

@ -0,0 +1,43 @@
using Avalonia.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Windows.Input;
namespace Avalonia.Markup
{
class AlwaysEnabledDelegateCommand : ICommand
{
private readonly Delegate action;
private ParameterInfo parameterInfo;
public AlwaysEnabledDelegateCommand(Delegate action)
{
this.action = action;
var parameters = action.Method.GetParameters();
parameterInfo = parameters.Length == 0 ? null : parameters[0];
}
#pragma warning disable 0067
public event EventHandler CanExecuteChanged;
#pragma warning restore 0067
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
if (parameterInfo == null)
{
action.DynamicInvoke();
}
else
{
TypeUtilities.TryConvert(parameterInfo.ParameterType, parameter, CultureInfo.CurrentCulture, out object convertedParameter);
action.DynamicInvoke(convertedParameter);
}
}
}
}

1
src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs

@ -25,6 +25,7 @@ namespace Avalonia.Markup.Data
new List<IPropertyAccessorPlugin> new List<IPropertyAccessorPlugin>
{ {
new AvaloniaPropertyAccessorPlugin(), new AvaloniaPropertyAccessorPlugin(),
new MethodAccessorPlugin(),
new InpcPropertyAccessorPlugin(), new InpcPropertyAccessorPlugin(),
}; };

87
src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Data;
using System.Reflection;
using System.Linq;
namespace Avalonia.Markup.Data.Plugins
{
class MethodAccessorPlugin : IPropertyAccessorPlugin
{
public bool Match(object obj, string methodName)
=> obj.GetType().GetRuntimeMethods().Any(x => x.Name == methodName);
public IPropertyAccessor Start(WeakReference reference, string methodName)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(methodName != null);
var instance = reference.Target;
var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == methodName);
if (method != null)
{
if (method.GetParameters().Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8)
{
var exception = new ArgumentException("Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", nameof(method));
return new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
}
return new Accessor(reference, method);
}
else
{
var message = $"Could not find CLR method '{methodName}' on '{instance}'";
var exception = new MissingMemberException(message);
return new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
}
}
private class Accessor : PropertyAccessorBase
{
public Accessor(WeakReference reference, MethodInfo method)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(method != null);
var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray();
var returnType = method.ReturnType;
if (returnType == typeof(void))
{
if (paramTypes.Length == 0)
{
PropertyType = typeof(Action);
}
else
{
PropertyType = Type.GetType($"System.Action`{paramTypes.Length}").MakeGenericType(paramTypes);
}
}
else
{
var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray();
PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters);
}
Value = method.IsStatic ? method.CreateDelegate(PropertyType) : method.CreateDelegate(PropertyType, reference.Target);
}
public override Type PropertyType { get; }
public override object Value { get; }
public override bool SetValue(object value, BindingPriority priority) => false;
protected override void SubscribeCore(IObserver<object> observer)
{
try
{
Observer.OnNext(Value);
}
catch { }
}
}
}
}

6
src/Markup/Avalonia.Markup/DefaultValueConverter.cs

@ -5,6 +5,7 @@ using System;
using System.Globalization; using System.Globalization;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Utilities; using Avalonia.Utilities;
using System.Windows.Input;
namespace Avalonia.Markup namespace Avalonia.Markup
{ {
@ -34,6 +35,11 @@ namespace Avalonia.Markup
return AvaloniaProperty.UnsetValue; return AvaloniaProperty.UnsetValue;
} }
if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)
{
return new AlwaysEnabledDelegateCommand(d);
}
if (TypeUtilities.TryConvert(targetType, value, culture, out object result)) if (TypeUtilities.TryConvert(targetType, value, culture, out object result))
{ {
return result; return result;

2
src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs

@ -16,7 +16,7 @@ namespace Avalonia.MonoMac
public event Action<DispatcherPriority?> Signaled; public event Action<DispatcherPriority?> Signaled;
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
=> NSTimer.CreateRepeatingScheduledTimer(interval, () => tick()); => NSTimer.CreateRepeatingScheduledTimer(interval, () => tick());
public void Signal(DispatcherPriority prio) public void Signal(DispatcherPriority prio)

6
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -354,6 +354,12 @@ namespace Avalonia.Skia
} }
} }
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
var pixelSize = size * (_dpi / 96);
return new BitmapImpl((int)pixelSize.Width, (int)pixelSize.Height, _dpi);
}
public void PushClip(Rect clip) public void PushClip(Rect clip)
{ {
Canvas.Save(); Canvas.Save();

4
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -145,7 +145,7 @@ namespace Avalonia.Direct2D1
return new HwndRenderTarget(nativeWindow); return new HwndRenderTarget(nativeWindow);
} }
if (s is IExternalDirect2DRenderTargetSurface external) if (s is IExternalDirect2DRenderTargetSurface external)
return new ExternalRenderTarget(external, s_dwfactory); return new ExternalRenderTarget(external, s_dwfactory, s_imagingFactory);
if (s is IFramebufferPlatformSurface fb) if (s is IFramebufferPlatformSurface fb)
return new FramebufferShimRenderTarget(fb, s_imagingFactory, s_d2D1Factory, s_dwfactory); return new FramebufferShimRenderTarget(fb, s_imagingFactory, s_d2D1Factory, s_dwfactory);
} }
@ -158,7 +158,7 @@ namespace Avalonia.Direct2D1
double dpiX, double dpiX,
double dpiY) double dpiY)
{ {
return new RenderTargetBitmapImpl( return new WicRenderTargetBitmapImpl(
s_imagingFactory, s_imagingFactory,
s_d2D1Factory, s_d2D1Factory,
s_dwfactory, s_dwfactory,

28
src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs

@ -1,9 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using SharpDX; using SharpDX;
@ -11,15 +8,20 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1 namespace Avalonia.Direct2D1
{ {
class ExternalRenderTarget : IRenderTarget class ExternalRenderTarget : IRenderTarget, ILayerFactory
{ {
private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider; private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider;
private readonly DirectWriteFactory _dwFactory; private readonly DirectWriteFactory _dwFactory;
public ExternalRenderTarget(IExternalDirect2DRenderTargetSurface externalRenderTargetProvider, private readonly SharpDX.WIC.ImagingFactory _wicFactory;
DirectWriteFactory dwFactory)
public ExternalRenderTarget(
IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
DirectWriteFactory dwFactory,
SharpDX.WIC.ImagingFactory wicFactory)
{ {
_externalRenderTargetProvider = externalRenderTargetProvider; _externalRenderTargetProvider = externalRenderTargetProvider;
_dwFactory = dwFactory; _dwFactory = dwFactory;
_wicFactory = wicFactory;
} }
public void Dispose() public void Dispose()
@ -31,7 +33,7 @@ namespace Avalonia.Direct2D1
{ {
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget(); var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
_externalRenderTargetProvider.BeforeDrawing(); _externalRenderTargetProvider.BeforeDrawing();
return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, null, () => return new DrawingContextImpl(visualBrushRenderer, null, target, _dwFactory, _wicFactory, null, () =>
{ {
try try
{ {
@ -43,5 +45,15 @@ namespace Avalonia.Direct2D1
} }
}); });
} }
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
return D2DRenderTargetBitmapImpl.CreateCompatible(
_wicFactory,
_dwFactory,
target,
size);
}
} }
} }

2
src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs

@ -47,7 +47,7 @@ namespace Avalonia.Direct2D1
.CreateDrawingContext(visualBrushRenderer); .CreateDrawingContext(visualBrushRenderer);
} }
class FramebufferShim : RenderTargetBitmapImpl class FramebufferShim : WicRenderTargetBitmapImpl
{ {
private readonly ILockedFramebuffer _target; private readonly ILockedFramebuffer _target;

10
src/Windows/Avalonia.Direct2D1/ILayerFactory.cs

@ -0,0 +1,10 @@
using System;
using Avalonia.Platform;
namespace Avalonia.Direct2D1
{
public interface ILayerFactory
{
IRenderTargetBitmapImpl CreateLayer(Size size);
}
}

39
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -2,16 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.RenderHelpers;
using Avalonia.Rendering; using Avalonia.Rendering;
using SharpDX; using SharpDX;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using SharpDX.Mathematics.Interop; using SharpDX.Mathematics.Interop;
using IBitmap = Avalonia.Media.Imaging.IBitmap;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
@ -21,9 +18,11 @@ namespace Avalonia.Direct2D1.Media
public class DrawingContextImpl : IDrawingContextImpl, IDisposable public class DrawingContextImpl : IDrawingContextImpl, IDisposable
{ {
private readonly IVisualBrushRenderer _visualBrushRenderer; private readonly IVisualBrushRenderer _visualBrushRenderer;
private readonly ILayerFactory _layerFactory;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget; private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
private readonly SharpDX.DXGI.SwapChain1 _swapChain; private readonly SharpDX.DXGI.SwapChain1 _swapChain;
private readonly Action _finishedCallback; private readonly Action _finishedCallback;
private readonly SharpDX.WIC.ImagingFactory _imagingFactory;
private SharpDX.DirectWrite.Factory _directWriteFactory; private SharpDX.DirectWrite.Factory _directWriteFactory;
/// <summary> /// <summary>
@ -31,21 +30,30 @@ namespace Avalonia.Direct2D1.Media
/// </summary> /// </summary>
/// <param name="visualBrushRenderer">The visual brush renderer.</param> /// <param name="visualBrushRenderer">The visual brush renderer.</param>
/// <param name="renderTarget">The render target to draw to.</param> /// <param name="renderTarget">The render target to draw to.</param>
/// <param name="layerFactory">
/// An object to use to create layers. May be null, in which case a
/// <see cref="WicRenderTargetBitmapImpl"/> will created when a new layer is requested.
/// </param>
/// <param name="directWriteFactory">The DirectWrite factory.</param> /// <param name="directWriteFactory">The DirectWrite factory.</param>
/// <param name="imagingFactory">The WIC imaging factory.</param>
/// <param name="swapChain">An optional swap chain associated with this drawing context.</param> /// <param name="swapChain">An optional swap chain associated with this drawing context.</param>
/// <param name="finishedCallback">An optional delegate to be called when context is disposed.</param> /// <param name="finishedCallback">An optional delegate to be called when context is disposed.</param>
public DrawingContextImpl( public DrawingContextImpl(
IVisualBrushRenderer visualBrushRenderer, IVisualBrushRenderer visualBrushRenderer,
ILayerFactory layerFactory,
SharpDX.Direct2D1.RenderTarget renderTarget, SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory, SharpDX.DirectWrite.Factory directWriteFactory,
SharpDX.WIC.ImagingFactory imagingFactory,
SharpDX.DXGI.SwapChain1 swapChain = null, SharpDX.DXGI.SwapChain1 swapChain = null,
Action finishedCallback = null) Action finishedCallback = null)
{ {
_visualBrushRenderer = visualBrushRenderer; _visualBrushRenderer = visualBrushRenderer;
_layerFactory = layerFactory;
_renderTarget = renderTarget; _renderTarget = renderTarget;
_swapChain = swapChain; _swapChain = swapChain;
_finishedCallback = finishedCallback; _finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory; _directWriteFactory = directWriteFactory;
_imagingFactory = imagingFactory;
_swapChain = swapChain; _swapChain = swapChain;
_renderTarget.BeginDraw(); _renderTarget.BeginDraw();
} }
@ -97,7 +105,7 @@ namespace Avalonia.Direct2D1.Media
using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget))
{ {
_renderTarget.DrawBitmap( _renderTarget.DrawBitmap(
d2d, d2d.Value,
destRect.ToSharpDX(), destRect.ToSharpDX(),
(float)opacity, (float)opacity,
BitmapInterpolationMode.Linear, BitmapInterpolationMode.Linear,
@ -115,7 +123,7 @@ namespace Avalonia.Direct2D1.Media
public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{ {
using (var d2dSource = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget)) using (var d2dSource = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget))
using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource)) using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size)) using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D())) using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D()))
{ {
@ -284,6 +292,25 @@ namespace Avalonia.Direct2D1.Media
} }
} }
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
if (_layerFactory != null)
{
return _layerFactory.CreateLayer(size);
}
else
{
var platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var dpi = new Vector(_renderTarget.DotsPerInch.Width, _renderTarget.DotsPerInch.Height);
var pixelSize = size * (dpi / 96);
return platform.CreateRenderTargetBitmap(
(int)pixelSize.Width,
(int)pixelSize.Height,
dpi.X,
dpi.Y);
}
}
/// <summary> /// <summary>
/// Pushes a clip rectange. /// Pushes a clip rectange.
/// </summary> /// </summary>
@ -397,7 +424,7 @@ namespace Avalonia.Direct2D1.Media
return new ImageBrushImpl( return new ImageBrushImpl(
visualBrush, visualBrush,
_renderTarget, _renderTarget,
new D2DBitmapImpl(intermediate.Bitmap), new D2DBitmapImpl(_imagingFactory, intermediate.Bitmap),
destinationSize); destinationSize);
} }
} }

7
src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs

@ -10,6 +10,8 @@ namespace Avalonia.Direct2D1.Media
{ {
public sealed class ImageBrushImpl : BrushImpl public sealed class ImageBrushImpl : BrushImpl
{ {
OptionalDispose<Bitmap> _bitmap;
public ImageBrushImpl( public ImageBrushImpl(
ITileBrush brush, ITileBrush brush,
SharpDX.Direct2D1.RenderTarget target, SharpDX.Direct2D1.RenderTarget target,
@ -20,9 +22,10 @@ namespace Avalonia.Direct2D1.Media
if (!calc.NeedsIntermediate) if (!calc.NeedsIntermediate)
{ {
_bitmap = bitmap.GetDirect2DBitmap(target);
PlatformBrush = new BitmapBrush( PlatformBrush = new BitmapBrush(
target, target,
bitmap.GetDirect2DBitmap(target), _bitmap.Value,
GetBitmapBrushProperties(brush), GetBitmapBrushProperties(brush),
GetBrushProperties(brush, calc.DestinationRect)); GetBrushProperties(brush, calc.DestinationRect));
} }
@ -41,7 +44,7 @@ namespace Avalonia.Direct2D1.Media
public override void Dispose() public override void Dispose()
{ {
((BitmapBrush)PlatformBrush)?.Bitmap.Dispose(); _bitmap.Dispose();
base.Dispose(); base.Dispose();
} }

32
src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs

@ -1,20 +1,38 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform; using Avalonia.Platform;
using SharpDX.Direct2D1; using SharpDX.WIC;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
public abstract class BitmapImpl : IBitmapImpl, IDisposable public abstract class BitmapImpl : IBitmapImpl, IDisposable
{ {
public abstract Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); public BitmapImpl(ImagingFactory imagingFactory)
{
WicImagingFactory = imagingFactory;
}
public ImagingFactory WicImagingFactory { get; }
public abstract int PixelWidth { get; } public abstract int PixelWidth { get; }
public abstract int PixelHeight { get; } public abstract int PixelHeight { get; }
public abstract void Save(string fileName);
public abstract OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target);
public void Save(string fileName)
{
if (Path.GetExtension(fileName) != ".png")
{
// Yeah, we need to support other formats.
throw new NotSupportedException("Use PNG, stoopid.");
}
using (FileStream s = new FileStream(fileName, FileMode.Create))
{
Save(s);
}
}
public abstract void Save(Stream stream); public abstract void Save(Stream stream);
public virtual void Dispose() public virtual void Dispose()

47
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs

@ -1,11 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using WICFactory = SharpDX.WIC.ImagingFactory;
using ImagingFactory2 = SharpDX.WIC.ImagingFactory2;
using ImageParameters = SharpDX.WIC.ImageParameters;
using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
@ -26,32 +25,42 @@ namespace Avalonia.Direct2D1.Media
/// or if the render target is a <see cref="SharpDX.Direct2D1.DeviceContext"/>, /// or if the render target is a <see cref="SharpDX.Direct2D1.DeviceContext"/>,
/// the device associated with this context, to be renderable. /// the device associated with this context, to be renderable.
/// </remarks> /// </remarks>
public D2DBitmapImpl(Bitmap d2DBitmap) public D2DBitmapImpl(WICFactory imagingFactory, Bitmap d2DBitmap)
: base(imagingFactory)
{ {
if (d2DBitmap == null) throw new ArgumentNullException(nameof(d2DBitmap)); _direct2D = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
_direct2D = d2DBitmap;
} }
public override Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) => _direct2D;
public override int PixelWidth => _direct2D.PixelSize.Width; public override int PixelWidth => _direct2D.PixelSize.Width;
public override int PixelHeight => _direct2D.PixelSize.Height; public override int PixelHeight => _direct2D.PixelSize.Height;
public override void Save(string fileName) public override void Dispose()
{ {
throw new NotImplementedException(); base.Dispose();
_direct2D.Dispose();
} }
public override void Save(Stream stream) public override OptionalDispose<Bitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{ {
throw new NotImplementedException(); return new OptionalDispose<Bitmap>(_direct2D, false);
} }
public override void Dispose() public override void Save(Stream stream)
{ {
base.Dispose(); using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
_direct2D.Dispose(); using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder))
using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)WicImagingFactory, null))
{
var parameters = new ImageParameters(
new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied),
_direct2D.DotsPerInch.Width,
_direct2D.DotsPerInch.Height,
0, 0, PixelWidth, PixelHeight);
imageEncoder.WriteFrame(_direct2D, frameEncode, parameters);
frameEncode.Commit();
encoder.Commit();
}
} }
} }
} }

68
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs

@ -0,0 +1,68 @@
using System;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media.Imaging
{
public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly BitmapRenderTarget _target;
public D2DRenderTargetBitmapImpl(
ImagingFactory imagingFactory,
DirectWriteFactory dwriteFactory,
BitmapRenderTarget target)
: base(imagingFactory, target.Bitmap)
{
_dwriteFactory = dwriteFactory;
_target = target;
}
public override int PixelWidth => _target.PixelSize.Width;
public override int PixelHeight => _target.PixelSize.Height;
public static D2DRenderTargetBitmapImpl CreateCompatible(
ImagingFactory imagingFactory,
DirectWriteFactory dwriteFactory,
SharpDX.Direct2D1.RenderTarget renderTarget,
Size size)
{
var bitmapRenderTarget = new BitmapRenderTarget(
renderTarget,
CompatibleRenderTargetOptions.None,
new Size2F((float)size.Width, (float)size.Height));
return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget);
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(
visualBrushRenderer,
this,
_target,
_dwriteFactory,
WicImagingFactory);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, size);
}
public override void Dispose()
{
_target.Dispose();
}
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{
return new OptionalDispose<D2DBitmap>(_target.Bitmap, false);
}
}
}

58
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -3,11 +3,10 @@
using System; using System;
using System.IO; using System.IO;
using Avalonia.Platform;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using PixelFormat = SharpDX.WIC.PixelFormat;
using APixelFormat = Avalonia.Platform.PixelFormat;
using SharpDX.WIC; using SharpDX.WIC;
using APixelFormat = Avalonia.Platform.PixelFormat;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
@ -16,18 +15,14 @@ namespace Avalonia.Direct2D1.Media
/// </summary> /// </summary>
public class WicBitmapImpl : BitmapImpl public class WicBitmapImpl : BitmapImpl
{ {
private readonly ImagingFactory _factory;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class. /// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary> /// </summary>
/// <param name="factory">The WIC imaging factory to use.</param> /// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="fileName">The filename of the bitmap to load.</param> /// <param name="fileName">The filename of the bitmap to load.</param>
public WicBitmapImpl(ImagingFactory factory, string fileName) public WicBitmapImpl(ImagingFactory factory, string fileName)
: base(factory)
{ {
_factory = factory;
using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand)) using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand))
{ {
WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand); WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
@ -40,9 +35,8 @@ namespace Avalonia.Direct2D1.Media
/// <param name="factory">The WIC imaging factory to use.</param> /// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="stream">The stream to read the bitmap from.</param> /// <param name="stream">The stream to read the bitmap from.</param>
public WicBitmapImpl(ImagingFactory factory, Stream stream) public WicBitmapImpl(ImagingFactory factory, Stream stream)
: base(factory)
{ {
_factory = factory;
using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad)) using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad))
{ {
WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad); WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad);
@ -57,11 +51,11 @@ namespace Avalonia.Direct2D1.Media
/// <param name="height">The height of the bitmap.</param> /// <param name="height">The height of the bitmap.</param>
/// <param name="pixelFormat">Pixel format</param> /// <param name="pixelFormat">Pixel format</param>
public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null) public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null)
: base(factory)
{ {
if (!pixelFormat.HasValue) if (!pixelFormat.HasValue)
pixelFormat = APixelFormat.Bgra8888; pixelFormat = APixelFormat.Bgra8888;
_factory = factory;
PixelFormat = pixelFormat; PixelFormat = pixelFormat;
WicImpl = new Bitmap( WicImpl = new Bitmap(
factory, factory,
@ -71,10 +65,10 @@ namespace Avalonia.Direct2D1.Media
BitmapCreateCacheOption.CacheOnLoad); BitmapCreateCacheOption.CacheOnLoad);
} }
public WicBitmapImpl(ImagingFactory factory, Platform.PixelFormat format, IntPtr data, int width, int height, int stride) public WicBitmapImpl(ImagingFactory factory, APixelFormat format, IntPtr data, int width, int height, int stride)
: base(factory)
{ {
WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
_factory = factory;
PixelFormat = format; PixelFormat = format;
using (var l = WicImpl.Lock(BitmapLockFlags.Write)) using (var l = WicImpl.Lock(BitmapLockFlags.Write))
{ {
@ -113,41 +107,23 @@ namespace Avalonia.Direct2D1.Media
/// </summary> /// </summary>
/// <param name="renderTarget">The render target.</param> /// <param name="renderTarget">The render target.</param>
/// <returns>The Direct2D bitmap.</returns> /// <returns>The Direct2D bitmap.</returns>
public override SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget) public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
{ {
FormatConverter converter = new FormatConverter(_factory); FormatConverter converter = new FormatConverter(WicImagingFactory);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA); converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter); return new OptionalDispose<D2DBitmap>(D2DBitmap.FromWicBitmap(renderTarget, converter), true);
} }
/// <summary> public override void Save(Stream stream)
/// Saves the bitmap to a file.
/// </summary>
/// <param name="fileName">The filename.</param>
public override void Save(string fileName)
{ {
if (Path.GetExtension(fileName) != ".png") using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
{ using (var frame = new BitmapFrameEncode(encoder))
// Yeah, we need to support other formats.
throw new NotSupportedException("Use PNG, stoopid.");
}
using (FileStream s = new FileStream(fileName, FileMode.Create))
{ {
Save(s); frame.Initialize();
frame.WriteSource(WicImpl);
frame.Commit();
encoder.Commit();
} }
} }
public override void Save(Stream stream)
{
PngBitmapEncoder encoder = new PngBitmapEncoder(_factory);
encoder.Initialize(stream);
BitmapFrameEncode frame = new BitmapFrameEncode(encoder);
frame.Initialize();
frame.WriteSource(WicImpl);
frame.Commit();
encoder.Commit();
}
} }
} }

6
src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs → src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs

@ -10,12 +10,12 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
public class RenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
{ {
private readonly DirectWriteFactory _dwriteFactory; private readonly DirectWriteFactory _dwriteFactory;
private readonly WicRenderTarget _target; private readonly WicRenderTarget _target;
public RenderTargetBitmapImpl( public WicRenderTargetBitmapImpl(
ImagingFactory imagingFactory, ImagingFactory imagingFactory,
Factory d2dFactory, Factory d2dFactory,
DirectWriteFactory dwriteFactory, DirectWriteFactory dwriteFactory,
@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1.Media
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback) public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback)
{ {
return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory, return new DrawingContextImpl(visualBrushRenderer, null, _target, _dwriteFactory, WicImagingFactory,
finishedCallback: finishedCallback); finishedCallback: finishedCallback);
} }
} }

22
src/Windows/Avalonia.Direct2D1/OptionalDispose.cs

@ -0,0 +1,22 @@
using System;
namespace Avalonia.Direct2D1
{
public struct OptionalDispose<T> : IDisposable where T : IDisposable
{
private readonly bool _dispose;
public OptionalDispose(T value, bool dispose)
{
Value = value;
_dispose = dispose;
}
public T Value { get; }
public void Dispose()
{
if (_dispose) Value?.Dispose();
}
}
}

34
src/Windows/Avalonia.Direct2D1/RenderTarget.cs

@ -3,14 +3,16 @@
using System; using System;
using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using DwFactory = SharpDX.DirectWrite.Factory; using DwFactory = SharpDX.DirectWrite.Factory;
using WicFactory = SharpDX.WIC.ImagingFactory;
namespace Avalonia.Direct2D1 namespace Avalonia.Direct2D1
{ {
public class RenderTarget : IRenderTarget public class RenderTarget : IRenderTarget, ILayerFactory
{ {
/// <summary> /// <summary>
/// The render target. /// The render target.
@ -25,24 +27,13 @@ namespace Avalonia.Direct2D1
{ {
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>(); Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<DwFactory>(); DirectWriteFactory = AvaloniaLocator.Current.GetService<DwFactory>();
WicFactory = AvaloniaLocator.Current.GetService<WicFactory>();
_renderTarget = renderTarget; _renderTarget = renderTarget;
} }
/// <summary> public Factory Direct2DFactory { get; }
/// Gets the Direct2D factory. public DwFactory DirectWriteFactory { get; }
/// </summary> public WicFactory WicFactory { get; }
public Factory Direct2DFactory
{
get;
}
/// <summary>
/// Gets the DirectWrite factory.
/// </summary>
public DwFactory DirectWriteFactory
{
get;
}
/// <summary> /// <summary>
/// Creates a drawing context for a rendering session. /// Creates a drawing context for a rendering session.
@ -50,7 +41,16 @@ namespace Avalonia.Direct2D1
/// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns> /// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns>
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{ {
return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory); return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, DirectWriteFactory, WicFactory);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
return D2DRenderTargetBitmapImpl.CreateCompatible(
WicFactory,
DirectWriteFactory,
_renderTarget,
size);
} }
public void Dispose() public void Dispose()

40
src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs

@ -10,10 +10,11 @@ using Factory = SharpDX.Direct2D1.Factory;
using Factory2 = SharpDX.DXGI.Factory2; using Factory2 = SharpDX.DXGI.Factory2;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Direct2D1.Media; using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
namespace Avalonia.Direct2D1 namespace Avalonia.Direct2D1
{ {
public abstract class SwapChainRenderTarget : IRenderTarget public abstract class SwapChainRenderTarget : IRenderTarget, ILayerFactory
{ {
private Size2 _savedSize; private Size2 _savedSize;
private Size2F _savedDpi; private Size2F _savedDpi;
@ -26,24 +27,12 @@ namespace Avalonia.Direct2D1
D2DDevice = AvaloniaLocator.Current.GetService<Device>(); D2DDevice = AvaloniaLocator.Current.GetService<Device>();
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>(); Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>(); DirectWriteFactory = AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>();
WicImagingFactory = AvaloniaLocator.Current.GetService<SharpDX.WIC.ImagingFactory>();
} }
public Factory Direct2DFactory { get; }
/// <summary> public SharpDX.DirectWrite.Factory DirectWriteFactory { get; }
/// Gets the Direct2D factory. public SharpDX.WIC.ImagingFactory WicImagingFactory { get; }
/// </summary>
public Factory Direct2DFactory
{
get;
}
/// <summary>
/// Gets the DirectWrite factory.
/// </summary>
public SharpDX.DirectWrite.Factory DirectWriteFactory
{
get;
}
protected SharpDX.DXGI.Device DxgiDevice { get; } protected SharpDX.DXGI.Device DxgiDevice { get; }
@ -67,11 +56,27 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl( return new DrawingContextImpl(
visualBrushRenderer, visualBrushRenderer,
this,
_deviceContext, _deviceContext,
DirectWriteFactory, DirectWriteFactory,
WicImagingFactory,
_swapChain); _swapChain);
} }
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
if (_deviceContext == null)
{
CreateSwapChain();
}
return D2DRenderTargetBitmapImpl.CreateCompatible(
WicImagingFactory,
DirectWriteFactory,
_deviceContext,
size);
}
public void Dispose() public void Dispose()
{ {
_deviceContext?.Dispose(); _deviceContext?.Dispose();
@ -86,7 +91,6 @@ namespace Avalonia.Direct2D1
_deviceContext?.Dispose(); _deviceContext?.Dispose();
_deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi}; _deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi};
var swapChainDesc = new SwapChainDescription1 var swapChainDesc = new SwapChainDescription1
{ {
Width = _savedSize.Width, Width = _savedSize.Width,

2
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -114,7 +114,7 @@ namespace Avalonia.Win32
} }
} }
public IDisposable StartTimer(TimeSpan interval, Action callback) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action callback)
{ {
UnmanagedMethods.TimerProc timerDelegate = UnmanagedMethods.TimerProc timerDelegate =
(hWnd, uMsg, nIDEvent, dwTime) => callback(); (hWnd, uMsg, nIDEvent, dwTime) => callback();

2
src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs

@ -51,7 +51,7 @@ namespace Avalonia.iOS
} }
}*/ }*/
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
=> NSTimer.CreateRepeatingScheduledTimer(interval, _ => tick()); => NSTimer.CreateRepeatingScheduledTimer(interval, _ => tick());
public void Signal(DispatcherPriority prio) public void Signal(DispatcherPriority prio)

2
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@ -187,7 +187,7 @@ namespace Avalonia.Base.UnitTests
source.OnNext(45); source.OnNext(45);
Assert.Equal(null, target.Foo); Assert.Null(target.Foo);
} }
[Fact] [Fact]

2
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs

@ -172,7 +172,7 @@ namespace Avalonia.Base.UnitTests
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

2
tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs

@ -66,7 +66,7 @@ namespace Avalonia.Controls.UnitTests.Generators
target.Dematerialize(1, 1); target.Dematerialize(1, 1);
Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0));
Assert.Equal(null, target.ContainerFromIndex(1)); Assert.Null(target.ContainerFromIndex(1));
Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2)); Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2));
} }

4
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@ -107,7 +107,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Content = child; target.Content = child;
target.Content = null; target.Content = null;
Assert.Equal(null, child.GetLogicalParent()); Assert.Null(child.GetLogicalParent());
Assert.Empty(target.GetLogicalChildren()); Assert.Empty(target.GetLogicalChildren());
} }
@ -120,7 +120,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Content = child; target.Content = child;
target.Content = null; target.Content = null;
Assert.Equal(null, child.GetVisualParent()); Assert.Null(child.GetVisualParent());
Assert.Empty(target.GetVisualChildren()); Assert.Empty(target.GetVisualChildren());
} }

2
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

@ -191,7 +191,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Content = "bar"; target.Content = "bar";
target.UpdateChild(); target.UpdateChild();
Assert.Equal(null, foo.Parent); Assert.Null(foo.Parent);
logicalChildren = target.GetLogicalChildren(); logicalChildren = target.GetLogicalChildren();

8
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -171,7 +171,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.SelectedItem = new Item(); target.SelectedItem = new Item();
Assert.Equal(null, target.SelectedItem); Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
} }
@ -278,7 +278,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Items = null; target.Items = null;
Assert.Equal(null, target.SelectedItem); Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
} }
@ -305,7 +305,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
items.RemoveAt(1); items.RemoveAt(1);
Assert.Equal(null, target.SelectedItem); Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
} }
@ -334,7 +334,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
items.Clear(); items.Clear();
Assert.Equal(null, target.SelectedItem); Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
} }

2
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -76,7 +76,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.SelectedItems = new AvaloniaList<object>(); target.SelectedItems = new AvaloniaList<object>();
Assert.Equal(-1, target.SelectedIndex); Assert.Equal(-1, target.SelectedIndex);
Assert.Equal(null, target.SelectedItem); Assert.Null(target.SelectedItem);
} }
[Fact] [Fact]

22
tests/Avalonia.LeakTests/ExpressionObserverTests.cs

@ -71,6 +71,28 @@ namespace Avalonia.LeakTests
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<NonIntegerIndexer>()).ObjectsCount)); Assert.Equal(0, memory.GetObjects(where => where.Type.Is<NonIntegerIndexer>()).ObjectsCount));
} }
[Fact]
public void Should_Not_Keep_Source_Alive_MethodBinding()
{
Func<ExpressionObserver> run = () =>
{
var source = new { Foo = new MethodBound() };
var target = new ExpressionObserver(source, "Foo.A");
target.Subscribe(_ => { });
return target;
};
var result = run();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<MethodBound>()).ObjectsCount));
}
private class MethodBound
{
public void A() { }
}
private class NonIntegerIndexer : NotifyingBase private class NonIntegerIndexer : NotifyingBase
{ {
private readonly Dictionary<string, string> _storage = new Dictionary<string, string>(); private readonly Dictionary<string, string> _storage = new Dictionary<string, string>();

87
tests/Avalonia.Markup.UnitTests/Data/ExpressionObserverTests_Method.cs

@ -0,0 +1,87 @@
using Avalonia.Data;
using Avalonia.Markup.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Avalonia.Markup.UnitTests.Data
{
public class ExpressionObserverTests_Method
{
private class TestObject
{
public void MethodWithoutReturn() { }
public int MethodWithReturn() => 0;
public int MethodWithReturnAndParameters(int i) => i;
public static void StaticMethod() { }
public static void TooManyParameters(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { }
public static int TooManyParametersWithReturnType(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) => 1;
}
[Fact]
public async Task Should_Get_Method()
{
var data = new TestObject();
var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithoutReturn));
var result = await observer.Take(1);
Assert.NotNull(result);
GC.KeepAlive(data);
}
[Theory]
[InlineData(nameof(TestObject.MethodWithoutReturn), typeof(Action))]
[InlineData(nameof(TestObject.MethodWithReturn), typeof(Func<int>))]
[InlineData(nameof(TestObject.MethodWithReturnAndParameters), typeof(Func<int, int>))]
[InlineData(nameof(TestObject.StaticMethod), typeof(Action))]
public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType)
{
var data = new TestObject();
var observer = new ExpressionObserver(data, methodName);
var result = await observer.Take(1);
Assert.IsType(expectedType, result);
GC.KeepAlive(data);
}
[Fact]
public async Task Can_Call_Method_Returned_From_Observer()
{
var data = new TestObject();
var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithReturnAndParameters));
var result = await observer.Take(1);
var callback = (Func<int, int>)result;
Assert.Equal(1, callback(1));
GC.KeepAlive(data);
}
[Theory]
[InlineData(nameof(TestObject.TooManyParameters))]
[InlineData(nameof(TestObject.TooManyParametersWithReturnType))]
public async Task Should_Return_Error_Notification_If_Too_Many_Parameters(string methodName)
{
var data = new TestObject();
var observer = new ExpressionObserver(data, methodName);
var result = await observer.Take(1);
Assert.IsType<BindingNotification>(result);
Assert.Equal(BindingErrorType.Error, ((BindingNotification)result).ErrorType);
GC.KeepAlive(data);
}
}
}

38
tests/Avalonia.Markup.UnitTests/DefaultValueConverterTests.cs

@ -5,6 +5,8 @@ using System.Globalization;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Xunit; using Xunit;
using System.Windows.Input;
using System;
namespace Avalonia.Markup.UnitTests namespace Avalonia.Markup.UnitTests
{ {
@ -118,6 +120,42 @@ namespace Avalonia.Markup.UnitTests
Assert.IsType<BindingNotification>(result); Assert.IsType<BindingNotification>(result);
} }
[Fact]
public void Can_Convert_From_Delegate_To_Command()
{
int commandResult = 0;
var result = DefaultValueConverter.Instance.Convert(
(Action<int>)((int i) => { commandResult = i; }),
typeof(ICommand),
null,
CultureInfo.InvariantCulture);
Assert.IsAssignableFrom<ICommand>(result);
(result as ICommand).Execute(5);
Assert.Equal(5, commandResult);
}
[Fact]
public void Can_Convert_From_Delegate_To_Command_No_Parameters()
{
int commandResult = 0;
var result = DefaultValueConverter.Instance.Convert(
(Action)(() => { commandResult = 1; }),
typeof(ICommand),
null,
CultureInfo.InvariantCulture);
Assert.IsAssignableFrom<ICommand>(result);
(result as ICommand).Execute(null);
Assert.Equal(1, commandResult);
}
private enum TestEnum private enum TestEnum
{ {
Foo, Foo,

105
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs

@ -0,0 +1,105 @@
// 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.Reactive.Subjects;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Data
{
public class BindingTests_Method
{
[Fact]
public void Binding_Method_To_Command_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<Button Name='button' Command='{Binding Method}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
var vm = new ViewModel();
button.DataContext = vm;
window.ApplyTemplate();
Assert.NotNull(button.Command);
PerformClick(button);
Assert.Equal("Called", vm.Value);
}
}
[Fact]
public void Binding_Method_With_Parameter_To_Command_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<Button Name='button' Command='{Binding Method1}' CommandParameter='5'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
var vm = new ViewModel();
button.DataContext = vm;
window.ApplyTemplate();
Assert.NotNull(button.Command);
PerformClick(button);
Assert.Equal("Called 5", vm.Value);
}
}
[Fact]
public void Binding_Method_To_TextBlock_Text_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<TextBlock Name='textBlock' Text='{Binding Method}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
var vm = new ViewModel();
textBlock.DataContext = vm;
window.ApplyTemplate();
Assert.NotNull(textBlock.Text);
}
}
static void PerformClick(Button button)
{
button.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = Input.Key.Enter,
});
}
private class ViewModel
{
public string Method() => Value = "Called";
public string Method1(int i) => Value = $"Called {i}";
public string Method2(int i, int j) => Value = $"Called {i},{j}";
public string Value { get; private set; } = "Not called";
}
}
}

2
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs

@ -117,7 +117,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
}; };
target.Bind(TextBox.TextProperty, binding); target.Bind(TextBox.TextProperty, binding);
Assert.Equal(null, target.Text); Assert.Null(target.Text);
} }
[Fact] [Fact]

4
tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs

@ -63,7 +63,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates
{ {
var selector = new MemberSelector() { MemberName = "StringValue" }; var selector = new MemberSelector() { MemberName = "StringValue" };
Assert.Equal(null, selector.Select(null)); Assert.Null(selector.Select(null));
} }
[Fact] [Fact]
@ -73,7 +73,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates
var data = new Item() { StringValue = "Value1" }; var data = new Item() { StringValue = "Value1" };
Assert.Same(null, selector.Select(data)); Assert.Null(selector.Select(data));
} }
[Fact] [Fact]

2
tests/Avalonia.RenderTests/TestBase.cs

@ -161,7 +161,7 @@ namespace Avalonia.Direct2D1.RenderTests
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IDisposable StartTimer(TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

2
tests/Avalonia.UnitTests/TestRoot.cs

@ -49,8 +49,6 @@ namespace Avalonia.UnitTests
public ILayoutManager LayoutManager => AvaloniaLocator.Current.GetService<ILayoutManager>(); public ILayoutManager LayoutManager => AvaloniaLocator.Current.GetService<ILayoutManager>();
public IRenderTarget RenderTarget => null;
public IRenderer Renderer { get; set; } public IRenderer Renderer { get; set; }
public IAccessKeyHandler AccessKeyHandler => null; public IAccessKeyHandler AccessKeyHandler => null;

70
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -30,7 +30,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root, root,
loop.Object, loop.Object,
sceneBuilder: MockSceneBuilder(root).Object, sceneBuilder: MockSceneBuilder(root).Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher.Object); dispatcher: dispatcher.Object);
target.Start(); target.Start();
@ -55,7 +54,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root, root,
loop.Object, loop.Object,
sceneBuilder: sceneBuilder.Object, sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher); dispatcher: dispatcher);
target.Start(); target.Start();
@ -75,7 +73,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root, root,
loop.Object, loop.Object,
sceneBuilder: sceneBuilder.Object, sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher); dispatcher: dispatcher);
target.Start(); target.Start();
@ -111,7 +108,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root, root,
loop.Object, loop.Object,
sceneBuilder: sceneBuilder.Object, sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher); dispatcher: dispatcher);
target.Start(); target.Start();
@ -146,22 +142,20 @@ namespace Avalonia.Visuals.UnitTests.Rendering
scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)); scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize));
}); });
var layers = new Mock<IRenderLayerFactory>();
layers.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer());
var renderInterface = new Mock<IPlatformRenderInterface>(); var renderInterface = new Mock<IPlatformRenderInterface>();
var target = new DeferredRenderer( var target = new DeferredRenderer(
root, root,
loop.Object, loop.Object,
sceneBuilder: sceneBuilder.Object, sceneBuilder: sceneBuilder.Object,
layerFactory: layers.Object, //layerFactory: layers.Object,
dispatcher: dispatcher); dispatcher: dispatcher);
target.Start(); target.Start();
RunFrame(loop); RunFrame(loop);
layers.Verify(x => x.CreateLayer(root, root.ClientSize, 96, 96)); var context = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
context.Verify(x => x.CreateLayer(root.ClientSize));
} }
[Fact] [Fact]
@ -185,25 +179,25 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity); root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize)); root.Arrange(new Rect(root.DesiredSize));
var loop = new Mock<IRenderLoop>(); var rootLayer = CreateLayer();
var layerFactory = new MockRenderLayerFactory(new Dictionary<IVisual, IRenderTargetBitmapImpl> var borderLayer = CreateLayer();
{ var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
{ root, CreateLayer() }, renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny<Size>()))
{ border, CreateLayer() }, .Returns(rootLayer)
}); .Returns(borderLayer);
var loop = new Mock<IRenderLoop>();
var target = new DeferredRenderer( var target = new DeferredRenderer(
root, root,
loop.Object, loop.Object,
layerFactory: layerFactory,
dispatcher: new ImmediateDispatcher()); dispatcher: new ImmediateDispatcher());
root.Renderer = target; root.Renderer = target;
target.Start(); target.Start();
RunFrame(loop); RunFrame(loop);
var rootContext = layerFactory.GetMockDrawingContext(root); var rootContext = Mock.Get(rootLayer.CreateDrawingContext(null));
var borderContext = layerFactory.GetMockDrawingContext(border); var borderContext = Mock.Get(borderLayer.CreateDrawingContext(null));
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
@ -223,7 +217,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
border.Opacity = 1; border.Opacity = 1;
RunFrame(loop); RunFrame(loop);
layerFactory.GetMockBitmap(border).Verify(x => x.Dispose()); Mock.Get(borderLayer).Verify(x => x.Dispose());
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once); rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once); rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
borderContext.Verify(x => x.FillRectangle(It.IsAny<IBrush>(), It.IsAny<Rect>(), It.IsAny<float>()), Times.Never); borderContext.Verify(x => x.FillRectangle(It.IsAny<IBrush>(), It.IsAny<Rect>(), It.IsAny<float>()), Times.Never);
@ -246,13 +240,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>()) == Mock.Of<IDrawingContextImpl>()); x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>()) == Mock.Of<IDrawingContextImpl>());
} }
private Mock<IRenderLayerFactory> MockLayerFactory(IRenderRoot root)
{
var result = new Mock<IRenderLayerFactory>();
result.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer());
return result;
}
private Mock<ISceneBuilder> MockSceneBuilder(IRenderRoot root) private Mock<ISceneBuilder> MockSceneBuilder(IRenderRoot root)
{ {
var result = new Mock<ISceneBuilder>(); var result = new Mock<ISceneBuilder>();
@ -260,34 +247,5 @@ namespace Avalonia.Visuals.UnitTests.Rendering
.Callback<Scene>(x => x.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize))); .Callback<Scene>(x => x.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)));
return result; return result;
} }
private class MockRenderLayerFactory : IRenderLayerFactory
{
private IDictionary<IVisual, IRenderTargetBitmapImpl> _layers;
public MockRenderLayerFactory(IDictionary<IVisual, IRenderTargetBitmapImpl> layers)
{
_layers = layers;
}
public IRenderTargetBitmapImpl CreateLayer(
IVisual layerRoot,
Size size,
double dpiX,
double dpiY)
{
return _layers[layerRoot];
}
public Mock<IRenderTargetBitmapImpl> GetMockBitmap(IVisual layerRoot)
{
return Mock.Get(_layers[layerRoot]);
}
public Mock<IDrawingContextImpl> GetMockDrawingContext(IVisual layerRoot)
{
return Mock.Get(_layers[layerRoot].CreateDrawingContext(null));
}
}
} }
} }

Loading…
Cancel
Save