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)
{
return new DrawingContextImpl(visualBrushRenderer, _window._d2dRenderTarget,
AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>());
return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget,
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;
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
if (interval.TotalMilliseconds < 10)
interval = TimeSpan.FromMilliseconds(10);

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

@ -17,10 +17,11 @@ namespace Avalonia.Platform
/// <summary>
/// Starts a timer.
/// </summary>
/// <param name="priority"></param>
/// <param name="interval">The interval.</param>
/// <param name="tick">The action to call on each tick.</param>
/// <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);

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

@ -84,6 +84,19 @@ namespace Avalonia.Threading
_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>
/// Allows unit tests to change the platform threading interface.
/// </summary>

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

@ -17,13 +17,11 @@ namespace Avalonia.Threading
private readonly DispatcherPriority _priority;
private TimeSpan _interval;
private readonly Action _raiseTickAction;
/// <summary>
/// Initializes a new instance of the <see cref="DispatcherTimer"/> class.
/// </summary>
public DispatcherTimer() : this(DispatcherPriority.Normal)
public DispatcherTimer() : this(DispatcherPriority.Background)
{
}
@ -34,7 +32,6 @@ namespace Avalonia.Threading
public DispatcherTimer(DispatcherPriority priority)
{
_priority = priority;
_raiseTickAction = RaiseTick;
}
/// <summary>
@ -187,7 +184,7 @@ namespace Avalonia.Threading
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>
private void InternalTick()
{
Dispatcher.UIThread.InvokeAsync(_raiseTickAction, _priority);
}
/// <summary>
/// Raises the <see cref="Tick"/> event.
/// </summary>
private void RaiseTick()
{
Dispatcher.UIThread.EnsurePriority(_priority);
Tick?.Invoke(this, EventArgs.Empty);
}
}

15
src/Avalonia.Controls/MenuItem.cs

@ -307,6 +307,21 @@ namespace Avalonia.Controls
() => IsSubMenuOpen = true,
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>

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

@ -14,7 +14,7 @@ namespace Avalonia.Controls.Platform
public InternalPlatformThreadingInterface()
{
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);
@ -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
{

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

@ -80,12 +80,29 @@ namespace Avalonia.Platform
/// <param name="cornerRadius">The corner radius.</param>
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>
/// Pushes a clip rectange.
/// </summary>
/// <param name="clip">The clip rectangle.</param>
void PushClip(Rect clip);
/// <summary>
/// Pops the latest pushed clip rectangle.
/// </summary>
void PopClip();
/// <summary>
@ -94,10 +111,19 @@ namespace Avalonia.Platform
/// <param name="opacity">The opacity.</param>
void PushOpacity(double opacity);
/// <summary>
/// Pops the latest pushed opacity value.
/// </summary>
void PopOpacity();
/// <summary>
/// Pushes an opacity mask
/// </summary>
void PushOpacityMask(IBrush mask, Rect bounds);
/// <summary>
/// Pops the latest pushed opacity mask.
/// </summary>
void PopOpacityMask();
/// <summary>
@ -106,6 +132,9 @@ namespace Avalonia.Platform
/// <param name="clip">The clip geometry.</param>
void PushGeometryClip(IGeometryImpl clip);
/// <summary>
/// Pops the latest pushed geometry clip.
/// </summary>
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 ISceneBuilder _sceneBuilder;
private readonly RenderLayers _layers;
private readonly IRenderLayerFactory _layerFactory;
private bool _running;
private Scene _scene;
@ -45,13 +44,11 @@ namespace Avalonia.Rendering
/// <param name="root">The control to render.</param>
/// <param name="renderLoop">The render loop.</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>
public DeferredRenderer(
IRenderRoot root,
IRenderLoop renderLoop,
ISceneBuilder sceneBuilder = null,
IRenderLayerFactory layerFactory = null,
IDispatcher dispatcher = null)
{
Contract.Requires<ArgumentNullException>(root != null);
@ -59,8 +56,7 @@ namespace Avalonia.Rendering
_dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory();
_layers = new RenderLayers(_layerFactory);
_layers = new RenderLayers();
_renderLoop = renderLoop;
}
@ -70,15 +66,13 @@ namespace Avalonia.Rendering
/// <param name="root">The control to render.</param>
/// <param name="renderTarget">The render target.</param>
/// <param name="sceneBuilder">The scene builder to use. Optional.</param>
/// <param name="layerFactory">The layer factory to use. Optional.</param>
/// <remarks>
/// This constructor is intended to be used for unit testing.
/// </remarks>
public DeferredRenderer(
IVisual root,
IRenderTarget renderTarget,
ISceneBuilder sceneBuilder = null,
IRenderLayerFactory layerFactory = null)
ISceneBuilder sceneBuilder = null)
{
Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(renderTarget != null);
@ -86,8 +80,7 @@ namespace Avalonia.Rendering
_root = root;
_renderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory();
_layers = new RenderLayers(_layerFactory);
_layers = new RenderLayers();
}
/// <inheritdoc/>
@ -180,38 +173,60 @@ namespace Avalonia.Rendering
bool renderOverlay = DrawDirtyRects || DrawFps;
bool composite = false;
if (_renderTarget == null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
if (renderOverlay)
{
_dirtyRectsDisplay.Tick();
}
if (scene != null && scene.Size != Size.Empty)
try
{
if (scene.Generation != _lastSceneId)
if (scene != null && scene.Size != Size.Empty)
{
_layers.Update(scene);
RenderToLayers(scene);
IDrawingContextImpl context = null;
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)
{
RenderOverlay(scene);
RenderComposite(scene);
}
else if(composite)
{
RenderComposite(scene);
_lastSceneId = scene.Generation;
composite = true;
}
if (renderOverlay)
{
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)
@ -273,11 +288,11 @@ namespace Avalonia.Rendering
}
}
private void RenderOverlay(Scene scene)
private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent)
{
if (DrawDirtyRects)
{
var overlay = GetOverlay(scene.Size, scene.Scaling);
var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling);
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);
foreach (var layer in scene.Layers)
{
var bitmap = _layers[layer.LayerRoot].Bitmap;
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);
}
context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
}
else
{
context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
}
if (DrawFps)
{
RenderFps(context, clientRect, true);
}
if (layer.GeometryClip != null)
{
context.PopGeometryClip();
}
}
catch (RenderTargetCorruptedException ex)
if (_overlay != null)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget?.Dispose();
_renderTarget = null;
var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight);
context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
}
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 ||
_overlay.PixelWidth != size.Width ||
_overlay.PixelHeight != size.Height)
_overlay.PixelWidth != pixelSize.Width ||
_overlay.PixelHeight != pixelSize.Height)
{
_overlay?.Dispose();
_overlay = _layerFactory.CreateLayer(null, size, 96 * scaling, 96 * scaling);
_overlay = parentContext.CreateLayer(size);
}
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
{
private readonly IRenderLayerFactory _factory;
private readonly IDrawingContextImpl _drawingContext;
public RenderLayer(
IRenderLayerFactory factory,
IDrawingContextImpl drawingContext,
Size size,
double scaling,
IVisual layerRoot)
{
_factory = factory;
Bitmap = factory.CreateLayer(layerRoot, size * scaling, 96 * scaling, 96 * scaling);
_drawingContext = drawingContext;
Bitmap = drawingContext.CreateLayer(size);
Size = size;
Scaling = scaling;
LayerRoot = layerRoot;
@ -31,7 +31,7 @@ namespace Avalonia.Rendering
{
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))
{

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

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.VisualTree;
@ -8,19 +9,17 @@ namespace Avalonia.Rendering
{
public class RenderLayers : IEnumerable<RenderLayer>
{
private readonly IRenderLayerFactory _factory;
private List<RenderLayer> _inner = new List<RenderLayer>();
private Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>();
public RenderLayers(IRenderLayerFactory factory)
public RenderLayers()
{
_factory = factory;
}
public int Count => _inner.Count;
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)
{
@ -29,7 +28,7 @@ namespace Avalonia.Rendering
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);
_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/>
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);
}
private static int X11ErrorHandler(IntPtr d, IntPtr e)
private static int X11ErrorHandler(IntPtr d, ref X11.XErrorEvent e)
{
return 0;
}

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

@ -25,18 +25,27 @@ namespace Avalonia.Gtk3
internal static IntPtr App { get; set; }
internal static string DisplayClassName;
public static bool UseDeferredRendering = true;
private static bool s_gtkInitialized;
public static void Initialize()
{
Resolver.Resolve();
Native.GtkInit(0, IntPtr.Zero);
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;
if (!s_gtkInitialized)
{
try
{
X11.XInitThreads();
}catch{}
Resolver.Resolve();
Native.GtkInit(0, IntPtr.Zero);
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)
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactory())
@ -70,15 +79,13 @@ namespace Avalonia.Gtk3
Native.GtkMainIteration();
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
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;
if (imsec == 0)
imsec = 1;
return GlibTimeout.StarTimer(imsec, tick);
return GlibTimeout.StartTimer(GlibPriority.FromDispatcherPriority(priority), imsec, tick);
}
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 ();
GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Normal), interval,
GlibTimeout.Add(priority, interval,
() =>
{
if (timer.Stopped)

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

@ -5,9 +5,18 @@ namespace Avalonia.Gtk3
{
class X11
{
[DllImport("libX11.so.6")]
public static extern IntPtr XInitThreads();
[DllImport("libX11.so.6")]
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")]
public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc);
@ -23,13 +32,28 @@ namespace Avalonia.Gtk3
[DllImport("libX11.so.6")]
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")]
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);
[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 int width, height; /* size of image */

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

@ -38,10 +38,13 @@ namespace Avalonia.Gtk3
image.depth = 24;
image.bytes_per_line = RowBytes - Width * 4;
image.bits_per_pixel = bitsPerPixel;
X11.XLockDisplay(_display);
X11.XInitImage(ref image);
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.XFreeGC(_display, gc);
X11.XSync(_display, true);
X11.XUnlockDisplay(_display);
_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 AvaloniaPropertyAccessorPlugin(),
new MethodAccessorPlugin(),
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 Avalonia.Data;
using Avalonia.Utilities;
using System.Windows.Input;
namespace Avalonia.Markup
{
@ -34,6 +35,11 @@ namespace Avalonia.Markup
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))
{
return result;

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

@ -16,7 +16,7 @@ namespace Avalonia.MonoMac
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());
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)
{
Canvas.Save();

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

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

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

@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX;
@ -11,15 +8,20 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1
{
class ExternalRenderTarget : IRenderTarget
class ExternalRenderTarget : IRenderTarget, ILayerFactory
{
private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider;
private readonly DirectWriteFactory _dwFactory;
public ExternalRenderTarget(IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
DirectWriteFactory dwFactory)
private readonly SharpDX.WIC.ImagingFactory _wicFactory;
public ExternalRenderTarget(
IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
DirectWriteFactory dwFactory,
SharpDX.WIC.ImagingFactory wicFactory)
{
_externalRenderTargetProvider = externalRenderTargetProvider;
_dwFactory = dwFactory;
_wicFactory = wicFactory;
}
public void Dispose()
@ -31,7 +33,7 @@ namespace Avalonia.Direct2D1
{
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
_externalRenderTargetProvider.BeforeDrawing();
return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, null, () =>
return new DrawingContextImpl(visualBrushRenderer, null, target, _dwFactory, _wicFactory, null, () =>
{
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);
}
class FramebufferShim : RenderTargetBitmapImpl
class FramebufferShim : WicRenderTargetBitmapImpl
{
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.
using System;
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;
using IBitmap = Avalonia.Media.Imaging.IBitmap;
namespace Avalonia.Direct2D1.Media
{
@ -21,9 +18,11 @@ namespace Avalonia.Direct2D1.Media
public class DrawingContextImpl : IDrawingContextImpl, IDisposable
{
private readonly IVisualBrushRenderer _visualBrushRenderer;
private readonly ILayerFactory _layerFactory;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
private readonly SharpDX.DXGI.SwapChain1 _swapChain;
private readonly Action _finishedCallback;
private readonly SharpDX.WIC.ImagingFactory _imagingFactory;
private SharpDX.DirectWrite.Factory _directWriteFactory;
/// <summary>
@ -31,21 +30,30 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
/// <param name="visualBrushRenderer">The visual brush renderer.</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="imagingFactory">The WIC imaging factory.</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>
public DrawingContextImpl(
IVisualBrushRenderer visualBrushRenderer,
ILayerFactory layerFactory,
SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory,
SharpDX.WIC.ImagingFactory imagingFactory,
SharpDX.DXGI.SwapChain1 swapChain = null,
Action finishedCallback = null)
{
_visualBrushRenderer = visualBrushRenderer;
_layerFactory = layerFactory;
_renderTarget = renderTarget;
_swapChain = swapChain;
_finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory;
_imagingFactory = imagingFactory;
_swapChain = swapChain;
_renderTarget.BeginDraw();
}
@ -97,7 +105,7 @@ namespace Avalonia.Direct2D1.Media
using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget))
{
_renderTarget.DrawBitmap(
d2d,
d2d.Value,
destRect.ToSharpDX(),
(float)opacity,
BitmapInterpolationMode.Linear,
@ -115,7 +123,7 @@ namespace Avalonia.Direct2D1.Media
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 sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
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>
/// Pushes a clip rectange.
/// </summary>
@ -397,7 +424,7 @@ namespace Avalonia.Direct2D1.Media
return new ImageBrushImpl(
visualBrush,
_renderTarget,
new D2DBitmapImpl(intermediate.Bitmap),
new D2DBitmapImpl(_imagingFactory, intermediate.Bitmap),
destinationSize);
}
}

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

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

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

@ -1,20 +1,38 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media
{
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 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 virtual void Dispose()

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

@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
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
{
@ -26,32 +25,42 @@ namespace Avalonia.Direct2D1.Media
/// or if the render target is a <see cref="SharpDX.Direct2D1.DeviceContext"/>,
/// the device associated with this context, to be renderable.
/// </remarks>
public D2DBitmapImpl(Bitmap d2DBitmap)
public D2DBitmapImpl(WICFactory imagingFactory, Bitmap d2DBitmap)
: base(imagingFactory)
{
if (d2DBitmap == null) throw new ArgumentNullException(nameof(d2DBitmap));
_direct2D = d2DBitmap;
_direct2D = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
}
public override Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) => _direct2D;
public override int PixelWidth => _direct2D.PixelSize.Width;
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();
_direct2D.Dispose();
using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
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.IO;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using PixelFormat = SharpDX.WIC.PixelFormat;
using APixelFormat = Avalonia.Platform.PixelFormat;
using SharpDX.WIC;
using APixelFormat = Avalonia.Platform.PixelFormat;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media
{
@ -16,18 +15,14 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
public class WicBitmapImpl : BitmapImpl
{
private readonly ImagingFactory _factory;
/// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="fileName">The filename of the bitmap to load.</param>
public WicBitmapImpl(ImagingFactory factory, string fileName)
: base(factory)
{
_factory = factory;
using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.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="stream">The stream to read the bitmap from.</param>
public WicBitmapImpl(ImagingFactory factory, Stream stream)
: base(factory)
{
_factory = factory;
using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.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="pixelFormat">Pixel format</param>
public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null)
: base(factory)
{
if (!pixelFormat.HasValue)
pixelFormat = APixelFormat.Bgra8888;
_factory = factory;
PixelFormat = pixelFormat;
WicImpl = new Bitmap(
factory,
@ -71,10 +65,10 @@ namespace Avalonia.Direct2D1.Media
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);
_factory = factory;
PixelFormat = format;
using (var l = WicImpl.Lock(BitmapLockFlags.Write))
{
@ -113,41 +107,23 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
/// <param name="renderTarget">The render target.</param>
/// <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);
return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
return new OptionalDispose<D2DBitmap>(D2DBitmap.FromWicBitmap(renderTarget, converter), true);
}
/// <summary>
/// Saves the bitmap to a file.
/// </summary>
/// <param name="fileName">The filename.</param>
public override void Save(string fileName)
public override void Save(Stream stream)
{
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))
using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
using (var frame = new BitmapFrameEncode(encoder))
{
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
{
public class RenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly WicRenderTarget _target;
public RenderTargetBitmapImpl(
public WicRenderTargetBitmapImpl(
ImagingFactory imagingFactory,
Factory d2dFactory,
DirectWriteFactory dwriteFactory,
@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1.Media
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback)
{
return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory,
return new DrawingContextImpl(visualBrushRenderer, null, _target, _dwriteFactory, WicImagingFactory,
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 Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX.Direct2D1;
using DwFactory = SharpDX.DirectWrite.Factory;
using WicFactory = SharpDX.WIC.ImagingFactory;
namespace Avalonia.Direct2D1
{
public class RenderTarget : IRenderTarget
public class RenderTarget : IRenderTarget, ILayerFactory
{
/// <summary>
/// The render target.
@ -25,24 +27,13 @@ namespace Avalonia.Direct2D1
{
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<DwFactory>();
WicFactory = AvaloniaLocator.Current.GetService<WicFactory>();
_renderTarget = renderTarget;
}
/// <summary>
/// Gets the Direct2D factory.
/// </summary>
public Factory Direct2DFactory
{
get;
}
/// <summary>
/// Gets the DirectWrite factory.
/// </summary>
public DwFactory DirectWriteFactory
{
get;
}
public Factory Direct2DFactory { get; }
public DwFactory DirectWriteFactory { get; }
public WicFactory WicFactory { get; }
/// <summary>
/// Creates a drawing context for a rendering session.
@ -50,7 +41,16 @@ namespace Avalonia.Direct2D1
/// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns>
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()

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

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

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

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

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

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

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

@ -66,7 +66,7 @@ namespace Avalonia.Controls.UnitTests.Generators
target.Dematerialize(1, 1);
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));
}

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

@ -107,7 +107,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Content = child;
target.Content = null;
Assert.Equal(null, child.GetLogicalParent());
Assert.Null(child.GetLogicalParent());
Assert.Empty(target.GetLogicalChildren());
}
@ -120,7 +120,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Content = child;
target.Content = null;
Assert.Equal(null, child.GetVisualParent());
Assert.Null(child.GetVisualParent());
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.UpdateChild();
Assert.Equal(null, foo.Parent);
Assert.Null(foo.Parent);
logicalChildren = target.GetLogicalChildren();

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

@ -171,7 +171,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.SelectedItem = new Item();
Assert.Equal(null, target.SelectedItem);
Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex);
}
@ -278,7 +278,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.Items = null;
Assert.Equal(null, target.SelectedItem);
Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex);
}
@ -305,7 +305,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
items.RemoveAt(1);
Assert.Equal(null, target.SelectedItem);
Assert.Null(target.SelectedItem);
Assert.Equal(-1, target.SelectedIndex);
}
@ -334,7 +334,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
items.Clear();
Assert.Equal(null, target.SelectedItem);
Assert.Null(target.SelectedItem);
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>();
Assert.Equal(-1, target.SelectedIndex);
Assert.Equal(null, target.SelectedItem);
Assert.Null(target.SelectedItem);
}
[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));
}
[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 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.Data;
using Xunit;
using System.Windows.Input;
using System;
namespace Avalonia.Markup.UnitTests
{
@ -118,6 +120,42 @@ namespace Avalonia.Markup.UnitTests
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
{
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);
Assert.Equal(null, target.Text);
Assert.Null(target.Text);
}
[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" };
Assert.Equal(null, selector.Select(null));
Assert.Null(selector.Select(null));
}
[Fact]
@ -73,7 +73,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates
var data = new Item() { StringValue = "Value1" };
Assert.Same(null, selector.Select(data));
Assert.Null(selector.Select(data));
}
[Fact]

2
tests/Avalonia.RenderTests/TestBase.cs

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

2
tests/Avalonia.UnitTests/TestRoot.cs

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

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

@ -30,7 +30,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: MockSceneBuilder(root).Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher.Object);
target.Start();
@ -55,7 +54,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher);
target.Start();
@ -75,7 +73,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher);
target.Start();
@ -111,7 +108,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher);
target.Start();
@ -146,22 +142,20 @@ namespace Avalonia.Visuals.UnitTests.Rendering
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 target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: layers.Object,
//layerFactory: layers.Object,
dispatcher: dispatcher);
target.Start();
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]
@ -185,25 +179,25 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var loop = new Mock<IRenderLoop>();
var layerFactory = new MockRenderLayerFactory(new Dictionary<IVisual, IRenderTargetBitmapImpl>
{
{ root, CreateLayer() },
{ border, CreateLayer() },
});
var rootLayer = CreateLayer();
var borderLayer = CreateLayer();
var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny<Size>()))
.Returns(rootLayer)
.Returns(borderLayer);
var loop = new Mock<IRenderLoop>();
var target = new DeferredRenderer(
root,
root,
loop.Object,
layerFactory: layerFactory,
dispatcher: new ImmediateDispatcher());
root.Renderer = target;
target.Start();
RunFrame(loop);
var rootContext = layerFactory.GetMockDrawingContext(root);
var borderContext = layerFactory.GetMockDrawingContext(border);
var rootContext = Mock.Get(rootLayer.CreateDrawingContext(null));
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.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
@ -223,7 +217,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
border.Opacity = 1;
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.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);
@ -246,13 +240,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
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)
{
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)));
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